In the above four articles, Android's graphics engine is viewed from the hardware abstraction layer, data drive, rendering, output and other aspects of the graphics engine. From the perspective of engine input, the data input end of the engine mainly includes UI, Camera, Media and so on. Today, from the perspective of UI, we take setContentView of Activity as the starting point, running through phonewindow, viewrootimpl, WindowManager and WindowManager Rservice up to the engine entry SufaceComposerClient and Surface.
1. Create rootview
frameworks\base\core\java\android\app\Activity.java
Calling the setContentView method in the onCreate() lifecycle function of Activity
public void setContentView(@LayoutRes int layoutResID) { getWindow().setContentView(layoutResID); //Setting up layout initWindowDecorActionBar(); }
frameworks\base\core\java\com\android\internal\policy\PhoneWindow.java
PhoneWindow inherits from the Window class and overloads the setContentView method of the parent class
@Override public void setContentView(int layoutResID) { ...... if (mContentParent == null) { installDecor(); // DecorView associated with Window } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) { mContentParent.removeAllViews(); } if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) { final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID, getContext()); transitionTo(newScene); } else { mLayoutInflater.inflate(layoutResID, mContentParent); // Analytical layout } mContentParent.requestApplyInsets(); final Callback cb = getCallback(); if (cb != null && !isDestroyed()) { cb.onContentChanged(); } mContentParentExplicitlySet = true; }
frameworks\base\core\java\com\android\internal\policy\PhoneWindow.java
Create a DecorView, add it to the PhoneWindow, analyze the layout, and add it to the DecorView
private void installDecor() { mForceDecorInstall = false; if (mDecor == null) { mDecor = generateDecor(-1); // Generate DecorView mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS); mDecor.setIsRootNamespace(true); if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) { mDecor.postOnAnimation(mInvalidatePanelMenuRunnable); } } else { mDecor.setWindow(this); //Associate PhoneWindow with DecorView } if (mContentParent == null) { mContentParent = generateLayout(mDecor); //Generate content area // Set up decor part of UI to ignore fitsSystemWindows if appropriate. mDecor.makeOptionalFitsSystemWindows(); final DecorContentParent decorContentParent = (DecorContentParent) mDecor.findViewById( R.id.decor_content_parent); } }
frameworks\base\core\java\android\app\ActivityThread.java
When the onResume function of Activity wakes up, the parsed View will be added to the PhoneWindow
final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) { ActivityClientRecord r = mActivities.get(token); if (r.window == null && !a.mFinished && willBeVisible) { r.window = r.activity.getWindow(); View decor = r.window.getDecorView(); decor.setVisibility(View.INVISIBLE); ViewManager wm = a.getWindowManager(); WindowManager.LayoutParams l = r.window.getAttributes(); a.mDecor = decor; l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION; l.softInputMode |= forwardBit; if (r.mPreserveWindow) { a.mWindowAdded = true; r.mPreserveWindow = false; // Normally the ViewRoot sets up callbacks with the Activity // in addView->ViewRootImpl#setView. If we are instead reusing // the decor view we have to notify the view root that the // callbacks may have changed. ViewRootImpl impl = decor.getViewRootImpl(); if (impl != null) { impl.notifyChildRebuilt(); } } if (a.mVisibleFromClient) { if (!a.mWindowAdded) { a.mWindowAdded = true; wm.addView(decor, l); //Call the addView method of ViewManager } else { // The activity will get a callback for this {@link LayoutParams} change // earlier. However, at that time the decor will not be set (this is set // in this method), so no action will be taken. This call ensures the // callback occurs with the decor set. a.onWindowAttributesChanged(l); } } } } }
frameworks\base\core\java\android\view\WindowManagerImpl.java
WindowManagerImpl inherits from WindowManager and indirectly from ViewManager
@Override public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) { applyDefaultToken(params); // mGlobal is an instance of WindowManagerGlobal mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow); }
frameworks\base\core\java\android\view\WindowManagerGlobal.java
WindowManagerGlobal
public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) { ..... ViewRootImpl root; // View panelParentView = null; synchronized (mLock) { ...... root = new ViewRootImpl(view.getContext(), display); //Newly build view.setLayoutParams(wparams); mViews.add(view); //Manage all views mRoots.add(root); //Manage all root ViewRootImpl mParams.add(wparams); //Manage layout parameters // do this last because it fires off messages to start doing things try { root.setView(view, wparams, panelParentView); // Set current root layout parameters } catch (RuntimeException e) { // BadTokenException or InvalidDisplayException, clean up. if (index >= 0) { removeViewLocked(index, true); } throw e; } } }
frameworks\base\core\java\android\view\ViewRootImpl.java
public ViewRootImpl(Context context, Display display) { mContext = context; mWindowSession = WindowManagerGlobal.getWindowSession(); //Get Session of WMS mDisplay = display; mBasePackageName = context.getBasePackageName(); mThread = Thread.currentThread(); mLocation = new WindowLeaked(null); mLocation.fillInStackTrace(); mWindow = new W(this); //Draw IWindow and receive commands from WMS ...... }
2. Connect to WindowManagerService
frameworks\base\core\java\android\view\WindowManagerGlobal.java
Connect WMS, get the connection sWindowSession
public static IWindowSession getWindowSession() { synchronized (WindowManagerGlobal.class) { if (sWindowSession == null) { try { InputMethodManager imm = InputMethodManager.getInstance(); IWindowManager windowManager = getWindowManagerService(); sWindowSession = windowManager.openSession( new IWindowSessionCallback.Stub() { @Override public void onAnimatorScaleChanged(float scale) { ValueAnimator.setDurationScale(scale); } }, imm.getClient(), imm.getInputContext()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } return sWindowSession; } } public static IWindowManager getWindowManagerService() { synchronized (WindowManagerGlobal.class) { if (sWindowManagerService == null) { sWindowManagerService = IWindowManager.Stub.asInterface( ServiceManager.getService("window")); try { if (sWindowManagerService != null) { ValueAnimator.setDurationScale( sWindowManagerService.getCurrentAnimatorScale()); } } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } return sWindowManagerService; } }
frameworks\base\services\core\java\com\android\server\wm\WindowManagerService.java
Return a Session token to the ViewRootImpl class
@Override public IWindowSession openSession(IWindowSessionCallback callback, IInputMethodClient client, IInputContext inputContext) { if (client == null) throw new IllegalArgumentException("null client"); if (inputContext == null) throw new IllegalArgumentException("null inputContext"); Session session = new Session(this, callback, client, inputContext); return session; }
3. Drawing of view
frameworks\base\core\java\android\view\ViewRootImpl.java
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) { synchronized (this) { if (mView == null) { mView = view; ...... requestLayout(); //Request layout ...... try { mOrigWindowType = mWindowAttributes.type; mAttachInfo.mRecomputeGlobalAttributes = true; collectViewAttributes(); // mWindow is a subclass W instance of IWinbdow res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes, getHostVisibility(), mDisplay.getDisplayId(), mAttachInfo.mContentInsets, mAttachInfo.mStableInsets, mAttachInfo.mOutsets, mInputChannel); } catch (RemoteException e) { ...... } finally { if (restore) { attrs.restore(); } } } } }
frameworks\base\core\java\android\view\ViewRootImpl.java
@Override public void requestLayout() { if (!mHandlingLayoutInLayoutRequest) { checkThread(); mLayoutRequested = true; scheduleTraversals(); //Scheduling rendering } }
void scheduleTraversals() { if (!mTraversalScheduled) { mTraversalScheduled = true; //Turn on the draw switch mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier(); mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); if (!mUnbufferedInputDispatch) { scheduleConsumeBatchedInput(); } notifyRendererOfFramePending(); pokeDrawLockIfNeeded(); } } final TraversalRunnable mTraversalRunnable = new TraversalRunnable(); final class TraversalRunnable implements Runnable { @Override public void run() { doTraversal(); //implement } }
void doTraversal() { if (mTraversalScheduled) { mTraversalScheduled = false; mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier); if (mProfile) { Debug.startMethodTracing("ViewAncestor"); } performTraversals(); // Famous performTraversals if (mProfile) { Debug.stopMethodTracing(); mProfile = false; } } }
frameworks\base\core\java\android\view\ViewRootImpl.java — performTraversals()
The performTraversals method is too long. Let's segment analysis
if (!cancelDraw && !newSurface) { if (mPendingTransitions != null && mPendingTransitions.size() > 0) { for (int i = 0; i < mPendingTransitions.size(); ++i) { mPendingTransitions.get(i).startChangingAnimations(); } mPendingTransitions.clear(); } performDraw(); //Draw } else { if (isViewVisible) { // Try again scheduleTraversals(); //retry } else if (mPendingTransitions != null && mPendingTransitions.size() > 0) { for (int i = 0; i < mPendingTransitions.size(); ++i) { mPendingTransitions.get(i).endChangingAnimations(); } mPendingTransitions.clear(); } } mIsInTraversal = false;
frameworks\base\core\java\android\view\ViewRootImpl.java
private void performDraw() { ..... final boolean fullRedrawNeeded = mFullRedrawNeeded; mFullRedrawNeeded = false; mIsDrawing = true; Trace.traceBegin(Trace.TRACE_TAG_VIEW, "draw"); try { draw(fullRedrawNeeded); } finally { mIsDrawing = false; Trace.traceEnd(Trace.TRACE_TAG_VIEW); } ..... }
frameworks\base\core\java\android\view\ViewRootImpl.java
/** * @return true if drawing was successful, false if an error occurred */ private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff, boolean scalingRequired, Rect dirty) { // Draw with software renderer. final Canvas canvas; try { final int left = dirty.left; final int top = dirty.top; final int right = dirty.right; final int bottom = dirty.bottom; canvas = mSurface.lockCanvas(dirty); //Lock canvas ...... } catch (Surface.OutOfResourcesException e) { handleOutOfResourcesException(e); return false; } catch (IllegalArgumentException e) { Log.e(mTag, "Could not lock surface", e); // Don't assume this is due to out of memory, it could be // something else, and if it is something else then we could // kill stuff (or ourself) for no reason. mLayoutRequested = true; // ask wm for a new surface next time. return false; } try { ...... } finally { try { surface.unlockCanvasAndPost(canvas); //Unlocking and submitting data } catch (IllegalArgumentException e) { Log.e(mTag, "Could not unlock surface", e); mLayoutRequested = true; // ask wm for a new surface next time. //noinspection ReturnInsideFinallyBlock return false; } if (LOCAL_LOGV) { Log.v(mTag, "Surface " + surface + " unlockCanvasAndPost"); } } return true; }
frameworks\base\core\java\android\view\Surface.java
private void unlockSwCanvasAndPost(Canvas canvas) { if (canvas != mCanvas) { throw new IllegalArgumentException("canvas object must be the same instance that " + "was previously returned by lockCanvas"); } if (mNativeObject != mLockedObject) { Log.w(TAG, "WARNING: Surface's mNativeObject (0x" + Long.toHexString(mNativeObject) + ") != mLockedObject (0x" + Long.toHexString(mLockedObject) +")"); } if (mLockedObject == 0) { throw new IllegalStateException("Surface was not locked"); } try { nativeUnlockCanvasAndPost(mLockedObject, canvas); } finally { nativeRelease(mLockedObject); mLockedObject = 0; } }
frameworks\base\core\jni\android_view_Surface.cpp
static void nativeUnlockCanvasAndPost(JNIEnv* env, jclass clazz, jlong nativeObject, jobject canvasObj) { sp<Surface> surface(reinterpret_cast<Surface *>(nativeObject)); if (!isSurfaceValid(surface)) { return; } // detach the canvas from the surface Canvas* nativeCanvas = GraphicsJNI::getNativeCanvas(env, canvasObj); nativeCanvas->setBitmap(SkBitmap()); // unlock surface status_t err = surface->unlockAndPost(); // finally, if you remember the function we analyzed last time if (err < 0) { doThrowIAE(env); } }
4. Connect to the graphic engine SurfaceFlinger
frameworks\base\core\java\android\view\ViewRootImpl.java
Add Window to WMS
@Override public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs, int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets, Rect outOutsets, InputChannel outInputChannel) { return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outContentInsets, outStableInsets, outOutsets, outInputChannel); }
public int addWindow(Session session, IWindow client, int seq, WindowManager.LayoutParams attrs, int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets, Rect outOutsets, InputChannel outInputChannel) { boolean reportNewConfig = false; WindowState parentWindow = null; long origId; final int callingUid = Binder.getCallingUid(); final int type = attrs.type; synchronized(mWindowMap) { if (!mDisplayReady) { throw new IllegalStateException("Display has not been initialialized"); } //Processing window level ....... win.attach(); //Associated graphics engine, win is WindowState type mWindowMap.put(client.asBinder(), win); win.mToken.addWindow(win); ........ } ...... return res; }
frameworks\base\services\core\java\com\android\server\wm\WindowState.java
void attach() { if (localLOGV) Slog.v(TAG, "Attaching " + this + " token=" + mToken); mSession.windowAddedLocked(mAttrs.packageName); //Window add }
frameworks\base\services\core\java\com\android\server\wm\Session.java
void windowAddedLocked(String packageName) { mPackageName = packageName; mRelayoutTag = "relayoutWindow: " + mPackageName; if (mSurfaceSession == null) { if (WindowManagerService.localLOGV) Slog.v( TAG_WM, "First window added to " + this + ", creating SurfaceSession"); mSurfaceSession = new SurfaceSession(); if (SHOW_TRANSACTIONS) Slog.i( TAG_WM, " NEW SURFACE SESSION " + mSurfaceSession); mService.mSessions.add(this); if (mLastReportedAnimatorScale != mService.getCurrentAnimatorScale()) { mService.dispatchNewAnimatorScaleLocked(this); } } mNumWindow++; }
frameworks\base\core\java\android\view\SurfaceSession.java
/** Create a new connection with the surface flinger. */ public SurfaceSession() { mNativeClient = nativeCreate(); }
frameworks\base\core\jni\android_view_SurfaceSession.cpp
Create SurfaceComposerClient
static jlong nativeCreate(JNIEnv* env, jclass clazz) { SurfaceComposerClient* client = new SurfaceComposerClient(); //finally, if you remember this class, it's a medium to connect to the graphics engine client->incStrong((void*)nativeCreate); return reinterpret_cast<jlong>(client); }
frameworks\native\libs\gui\SurfaceComposerClient.cpp
SurfaceComposerClient automatically connects to SurfaceFlinger when it is created
void SurfaceComposerClient::onFirstRef() { sp<ISurfaceComposer> sm(ComposerService::getComposerService()); if (sm != 0) { auto rootProducer = mParent.promote(); sp<ISurfaceComposerClient> conn; conn = (rootProducer != nullptr) ? sm->createScopedConnection(rootProducer) : sm->createConnection(); if (conn != 0) { mClient = conn; mStatus = NO_ERROR; } } }
5. Surface creation
frameworks\base\core\java\android\view\ViewRootImpl.java — performTraversals()
try { ...... //Layout window relayoutResult = relayoutWindow(params, viewVisibility, insetsPending); ...... }
frameworks\base\core\java\android\view\ViewRootImpl.java
private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility, boolean insetsPending) throws RemoteException { float appScale = mAttachInfo.mApplicationScale; boolean restore = false; if (params != null && mTranslator != null) { restore = true; params.backup(); mTranslator.translateWindowLayout(params); } ...... //layout of Session int relayoutResult = mWindowSession.relayout( mWindow, mSeq, params, (int) (mView.getMeasuredWidth() * appScale + 0.5f), (int) (mView.getMeasuredHeight() * appScale + 0.5f), viewVisibility, insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0, mWinFrame, mPendingOverscanInsets, mPendingContentInsets, mPendingVisibleInsets, mPendingStableInsets, mPendingOutsets, mPendingBackDropFrame, mPendingMergedConfiguration, mSurface); ..... return relayoutResult; }
frameworks\base\services\core\java\com\android\server\wm\Session.java
public int relayout(IWindow window, int seq, WindowManager.LayoutParams attrs, int requestedWidth, int requestedHeight, int viewFlags, int flags, Rect outFrame, Rect outOverscanInsets, Rect outContentInsets, Rect outVisibleInsets, Rect outStableInsets, Rect outsets, Rect outBackdropFrame, MergedConfiguration mergedConfiguration, Surface outSurface) { if (false) Slog.d(TAG_WM, ">>>>>> ENTERED relayout from " + Binder.getCallingPid()); Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, mRelayoutTag); int res = mService.relayoutWindow(this, window, seq, attrs, requestedWidth, requestedHeight, viewFlags, flags, outFrame, outOverscanInsets, outContentInsets, outVisibleInsets, outStableInsets, outsets, outBackdropFrame, mergedConfiguration, outSurface); Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); if (false) Slog.d(TAG_WM, "<<<<<< EXITING relayout to " + Binder.getCallingPid()); return res; }
frameworks\base\services\core\java\com\android\server\wm\WindowManagerService.java
public int relayoutWindow(Session session, IWindow client, int seq, WindowManager.LayoutParams attrs, int requestedWidth, int requestedHeight, int viewVisibility, int flags, Rect outFrame, Rect outOverscanInsets, Rect outContentInsets, Rect outVisibleInsets, Rect outStableInsets, Rect outOutsets, Rect outBackdropFrame, MergedConfiguration mergedConfiguration, Surface outSurface) { if (viewVisibility == View.VISIBLE && (win.mAppToken == null || win.mAttrs.type == TYPE_APPLICATION_STARTING || !win.mAppToken.isClientHidden())) { ...... try { //Create Surface result = createSurfaceControl(outSurface, result, win, winAnimator); } catch (Exception e) { mInputMonitor.updateInputWindowsLw(true /*force*/); Slog.w(TAG_WM, "Exception thrown when creating surface for client " + client + " (" + win.mAttrs.getTitle() + ")", e); Binder.restoreCallingIdentity(origId); return 0; } ...... } } Binder.restoreCallingIdentity(origId); return result; }
frameworks\base\services\core\java\com\android\server\wm\WindowManagerService.java
private int createSurfaceControl(Surface outSurface, int result, WindowState win, WindowStateAnimator winAnimator) { if (!win.mHasSurface) { result |= RELAYOUT_RES_SURFACE_CHANGED; } WindowSurfaceController surfaceController; try { Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "createSurfaceControl"); surfaceController = winAnimator.createSurfaceLocked(win.mAttrs.type, win.mOwnerUid); } finally { Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); } if (surfaceController != null) { surfaceController.getSurface(outSurface); if (SHOW_TRANSACTIONS) Slog.i(TAG_WM, " OUT SURFACE " + outSurface + ": copied"); } else { // For some reason there isn't a surface. Clear the // caller's object so they see the same state. Slog.w(TAG_WM, "Failed to create surface control for " + win); outSurface.release(); } return result; }
frameworks\base\services\core\java\com\android\server\wm\WindowStateAnimator.java
WindowSurfaceController createSurfaceLocked(int windowType, int ownerUid) { final WindowState w = mWin; if (w.restoreSavedSurface()) { if (DEBUG_ANIM) Slog.i(TAG, "createSurface: " + this + ": called when we had a saved surface"); return mSurfaceController; } if (mSurfaceController != null) { return mSurfaceController; } w.setHasSurface(false); try { ...... //Create WindowSurfaceController mSurfaceController = new WindowSurfaceController(mSession.mSurfaceSession, attrs.getTitle().toString(), width, height, format, flags, this, windowType, ownerUid); mSurfaceFormat = format; w.setHasSurface(true); } catch (OutOfResourcesException e) { Slog.w(TAG, "OutOfResourcesException creating surface"); mService.mRoot.reclaimSomeSurfaceMemory(this, "create", true); mDrawState = NO_SURFACE; return null; } ...... mLastHidden = true; if (WindowManagerService.localLOGV) Slog.v(TAG, "Created surface " + this); return mSurfaceController; }
frameworks\base\services\core\java\com\android\server\wm\WindowSurfaceController.java
public WindowSurfaceController(SurfaceSession s, String name, int w, int h, int format, int flags, WindowStateAnimator animator, int windowType, int ownerUid) { ...... if (DEBUG_SURFACE_TRACE) { mSurfaceControl = new SurfaceTrace( s, name, w, h, format, flags, windowType, ownerUid); } else { //Create SurfaceControl mSurfaceControl = new SurfaceControl( s, name, w, h, format, flags, windowType, ownerUid); Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); } ...... }
frameworks\base\core\java\android\view\SurfaceControl.java
Continue to call the nativeCreate method of JNI layer
public SurfaceControl(SurfaceSession session, String name, int w, int h, int format, int flags, SurfaceControl parent, int windowType, int ownerUid) throws OutOfResourcesException { ...... mNativeObject = nativeCreate(session, name, w, h, format, flags, parent != null ? parent.mNativeObject : 0, windowType, ownerUid); ...... }
frameworks\base\core\jni\android_view_SurfaceControl.cpp
JNI layer creates a local SurfaceControl. SurfaceControl has the function of creating Surface, which we mentioned in the previous article
static jlong nativeCreate(JNIEnv* env, jclass clazz, jobject sessionObj, jstring nameStr, jint w, jint h, jint format, jint flags, jlong parentObject, jint windowType, jint ownerUid) { ScopedUtfChars name(env, nameStr); sp<SurfaceComposerClient> client(android_view_SurfaceSession_getClient(env, sessionObj)); SurfaceControl *parent = reinterpret_cast<SurfaceControl*>(parentObject); //Familiar taste sp<SurfaceControl> surface = client->createSurface( String8(name.c_str()), w, h, format, flags, parent, windowType, ownerUid); if (surface == NULL) { jniThrowException(env, OutOfResourcesException, NULL); return 0; } surface->incStrong((void *)nativeCreate); return reinterpret_cast<jlong>(surface.get()); }
6. True and false Surface
frameworks\base\services\core\java\com\android\server\wm\WindowManagerService.java
In the createSurfaceControl function of WindowManagerService, after the SurfaceController is created, the following methods continue to be called
void getSurface(Surface outSurface) { outSurface.copyFrom(mSurfaceControl); }
frameworks\base\core\java\android\view\Surface.java
The famous copyFrom method, the way of changing civet for prince, is brilliant
public void copyFrom(SurfaceControl other) { ...... long newNativeObject = nativeGetFromSurfaceControl(surfaceControlPtr); ..... }
frameworks\base\core\jni\android_view_Surface.cpp
Retrieve Surface from SurfaceControl
static jlong nativeGetFromSurfaceControl(JNIEnv* env, jclass clazz, jlong surfaceControlNativeObj) { ...... sp<SurfaceControl> ctrl(reinterpret_cast<SurfaceControl *>(surfaceControlNativeObj)); sp<Surface> surface(ctrl->getSurface()); //The Surface object returned to the java layer is created if (surface != NULL) { surface->incStrong(&sRefBaseOwner); } return reinterpret_cast<jlong>(surface.get()); }
OK, that's it today!