【Android surface 】二:源码分析App的surface创建过程

文章目录

  • 画布surface
  • ViewRoot的创建&setView分析
    • setView
      • requestLayout
      • ViewRoot和WMS的关系
  • activity的UI绘制
      • draw
  • surface
    • jni层分析
      • Surface无参构造
      • SurfaceSession
      • SurfaceSession_init
    • surface的有参构造
      • Surface_copyFrom
      • Surface_writeToParcel
      • Surface_readFromParcel
  • 总结

上一篇我们分析到了Android创建了UI控件,但是作画的地方在哪里呢?
这里继续进行分析。

画布surface

Handle onto a raw buffer that is being managed by the screen compositor.
A Surface is generally created by or from a consumer of image buffers (such as a android.graphics.SurfaceTexture, android.media.MediaRecorder, or android.renderscript.Allocation), and is handed to some kind of producer (such as OpenGL, MediaPlayer, or android.hardware.camera2.CameraDevice#createCaptureSession) to draw into.
Note: A Surface acts like a weak reference to the consumer it is associated with. By itself it will not keep its parent consumer from being reclaimed.
https://developer.android.com/reference/kotlin/android/view/Surface?hl=en

从官方文档的描述,我们可以知道surface其实就是我们苦苦思索的”在哪里作画”的画布。相机、OpenGL、媒体播放器可以作为画家在上面作画。
Handle onto a raw buffer that is being managed by the screen compositor.这里说的 screen compositor其实就是SurfaceFlinger。我们可以把他们来一次具象化理解:

在这里插入图片描述
我们需要理解的是SurfaceFlinger和Surface的联系,这对于我们开发Android很重要。

ViewRoot的创建&setView分析

frameworks/base/core/java/android/view/ViewRoot.java

构造函数:

```java
public ViewRoot(Context context) {
        super();

        if (MEASURE_LATENCY && lt == null) {
            lt = new LatencyTimer(100, 1000);
        }

        // For debug only
        //++sInstanceCount;

        // Initialize the statics when this class is first instantiated. This is
        // done here instead of in the static block because Zygote does not
        // allow the spawning of threads.
        getWindowSession(context.getMainLooper());
        
        mThread = Thread.currentThread();
        mLocation = new WindowLeaked(null);
        mLocation.fillInStackTrace();
        mWidth = -1;
        mHeight = -1;
        mDirty = new Rect();
        mTempRect = new Rect();
        mVisRect = new Rect();
        mWinFrame = new Rect();
        mWindow = new W(this, context);
        mInputMethodCallback = new InputMethodCallback(this);
        mViewVisibility = View.GONE;
        mTransparentRegion = new Region();
        mPreviousTransparentRegion = new Region();
        mFirst = true; // true for the first time the view is added
        mAdded = false;
        mAttachInfo = new View.AttachInfo(sWindowSession, mWindow, this, this);
        mViewConfiguration = ViewConfiguration.get(context);
        mDensity = context.getResources().getDisplayMetrics().densityDpi;
    }

getWindowSession:

 public static IWindowSession getWindowSession(Looper mainLooper) {
        synchronized (mStaticInit) {
            if (!mInitialized) {
                try {
                    InputMethodManager imm = InputMethodManager.getInstance(mainLooper);
                    //binder通信的写法,很熟悉
                    sWindowSession = IWindowManager.Stub.asInterface(
                            ServiceManager.getService("window"))
                            .openSession(imm.getClient(), imm.getInputContext());
                    mInitialized = true;
                } catch (RemoteException e) {
                }
            }
            return sWindowSession;
        }
    }

这个函数其实会建立Activity里面ViewRoot和WindowManagerService的关系。
我们知道,WindowManagerService由SystemServer进程启动,surfaceflinger服务也在这个进程中。所以activity的显示是需要跨进程通信的。

setView

接着分析

  /**
     * We have one child
     */
     //第一个参数是decorview
    public void setView(View view, WindowManager.LayoutParams attrs,
            View panelParentView) {
        synchronized (this) {
            if (mView == null) {
                mView = view;
                mWindowAttributes.copyFrom(attrs);
                attrs = mWindowAttributes;
                Resources resources = mView.getContext().getResources();
                CompatibilityInfo compatibilityInfo = resources.getCompatibilityInfo();
                mTranslator = compatibilityInfo.getTranslator();

                if (mTranslator != null || !compatibilityInfo.supportsScreen()) {
                    mSurface.setCompatibleDisplayMetrics(resources.getDisplayMetrics(),
                            mTranslator);
                }

                boolean restore = false;
                if (mTranslator != null) {
                    restore = true;
                    attrs.backup();
                    mTranslator.translateWindowLayout(attrs);
                }
                if (DEBUG_LAYOUT) Log.d(TAG, "WindowLayout in setView:" + attrs);

                if (!compatibilityInfo.supportsScreen()) {
                    attrs.flags |= WindowManager.LayoutParams.FLAG_COMPATIBLE_WINDOW;
                }

                mSoftInputMode = attrs.softInputMode;
                mWindowAttributesChanged = true;
                mAttachInfo.mRootView = view;
                mAttachInfo.mScalingRequired = mTranslator != null;
                mAttachInfo.mApplicationScale =
                        mTranslator == null ? 1.0f : mTranslator.applicationScale;
                if (panelParentView != null) {
                    mAttachInfo.mPanelParentWindowToken
                            = panelParentView.getApplicationWindowToken();
                }
                mAdded = true;
                int res; /* = WindowManagerImpl.ADD_OKAY; */

                // Schedule the first layout -before- adding to the window
                // manager, to make sure we do the relayout before receiving
                // any other events from the system.
                //重点
                requestLayout();
                try {
                //重点
                    res = sWindowSession.add(mWindow, mWindowAttributes,
                            getHostVisibility(), mAttachInfo.mContentInsets);
                } catch (RemoteException e) {
                    mAdded = false;
                    mView = null;
                    mAttachInfo.mRootView = null;
                    unscheduleTraversals();
                    throw new RuntimeException("Adding window failed", e);
                } finally {
                    if (restore) {
                        attrs.restore();
                    }
                }

                if (mTranslator != null) {
                    mTranslator.translateRectInScreenToAppWindow(mAttachInfo.mContentInsets);
                }
                mPendingContentInsets.set(mAttachInfo.mContentInsets);
                mPendingVisibleInsets.set(0, 0, 0, 0);
                if (Config.LOGV) Log.v("ViewRoot", "Added window " + mWindow);
                if (res < WindowManagerImpl.ADD_OKAY) {
                    mView = null;
                    mAttachInfo.mRootView = null;
                    mAdded = false;
                    unscheduleTraversals();
                    switch (res) {
                        case WindowManagerImpl.ADD_BAD_APP_TOKEN:
                        case WindowManagerImpl.ADD_BAD_SUBWINDOW_TOKEN:
                            throw new WindowManagerImpl.BadTokenException(
                                "Unable to add window -- token " + attrs.token
                                + " is not valid; is your activity running?");
                        case WindowManagerImpl.ADD_NOT_APP_TOKEN:
                            throw new WindowManagerImpl.BadTokenException(
                                "Unable to add window -- token " + attrs.token
                                + " is not for an application");
                        case WindowManagerImpl.ADD_APP_EXITING:
                            throw new WindowManagerImpl.BadTokenException(
                                "Unable to add window -- app for token " + attrs.token
                                + " is exiting");
                        case WindowManagerImpl.ADD_DUPLICATE_ADD:
                            throw new WindowManagerImpl.BadTokenException(
                                "Unable to add window -- window " + mWindow
                                + " has already been added");
                        case WindowManagerImpl.ADD_STARTING_NOT_NEEDED:
                            // Silently ignore -- we would have just removed it
                            // right away, anyway.
                            return;
                        case WindowManagerImpl.ADD_MULTIPLE_SINGLETON:
                            throw new WindowManagerImpl.BadTokenException(
                                "Unable to add window " + mWindow +
                                " -- another window of this type already exists");
                        case WindowManagerImpl.ADD_PERMISSION_DENIED:
                            throw new WindowManagerImpl.BadTokenException(
                                "Unable to add window " + mWindow +
                                " -- permission denied for this window type");
                    }
                    throw new RuntimeException(
                        "Unable to add window -- unknown error code " + res);
                }
                view.assignParent(this);
                mAddedTouchMode = (res&WindowManagerImpl.ADD_FLAG_IN_TOUCH_MODE) != 0;
                mAppVisible = (res&WindowManagerImpl.ADD_FLAG_APP_VISIBLE) != 0;
            }
        }
    }

这里做了几件事情,保存传入的decorview,调用了requestLayout,跨进程通信调用了add函数。

requestLayout

frameworks/base/core/java/android/view/ViewRoot.java
    /**
     * {@inheritDoc}
     */
    public void requestLayout() {
        checkThread();
        mLayoutRequested = true;
        scheduleTraversals();
    }
frameworks/base/core/java/android/view/ViewRoot.java
 public void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            sendEmptyMessage(DO_TRAVERSAL);
        }
    }

ViewRoot和WMS进行了交互,ViewRoot调用openSession拿到了IWindowSession,调用它的add函数,把W类型的mWindow对象作为参数传入。

ViewRoot和WMS的关系

来看下WMS的openSession

frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
   @Override
    public IWindowSession openSession(IWindowSessionCallback callback) {
        return new Session(this, callback);
    }
frameworks/base/services/java/com/android/server/WindowManagerService.java
  public int add(IWindow window, WindowManager.LayoutParams attrs,
                int viewVisibility, Rect outContentInsets) {
            return addWindow(this, window, attrs, viewVisibility, outContentInsets);
        }
   public int addWindow(Session session, IWindow client,
            WindowManager.LayoutParams attrs, int viewVisibility,
            Rect outContentInsets) {
       ...
			//重点
            win = new WindowState(session, client, token,
                    attachedWindow, attrs, viewVisibility);
            if (win.mDeathRecipient == null) {
                // Client has apparently died, so there is no reason to
                // continue.
                Slog.w(TAG, "Adding window client " + client.asBinder()
                        + " that is dead, aborting.");
                return WindowManagerImpl.ADD_APP_EXITING;
            }

            mPolicy.adjustWindowParamsLw(win.mAttrs);

            res = mPolicy.prepareAddWindowLw(win, attrs);
            if (res != WindowManagerImpl.ADD_OKAY) {
                return res;
            }

            // From now on, no exceptions or errors allowed!

            res = WindowManagerImpl.ADD_OKAY;

            final long origId = Binder.clearCallingIdentity();

            if (addToken) {
                mTokenMap.put(attrs.token, token);
                mTokenList.add(token);
            }
            //重点
            win.attach();
            mWindowMap.put(client.asBinder(), win);
	...

        return res;
    }

WindowState的attach:

     void attach() {
            if (localLOGV) Slog.v(
                TAG, "Attaching " + this + " token=" + mToken
                + ", list=" + mToken.windows);
            mSession.windowAddedLocked();
        }

     void windowAddedLocked() {
            if (mSurfaceSession == null) {
                if (localLOGV) Slog.v(
                    TAG, "First window added to " + this + ", creating SurfaceSession");
                    //重点
                mSurfaceSession = new SurfaceSession();
                if (SHOW_TRANSACTIONS) Slog.i(
                        TAG, "  NEW SURFACE SESSION " + mSurfaceSession);
                mSessions.add(this);
            }
            mNumWindow++;
        }

到这里,我们得总结一番:
在这里插入图片描述

activity的UI绘制

 @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
  
        case DO_TRAVERSAL:
            if (mProfile) {
                Debug.startMethodTracing("ViewRoot");
            }

            performTraversals();

            if (mProfile) {
                Debug.stopMethodTracing();
                mProfile = false;
            }
            break;
       
    }

performTraversals:

 private void performTraversals() {
 ...
        // cache mView since it is used so much below...
        final View host = mView;
        //重点
   relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
...
   if (!cancelDraw && !newSurface) {
            mFullRedrawNeeded = false;
            //绘制
            draw(fullRedrawNeeded);
...

relayoutWindow:

private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility,
            boolean insetsPending) throws RemoteException {

        ...
        //重点
        int relayoutResult = sWindowSession.relayout(
                mWindow, params,
                (int) (mView.mMeasuredWidth * appScale + 0.5f),
                (int) (mView.mMeasuredHeight * appScale + 0.5f),
                viewVisibility, insetsPending, mWinFrame,
                mPendingContentInsets, mPendingVisibleInsets,
                mPendingConfiguration, mSurface);//mSurface
  		...
        return relayoutResult;
    }

draw

frameworks/base/core/java/android/view/ViewRoot.java
private void draw(boolean fullRedrawNeeded) {
        Surface surface = mSurface;//viewroot的成员变量
    ...

        Rect dirty = mDirty;
        if (mUseGL) {
            if (!dirty.isEmpty()) {
                Canvas canvas = mGlCanvas;
                if (mGL != null && canvas != null) {
                    mGL.glDisable(GL_SCISSOR_TEST);
                    mGL.glClearColor(0, 0, 0, 0);
                    mGL.glClear(GL_COLOR_BUFFER_BIT);
                    mGL.glEnable(GL_SCISSOR_TEST);

                    mAttachInfo.mDrawingTime = SystemClock.uptimeMillis();
                    mAttachInfo.mIgnoreDirtyState = true;
                    mView.mPrivateFlags |= View.DRAWN;

                    int saveCount = canvas.save(Canvas.MATRIX_SAVE_FLAG);
                    try {
                        canvas.translate(0, -yoff);
                        if (mTranslator != null) {
                            mTranslator.translateCanvas(canvas);
                        }
                        canvas.setScreenDensity(scalingRequired
                                ? DisplayMetrics.DENSITY_DEVICE : 0);
                        mView.draw(canvas);
                        if (Config.DEBUG && ViewDebug.consistencyCheckEnabled) {
                            mView.dispatchConsistencyCheck(ViewDebug.CONSISTENCY_DRAWING);
                        }
                    } finally {
                        canvas.restoreToCount(saveCount);
                    }

                    mAttachInfo.mIgnoreDirtyState = false;

                    mEgl.eglSwapBuffers(mEglDisplay, mEglSurface);
                    checkEglErrors();

                    if (SHOW_FPS || Config.DEBUG && ViewDebug.showFps) {
                        int now = (int)SystemClock.elapsedRealtime();
                        if (sDrawTime != 0) {
                            nativeShowFPS(canvas, now - sDrawTime);
                        }
                        sDrawTime = now;
                    }
                }
            }
            if (scrolling) {
                mFullRedrawNeeded = true;
                scheduleTraversals();
            }
            return;
        }

  ...

        Canvas canvas;
        try {
            int left = dirty.left;
            int top = dirty.top;
            int right = dirty.right;
            int bottom = dirty.bottom;
            //lock canvas
            canvas = surface.lockCanvas(dirty);

    ...

        try {
            if (!dirty.isEmpty() || mIsAnimating) {
                long startTime = 0L;

                if (DEBUG_ORIENTATION || DEBUG_DRAW) {
                    Log.v("ViewRoot", "Surface " + surface + " drawing to bitmap w="
                            + canvas.getWidth() + ", h=" + canvas.getHeight());
                    //canvas.drawARGB(255, 255, 0, 0);
                }

                if (Config.DEBUG && ViewDebug.profileDrawing) {
                    startTime = SystemClock.elapsedRealtime();
                }

                // If this bitmap's format includes an alpha channel, we
                // need to clear it before drawing so that the child will
                // properly re-composite its drawing on a transparent
                // background. This automatically respects the clip/dirty region
                // or
                // If we are applying an offset, we need to clear the area
                // where the offset doesn't appear to avoid having garbage
                // left in the blank areas.
                if (!canvas.isOpaque() || yoff != 0) {
                    canvas.drawColor(0, PorterDuff.Mode.CLEAR);
                }

                dirty.setEmpty();
                mIsAnimating = false;
                mAttachInfo.mDrawingTime = SystemClock.uptimeMillis();
                mView.mPrivateFlags |= View.DRAWN;

                if (DEBUG_DRAW) {
                    Context cxt = mView.getContext();
                    Log.i(TAG, "Drawing: package:" + cxt.getPackageName() +
                            ", metrics=" + cxt.getResources().getDisplayMetrics() +
                            ", compatibilityInfo=" + cxt.getResources().getCompatibilityInfo());
                }
                int saveCount = canvas.save(Canvas.MATRIX_SAVE_FLAG);
                try {
                    canvas.translate(0, -yoff);
                    if (mTranslator != null) {
                        mTranslator.translateCanvas(canvas);
                    }
                    canvas.setScreenDensity(scalingRequired
                            ? DisplayMetrics.DENSITY_DEVICE : 0);
                           //在canvas中绘制
                    mView.draw(canvas);
                } finally {
                    mAttachInfo.mIgnoreDirtyState = false;
                    canvas.restoreToCount(saveCount);
                }

                if (Config.DEBUG && ViewDebug.consistencyCheckEnabled) {
                    mView.dispatchConsistencyCheck(ViewDebug.CONSISTENCY_DRAWING);
                }

                if (SHOW_FPS || Config.DEBUG && ViewDebug.showFps) {
                    int now = (int)SystemClock.elapsedRealtime();
                    if (sDrawTime != 0) {
                        nativeShowFPS(canvas, now - sDrawTime);
                    }
                    sDrawTime = now;
                }

                if (Config.DEBUG && ViewDebug.profileDrawing) {
                    EventLog.writeEvent(60000, SystemClock.elapsedRealtime() - startTime);
                }
            }

        } finally {
        	//unlock画布
            surface.unlockCanvasAndPost(canvas);
        }

        if (LOCAL_LOGV) {
            Log.v("ViewRoot", "Surface " + surface + " unlockCanvasAndPost");
        }

        if (scrolling) {
            mFullRedrawNeeded = true;
            scheduleTraversals();
        }
    }

总结下,ViewRoot能处理Handler的消息,Activity的显示就是由ViewRoot在它的performTraversals函数中完成的。
activity的绘制就是从mSurface中lock一块canvas,然后交给mView去画画,最后unlock并释放这块canvas。

surface

在ViewRoot构造时候,会创建一个surface,viewroot通过IWindowSession和WMS交互,WMS中调用的attach函数会构造一个SurfaceSession。而ViewRoot在performTraversal的过程会调用IWindowSession的relayout函数。
从relayout开始,relayout是一个跨进程调用,实际工作由WMS完成。

客户端的relayoutWindow:

    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);
        }
        if (params != null) {
            if (DBG) Log.d(TAG, "WindowLayout in layoutWindow:" + params);
        }
        mPendingConfiguration.seq = 0;
        int relayoutResult = sWindowSession.relayout(
                mWindow, params,
                (int) (mView.mMeasuredWidth * appScale + 0.5f),
                (int) (mView.mMeasuredHeight * appScale + 0.5f),
                viewVisibility, insetsPending, mWinFrame,
                mPendingContentInsets, mPendingVisibleInsets,
                mPendingConfiguration, mSurface);//传递了surface
        if (restore) {
            params.restore();
        }
        
        if (mTranslator != null) {
            mTranslator.translateRectInScreenToAppWinFrame(mWinFrame);
            mTranslator.translateRectInScreenToAppWindow(mPendingContentInsets);
            mTranslator.translateRectInScreenToAppWindow(mPendingVisibleInsets);
        }
        return relayoutResult;
    }

服务端的relayoutWindow

frameworks/base/services/java/com/android/server/WindowManagerService.java
   public int relayoutWindow(Session session, IWindow client,
            WindowManager.LayoutParams attrs, int requestedWidth,
            int requestedHeight, int viewVisibility, boolean insetsPending,
            Rect outFrame, Rect outContentInsets, Rect outVisibleInsets,
            Configuration outConfig, Surface outSurface) {
        boolean displayed = false;
        boolean inTouchMode;
        boolean configChanged;
        long origId = Binder.clearCallingIdentity();

        synchronized(mWindowMap) {
            WindowState win = windowForClientLocked(session, client, false);
            if (win == null) {
                return 0;
            }
            win.mRequestedWidth = requestedWidth;
            win.mRequestedHeight = requestedHeight;

            if (attrs != null) {
                mPolicy.adjustWindowParamsLw(attrs);
            }

            int attrChanges = 0;
            int flagChanges = 0;
            if (attrs != null) {
                flagChanges = win.mAttrs.flags ^= attrs.flags;
                attrChanges = win.mAttrs.copyFrom(attrs);
            }

            if (DEBUG_LAYOUT) Slog.v(TAG, "Relayout " + win + ": " + win.mAttrs);

            if ((attrChanges & WindowManager.LayoutParams.ALPHA_CHANGED) != 0) {
                win.mAlpha = attrs.alpha;
            }

            final boolean scaledWindow =
                ((win.mAttrs.flags & WindowManager.LayoutParams.FLAG_SCALED) != 0);

            if (scaledWindow) {
                // requested{Width|Height} Surface's physical size
                // attrs.{width|height} Size on screen
                win.mHScale = (attrs.width  != requestedWidth)  ?
                        (attrs.width  / (float)requestedWidth) : 1.0f;
                win.mVScale = (attrs.height != requestedHeight) ?
                        (attrs.height / (float)requestedHeight) : 1.0f;
            } else {
                win.mHScale = win.mVScale = 1;
            }

            boolean imMayMove = (flagChanges&(
                    WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM |
                    WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE)) != 0;

            boolean focusMayChange = win.mViewVisibility != viewVisibility
                    || ((flagChanges&WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) != 0)
                    || (!win.mRelayoutCalled);

            boolean wallpaperMayMove = win.mViewVisibility != viewVisibility
                    && (win.mAttrs.flags & FLAG_SHOW_WALLPAPER) != 0;

            win.mRelayoutCalled = true;
            final int oldVisibility = win.mViewVisibility;
            win.mViewVisibility = viewVisibility;
            if (viewVisibility == View.VISIBLE &&
                    (win.mAppToken == null || !win.mAppToken.clientHidden)) {
                displayed = !win.isVisibleLw();
                if (win.mExiting) {
                    win.mExiting = false;
                    win.mAnimation = null;
                }
                if (win.mDestroying) {
                    win.mDestroying = false;
                    mDestroySurface.remove(win);
                }
                if (oldVisibility == View.GONE) {
                    win.mEnterAnimationPending = true;
                }
                if (displayed) {
                    if (win.mSurface != null && !win.mDrawPending
                            && !win.mCommitDrawPending && !mDisplayFrozen
                            && mPolicy.isScreenOn()) {
                        applyEnterAnimationLocked(win);
                    }
                    if ((win.mAttrs.flags
                            & WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON) != 0) {
                        if (DEBUG_VISIBILITY) Slog.v(TAG,
                                "Relayout window turning screen on: " + win);
                        win.mTurnOnScreen = true;
                    }
                    int diff = 0;
                    if (win.mConfiguration != mCurConfiguration
                            && (win.mConfiguration == null
                                    || (diff=mCurConfiguration.diff(win.mConfiguration)) != 0)) {
                        win.mConfiguration = mCurConfiguration;
                        if (DEBUG_CONFIGURATION) {
                            Slog.i(TAG, "Window " + win + " visible with new config: "
                                    + win.mConfiguration + " / 0x"
                                    + Integer.toHexString(diff));
                        }
                        outConfig.setTo(mCurConfiguration);
                    }
                }
                if ((attrChanges&WindowManager.LayoutParams.FORMAT_CHANGED) != 0) {
                    // To change the format, we need to re-build the surface.
                    win.destroySurfaceLocked();
                    displayed = true;
                }
                try {
                //win就是WinStat,创建本地的surface对象
                    Surface surface = win.createSurfaceLocked();
                    if (surface != null) {
                    //在outSurface调用copyFrom,将本地surface拷贝到outSurface中
                        outSurface.copyFrom(surface);
                        win.mReportDestroySurface = false;
                        win.mSurfacePendingDestroy = false;
                        if (SHOW_TRANSACTIONS) Slog.i(TAG,
                                "  OUT SURFACE " + outSurface + ": copied");
                    } else {
                        // For some reason there isn't a surface.  Clear the
                        // caller's object so they see the same state.
                        outSurface.release();
                    }
                } catch (Exception e) {
                    Slog.w(TAG, "Exception thrown when creating surface for client "
                             + client + " (" + win.mAttrs.getTitle() + ")",
                             e);
                    Binder.restoreCallingIdentity(origId);
                    return 0;
                }
                if (displayed) {
                    focusMayChange = true;
                }
                if (win.mAttrs.type == TYPE_INPUT_METHOD
                        && mInputMethodWindow == null) {
                    mInputMethodWindow = win;
                    imMayMove = true;
                }
                if (win.mAttrs.type == TYPE_BASE_APPLICATION
                        && win.mAppToken != null
                        && win.mAppToken.startingWindow != null) {
                    // Special handling of starting window over the base
                    // window of the app: propagate lock screen flags to it,
                    // to provide the correct semantics while starting.
                    final int mask =
                        WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
                        | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD
                        | WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON;
                    WindowManager.LayoutParams sa = win.mAppToken.startingWindow.mAttrs;
                    sa.flags = (sa.flags&~mask) | (win.mAttrs.flags&mask);
                }
            } else {
                win.mEnterAnimationPending = false;
                if (win.mSurface != null) {
                    if (DEBUG_VISIBILITY) Slog.i(TAG, "Relayout invis " + win
                            + ": mExiting=" + win.mExiting
                            + " mSurfacePendingDestroy=" + win.mSurfacePendingDestroy);
                    // If we are not currently running the exit animation, we
                    // need to see about starting one.
                    if (!win.mExiting || win.mSurfacePendingDestroy) {
                        // Try starting an animation; if there isn't one, we
                        // can destroy the surface right away.
                        int transit = WindowManagerPolicy.TRANSIT_EXIT;
                        if (win.getAttrs().type == TYPE_APPLICATION_STARTING) {
                            transit = WindowManagerPolicy.TRANSIT_PREVIEW_DONE;
                        }
                        if (!win.mSurfacePendingDestroy && win.isWinVisibleLw() &&
                              applyAnimationLocked(win, transit, false)) {
                            focusMayChange = true;
                            win.mExiting = true;
                            mKeyWaiter.finishedKey(session, client, true,
                                    KeyWaiter.RETURN_NOTHING);
                        } else if (win.isAnimating()) {
                            // Currently in a hide animation... turn this into
                            // an exit.
                            win.mExiting = true;
                        } else if (win == mWallpaperTarget) {
                            // If the wallpaper is currently behind this
                            // window, we need to change both of them inside
                            // of a transaction to avoid artifacts.
                            win.mExiting = true;
                            win.mAnimating = true;
                        } else {
                            if (mInputMethodWindow == win) {
                                mInputMethodWindow = null;
                            }
                            win.destroySurfaceLocked();
                        }
                    }
                }

                if (win.mSurface == null || (win.getAttrs().flags
                        & WindowManager.LayoutParams.FLAG_KEEP_SURFACE_WHILE_ANIMATING) == 0
                        || win.mSurfacePendingDestroy) {
                    // We are being called from a local process, which
                    // means outSurface holds its current surface.  Ensure the
                    // surface object is cleared, but we don't want it actually
                    // destroyed at this point.
                    win.mSurfacePendingDestroy = false;
                    outSurface.release();
                    if (DEBUG_VISIBILITY) Slog.i(TAG, "Releasing surface in: " + win);
                } else if (win.mSurface != null) {
                    if (DEBUG_VISIBILITY) Slog.i(TAG,
                            "Keeping surface, will report destroy: " + win);
                    win.mReportDestroySurface = true;
                    outSurface.copyFrom(win.mSurface);
                }
            }

            if (focusMayChange) {
                //System.out.println("Focus may change: " + win.mAttrs.getTitle());
                if (updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES)) {
                    imMayMove = false;
                }
                //System.out.println("Relayout " + win + ": focus=" + mCurrentFocus);
            }

            // updateFocusedWindowLocked() already assigned layers so we only need to
            // reassign them at this point if the IM window state gets shuffled
            boolean assignLayers = false;

            if (imMayMove) {
                if (moveInputMethodWindowsIfNeededLocked(false) || displayed) {
                    // Little hack here -- we -should- be able to rely on the
                    // function to return true if the IME has moved and needs
                    // its layer recomputed.  However, if the IME was hidden
                    // and isn't actually moved in the list, its layer may be
                    // out of data so we make sure to recompute it.
                    assignLayers = true;
                }
            }
            if (wallpaperMayMove) {
                if ((adjustWallpaperWindowsLocked()&ADJUST_WALLPAPER_LAYERS_CHANGED) != 0) {
                    assignLayers = true;
                }
            }

            mLayoutNeeded = true;
            win.mGivenInsetsPending = insetsPending;
            if (assignLayers) {
                assignLayersLocked();
            }
            configChanged = updateOrientationFromAppTokensLocked();
            performLayoutAndPlaceSurfacesLocked();
            if (displayed && win.mIsWallpaper) {
                updateWallpaperOffsetLocked(win, mDisplay.getWidth(),
                        mDisplay.getHeight(), false);
            }
            if (win.mAppToken != null) {
                win.mAppToken.updateReportedVisibilityLocked();
            }
            outFrame.set(win.mFrame);
            outContentInsets.set(win.mContentInsets);
            outVisibleInsets.set(win.mVisibleInsets);
            if (localLOGV) Slog.v(
                TAG, "Relayout given client " + client.asBinder()
                + ", requestedWidth=" + requestedWidth
                + ", requestedHeight=" + requestedHeight
                + ", viewVisibility=" + viewVisibility
                + "\nRelayout returning frame=" + outFrame
                + ", surface=" + outSurface);

            if (localLOGV || DEBUG_FOCUS) Slog.v(
                TAG, "Relayout of " + win + ": focusMayChange=" + focusMayChange);

            inTouchMode = mInTouchMode;
        }

        if (configChanged) {
            sendNewConfiguration();
        }

        Binder.restoreCallingIdentity(origId);

        return (inTouchMode ? WindowManagerImpl.RELAYOUT_IN_TOUCH_MODE : 0)
                | (displayed ? WindowManagerImpl.RELAYOUT_FIRST_TIME : 0);
    }

createSurfaceLocked

frameworks/base/services/java/com/android/server/WindowManagerService.java:WindowState
        Surface createSurfaceLocked() {
            if (mSurface == null) {
                mReportDestroySurface = false;
                mSurfacePendingDestroy = false;
                mDrawPending = true;
                mCommitDrawPending = false;
                mReadyToShow = false;
                if (mAppToken != null) {
                    mAppToken.allDrawn = false;
                }

                int flags = 0;
                if (mAttrs.memoryType == MEMORY_TYPE_PUSH_BUFFERS) {
                    flags |= Surface.PUSH_BUFFERS;
                }

                if ((mAttrs.flags&WindowManager.LayoutParams.FLAG_SECURE) != 0) {
                    flags |= Surface.SECURE;
                }
                if (DEBUG_VISIBILITY) Slog.v(
                    TAG, "Creating surface in session "
                    + mSession.mSurfaceSession + " window " + this
                    + " w=" + mFrame.width()
                    + " h=" + mFrame.height() + " format="
                    + mAttrs.format + " flags=" + flags);

                int w = mFrame.width();
                int h = mFrame.height();
                if ((mAttrs.flags & LayoutParams.FLAG_SCALED) != 0) {
                    // for a scaled surface, we always want the requested
                    // size.
                    w = mRequestedWidth;
                    h = mRequestedHeight;
                }

                // Something is wrong and SurfaceFlinger will not like this,
                // try to revert to sane values
                if (w <= 0) w = 1;
                if (h <= 0) h = 1;

                mSurfaceShown = false;
                mSurfaceLayer = 0;
                mSurfaceAlpha = 1;
                mSurfaceX = 0;
                mSurfaceY = 0;
                mSurfaceW = w;
                mSurfaceH = h;
                try {
                    mSurface = new Surface(
                            mSession.mSurfaceSession, mSession.mPid,
                            mAttrs.getTitle().toString(),
                            0, w, h, mAttrs.format, flags);
                    if (SHOW_TRANSACTIONS) Slog.i(TAG, "  CREATE SURFACE "
                            + mSurface + " IN SESSION "
                            + mSession.mSurfaceSession
                            + ": pid=" + mSession.mPid + " format="
                            + mAttrs.format + " flags=0x"
                            + Integer.toHexString(flags)
                            + " / " + this);
                } catch (Surface.OutOfResourcesException e) {
                    Slog.w(TAG, "OutOfResourcesException creating surface");
                    reclaimSomeSurfaceMemoryLocked(this, "create");
                    return null;
                } catch (Exception e) {
                    Slog.e(TAG, "Exception creating surface", e);
                    return null;
                }

                if (localLOGV) Slog.v(
                    TAG, "Got surface: " + mSurface
                    + ", set left=" + mFrame.left + " top=" + mFrame.top
                    + ", animLayer=" + mAnimLayer);
                if (SHOW_TRANSACTIONS) {
                    Slog.i(TAG, ">>> OPEN TRANSACTION");
                    if (SHOW_TRANSACTIONS) logSurface(this,
                            "CREATE pos=(" + mFrame.left + "," + mFrame.top + ") (" +
                            mFrame.width() + "x" + mFrame.height() + "), layer=" +
                            mAnimLayer + " HIDE", null);
                }
                Surface.openTransaction();//打开一个事务
                try {
                    try {
                        mSurfaceX = mFrame.left + mXOffset;
                        mSurfaceY = mFrame.top + mYOffset;
                        mSurface.setPosition(mSurfaceX, mSurfaceY);
                        mSurfaceLayer = mAnimLayer;
                        mSurface.setLayer(mAnimLayer);
                        mSurfaceShown = false;
                        mSurface.hide();
                        if ((mAttrs.flags&WindowManager.LayoutParams.FLAG_DITHER) != 0) {
                            if (SHOW_TRANSACTIONS) logSurface(this, "DITHER", null);
                            mSurface.setFlags(Surface.SURFACE_DITHER,
                                    Surface.SURFACE_DITHER);
                        }
                    } catch (RuntimeException e) {
                        Slog.w(TAG, "Error creating surface in " + w, e);
                        reclaimSomeSurfaceMemoryLocked(this, "create-init");
                    }
                    mLastHidden = true;
                } finally {
                    if (SHOW_TRANSACTIONS) Slog.i(TAG, "<<< CLOSE TRANSACTION");
                    Surface.closeTransaction();//关闭事务
                }
                if (localLOGV) Slog.v(
                        TAG, "Created surface " + this);
            }
            return mSurface;
        }

jni层分析

Surface无参构造

frameworks/base/core/java/android/view/Surface.java
    /**
     * Create an empty surface, which will later be filled in by
     * readFromParcel().
     * {@hide}
     */
    public Surface() {
        if (DEBUG_RELEASE) {
            mCreationStack = new Exception();
        }
        //CompatibleCanvas继承自Canvas
        mCanvas = new CompatibleCanvas();
    }

canvas 根据文档可以知道,canvas相关的类有几个。

  • Bitmap:存储像素,就是画布
  • canvas:记载画图的工作,比如画圆形、方形
  • drawing primitive:在计算机图形学中绘制基本形状的过程。基本形状通常指的是点、线和三角形。在OpenGL或其他图形渲染API中,绘制基本形状是构建复杂图形的基础。
  • paint:描述绘画时候使用的颜色、风格(实线、虚线)

canvas一般会封装一块bitmap,作画就基于这块bitmap。

SurfaceSession

frameworks/base/core/java/android/view/SurfaceSession.java
/**
 * An instance of this class represents a connection to the surface
 * flinger, in which you can create one or more Surface instances that will
 * be composited to the screen.
 * {@hide}
 */
public class SurfaceSession {
    /** Create a new connection with the surface flinger. */
    public SurfaceSession() {
        init();//native函数
    }

看下这个init的实现:

SurfaceSession_init

frameworks/base/core/jni/android_view_Surface.cpp
static void SurfaceSession_init(JNIEnv* env, jobject clazz)
{
//创建了SurfaceComposerClient对象
    sp<SurfaceComposerClient> client = new SurfaceComposerClient;
    client->incStrong(clazz);
    //在java对象中保存它
    env->SetIntField(clazz, sso.client, (int)client.get());
}

surface的有参构造

frameworks/base/core/java/android/view/Surface.java
  /**
     * create a surface with a name
     * {@hide}
     */
    public Surface(SurfaceSession s,//传入SurfaceSession
            int pid, String name, int display, int w, int h, int format, int flags)
        throws OutOfResourcesException {
        if (DEBUG_RELEASE) {
            mCreationStack = new Exception();
        }
        mCanvas = new CompatibleCanvas();
        //native函数,传递了display,w h是宽高
        init(s,pid,name,display,w,h,format,flags);
        mName = name;
    }

看下 init(s,pid,name,display,w,h,format,flags);

frameworks/base/core/jni/android_view_Surface.cpp
static void Surface_init(
        JNIEnv* env, jobject clazz, 
        jobject session,
        jint pid, jstring jname, jint dpy, jint w, jint h, jint format, jint flags)
{
    if (session == NULL) {
        doThrow(env, "java/lang/NullPointerException");
        return;
    }
    //从 SurfaceSession对象中取出之前创建的之前创建的SurfaceComposerClient对象
    SurfaceComposerClient* client =
            (SurfaceComposerClient*)env->GetIntField(session, sso.client);

	//SurfaceControl
    sp<SurfaceControl> surface;
    if (jname == NULL) {
        surface = client->createSurface(pid, dpy, w, h, format, flags);
    } else {
        const jchar* str = env->GetStringCritical(jname, 0);
        const String8 name(str, env->GetStringLength(jname));
        env->ReleaseStringCritical(jname, str);
        //调用SurfaceComposerClient的createSurface,返回的是是SurfaceControl
        surface = client->createSurface(pid, name, dpy, w, h, format, flags);
    }

    if (surface == 0) {
        doThrow(env, OutOfResourcesException);
        return;
    }
    //把SurfaceControl设置到java层的surface对象中
    setSurfaceControl(env, clazz, surface);
}

Surface_copyFrom

static void Surface_copyFrom(
        JNIEnv* env, jobject clazz, jobject other)
{
    if (clazz == other)
        return;

    if (other == NULL) {
        doThrow(env, "java/lang/NullPointerException", NULL);
        return;
    }

    /*
     * This is used by the WindowManagerService just after constructing
     * a Surface and is necessary for returning the Surface reference to
     * the caller. At this point, we should only have a SurfaceControl.
     */
    
    const sp<SurfaceControl>& surface = getSurfaceControl(env, clazz);
    const sp<SurfaceControl>& rhs = getSurfaceControl(env, other);
    if (!SurfaceControl::isSameSurface(surface, rhs)) {
        // we reassign the surface only if it's a different one
        // otherwise we would loose our client-side state.
        setSurfaceControl(env, clazz, rhs);
    }
}

Surface_writeToParcel

static void Surface_writeToParcel(
        JNIEnv* env, jobject clazz, jobject argParcel, jint flags)
{
    Parcel* parcel = (Parcel*)env->GetIntField(
            argParcel, no.native_parcel);

    if (parcel == NULL) {
        doThrow(env, "java/lang/NullPointerException", NULL);
        return;
    }

    const sp<SurfaceControl>& control(getSurfaceControl(env, clazz));
    SurfaceControl::writeSurfaceToParcel(control, parcel);
    if (flags & PARCELABLE_WRITE_RETURN_VALUE) {
        setSurfaceControl(env, clazz, 0);
    }
}

Surface_readFromParcel

static void Surface_readFromParcel(
        JNIEnv* env, jobject clazz, jobject argParcel)
{
    Parcel* parcel = (Parcel*)env->GetIntField( argParcel, no.native_parcel);
    if (parcel == NULL) {
        doThrow(env, "java/lang/NullPointerException", NULL);
        return;
    }

    const sp<Surface>& control(getSurface(env, clazz));
    sp<Surface> rhs = new Surface(*parcel);
    if (!Surface::isSameSurface(control, rhs)) {
        // we reassign the surface only if it's a different one
        // otherwise we would loose our client-side state.
        setSurface(env, clazz, rhs);
    }
}

这里面WMS的surface调用了带参的SurfaceSession构造函数,ViewRoot的surface调用了无参的构造函数。
copyFrom就是把WMS的surface信息,拷贝到ViewRoot中的surface中。这其中使用了AIDL,xxx.aidl文件可以转换为java文件.

总结

我们来回顾和总结一路过来的分析,为后续破解surfaceflinger做准备。
创建了一个SurfaeComposerClient,调用它的createSurface,拿到一个SurfaceControl对象。调用SurfaceControl的writeToParcel把信息写道parcel中。根据parcel的信息构造一个surface对象,并保存到java层的mSurface对象中。
这样viewroot得到了一个native的surface对象。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/538714.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

无人机概述

1、中英文对照表 中文中文简称英文全称英文简称无人驾驶飞机无人机Unmanned Aerial VehicleUAV无人机自组织网络无人机网络flying Ad-Hoc networkFANET 2、相关概念 2.1鲁棒性 网络鲁棒性是指网络系统在面对随机故障、蓄意攻击或其他异常情况时&#xff0c;能够保持其基本功…

Linux的网口名字的命名规则

在工作中&#xff0c;偶尔看到有些机器的网口名字是以ethX命令&#xff0c;有些则以enpXsX这种名字命名。网上的资料说的都不太明白,资料也无据可查&#xff0c;很难让人信服。于是决定自己查了下官方的资料和源码&#xff0c;把这些搞清楚。 官方文档&#xff1a;Predictable…

MobX进阶:从基础到高级特性全面探索

MobX 提供了丰富的高级特性,包括计算属性、反应式视图、中间件、异步流程控制、依赖注入和动态 observable 、在服务端渲染和 TypeScript 支持方面提供了良好的集成。这些特性进一步增强了 MobX 在状态管理方面的灵活性和可扩展性,使其成为一个功能强大、易于使用的状态管理解决…

【NLP练习】调用Gensim库训练Word2Vec模型

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 一、准备工作 1.安装Gensim库 使用pip安装&#xff1a; !pip install gensim2. 对原始语料分词 选择《人民的民义》的小说原文作为语料&#xff0c;先采用…

在Windows 10中打开PowerShell的几种方法,总有一种适合你

PowerShell是一种比命令提示符更强大的命令行shell和脚本语言。自Windows10发布以来,它已成为默认选择,并且有许多方法可以打开它。 PowerShell和命令提示符之间的区别是什么 PowerShell的使用更复杂,但它比命令提示符强大得多。这就是为什么它成为超级用户和it专业人员的…

CTF之Flask_FileUpload

拿到题目就一个上传文件的网页 查看源码&#xff0c;发现注释告诉我们会运行python的文件&#xff0c;但是系统只能上传图片格式&#xff08;这个是自己尝试知道的&#xff09; 那我们就写一个python代码改成jpg或者png格式的文件 内容为 import os os.system(cat /flag) 上传…

算法 囚犯幸存者

题目 主类 public static List<Prisoner> prisoners new ArrayList<Prisoner>(); public static List<Prisoner> remainPrisoners new ArrayList<Prisoner>(); public static Prisoner lastPrisoner null;public static void main(String[] args) …

jeecg-boot安装

我看大家都挺关注&#xff0c;所以集中上传了下代码和相关工具&#xff0c;方便大家快速完成 链接&#xff1a;https://pan.baidu.com/s/1-Y9yHVZ-4DQFDjPBWUk4-A 提取码&#xff1a;op1r 1. 下载代码 下载地址 : JEECG官方网站 - 基于BPM的低代码开发平台(低代码平台_零代…

单链表的应用

上篇博客中&#xff0c;我们学习了单链表&#xff0c;为了更加熟练掌握这一知识点&#xff0c;就让我们将单链表的应用操练起来吧&#xff01; 203. 移除链表元素 - 力扣&#xff08;LeetCode&#xff09; 思路一&#xff1a;遍历原链表&#xff0c;将值为val的节点释放掉。 …

华为校园公开课走入上海交大,鸿蒙成为专业核心课程

4月12日&#xff0c;华为校园公开课在中国上海交通大学成功举办&#xff0c;吸引了来自计算机等相关专业的150余名学生参加。据了解&#xff0c;由吴帆、陈贵海、过敏意、吴晨涛、刘生钟等教授在中国上海交通大学面向计算机系本科生开设的《操作系统》课程&#xff0c;是该系学…

CS224N第二课作业--word2vec与skipgram

文章目录 CS224N: 作业2 word2vec (49 Points)1. Math: 理解 word2vec计算 J n a i v e − s o f t m a x ( v c , o , U ) J_{naive-softmax}(v_c, o, U) Jnaive−softmax​(vc​,o,U) 关于 v c v_c vc​ 的偏导数计算 J n a i v e − s o f t m a x ( v c , o , U ) J_{na…

【SpringBoot】mybatis-plus实现增删改查

mapper继承BaseMapper service 继承ServiceImpl 使用方法新增 save,updateById新增和修改方法返回boolean值,或者使用saveOrUpdate方法有id执行修改操作,没有id 执行新增操作 案例 Service public class UserService extends ServiceImpl<UserMapper,User> {// Au…

第四百五十六回

文章目录 1. 概念介绍2. 思路与方法2.1 实现思路2.2 使用方法 3. 内容总结 我们在上一章回中介绍了"overlay_tooltip用法"相关的内容&#xff0c;本章回中将介绍onBoarding包.闲话休提&#xff0c;让我们一起Talk Flutter吧。 1. 概念介绍 我们在本章回中介绍的onBo…

Python 以点生成均匀二维圆点数据

已知一个数&#xff0c;但这个数不是最对的&#xff0c;最对的可能在它附近&#xff0c;想要从附近随机生成一群数&#xff0c;放入模型中暴力查找最对的那个值。 以下是代码片段 n 800 # 个数 m 2 # 角度&#xff0c;只有2才是均匀的&#xff0c;1为半圆&#xff0c;以此类…

HashMap的常见问题

Entry中的hash属性为什么不直接使用key的hashCode()返回值呢&#xff1f; 不管是JDK1.7还是JDK1.8中&#xff0c;都不是直接用key的hashCode值直接与table.length-1计算求下标的&#xff0c;而是先对key的hashCode值进行了一个运算&#xff0c;JDK1.7和JDK1.8关于hash()的实现…

1. Django建站基础

1. Django建站基础 学习开发网站必须了解网站的组成部分, 网站类型, 运行原理和开发流程. 使用Django开发网站必须掌握Django的基本操作, 比如创建项目, 使用Django的操作指令以及开发过程中的调试方法.1.1 网站的定义及组成 网站(Website)是指在因特网上根据一定的规则, 使用…

C++高级特性:柯里化过程与std::bind(六)

1、柯里化过程 1.1、operator()的引入 现在需要完成这样一个需求&#xff1a;有一个函数每次调用返回的结果不一样。例如&#xff1a;两次调用的返回值都不一样那么就可以达到这种目的 1.1.1、简单点的写法 可以给一个全局的变量&#xff08;静态变量&#xff09;&#xff…

竞赛课第六周(树状数组的应用)

实验内容: HDU 1166 敌兵布阵【线段树】 线段树的应用 敌兵布阵 C国的死对头A国这段时间正在进行军事演习&#xff0c;所以C国间谍头子Derek和他手下Tidy又开始忙乎了。A国在海岸线沿直线布置了N个工兵营地,Derek和Tidy的任务就是要监视这些工兵营地的活动情况。由于采取…

批量发送朋友圈还能自动同步朋友圈

现在不管是做服装店、水果店、化妆店、影楼、二手车房等各行各业来说&#xff0c;每天必不可少的就是发各种朋友圈&#xff0c;量大号又多还占手机内存&#xff0c;真!的!很!累! 没有人能拒绝一键转发带来的方便&#xff0c;有了它发朋友圈只需要点一下就复制到了你自己的朋友圈…

tkinter窗口

简单的窗口程序 导入所需的库 from tkinter import * import json 创建一个主窗口 app Tk() 设置窗口大小为 1048x2048 app.geometry(“1048x2048”) 设置窗口背景为灰色 app.configure(bg“gray”) 创建一个 Label 对象&#xff0c;显示 “账号&#xff1a;” 和红色…