如何应对Android面试官 -> Window 显示次序,尺寸计算

Android---WMS核心分析 比如:告诉 WMS 某一个类型 Window 的 Z-Order的值是多少,帮助 WMS 矫正不合理的窗口属性,为 WMS 监听屏幕旋转的状态,预处理一些系统按键事件(例如HOMW、BACK 键等的默认行为就是在这里实现的)等。 阅读详情

前言


image.png

本章我们继续上一节讲解 WMS 相关知识点,Window 是如何显示以及尺寸计算;

View 的添加过程


View 的添加过程是有两个地方,一个是在 onResume 的时候,一个是在

onResume

Activity 在创建之后,执行 onResume 的时候,把 View 添加到 WMS,然后由 WMS 交给 SurfaceFinger 进行绘制;

startActivity

在 startActivity 中的 startingWindow 的时候 也会有一个添加窗口的过程;

image.png

在 Window 的显示次序、窗口尺寸的计算,以及与 AMS 的通信等逻辑中有两个重要的概念,一个是 WindowContainerController 一个是 WindowContainer;我们来看下这两个重要的概念;

WindowContainer


/**
 * Defines common functionality for classes that can hold windows directly or through their
 * children in a hierarchy form.
 * The test class is {@link WindowContainerTests} which must be kept up-to-date and ran anytime
 * changes are made to this class.
 */
class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<E>
        implements Comparable<WindowContainer>, Animatable, SurfaceFreezer.Freezable {
} 

可以看下它的定义:主要是用来实现 window 显示次序;它继承了 ConfigurationContainer 这个类,这个类主要是手机的一些设置,它也实现了 Comparable 接口,用来进行排序的,也就是控制显示次序;我们来看下它的 compareTo 方法的具体实现;

public int compareTo(WindowContainer other) {
    if (this == other) {
        return 0;
    }

    if (mParent != null && mParent == other.mParent) {
        final WindowList<WindowContainer> list = mParent.mChildren;
        return list.indexOf(this) > list.indexOf(other) ? 1 : -1;
    }

    final LinkedList<WindowContainer> thisParentChain = mTmpChain1;
    final LinkedList<WindowContainer> otherParentChain = mTmpChain2;
    try {
        getParents(thisParentChain);
        other.getParents(otherParentChain);

        // Find the common ancestor of both containers.
        WindowContainer commonAncestor = null;
        WindowContainer thisTop = thisParentChain.peekLast();
        WindowContainer otherTop = otherParentChain.peekLast();
        while (thisTop != null && otherTop != null && thisTop == otherTop) {
            commonAncestor = thisParentChain.removeLast();
            otherParentChain.removeLast();
            thisTop = thisParentChain.peekLast();
            otherTop = otherParentChain.peekLast();
        }

        // Containers don't belong to the same hierarchy???
        if (commonAncestor == null) {
            throw new IllegalArgumentException("No in the same hierarchy this="
                    + thisParentChain + " other=" + otherParentChain);
        }

        // Children are always considered greater than their parents, so if one of the containers
        // we are comparing it the parent of the other then whichever is the child is greater.
        if (commonAncestor == this) {
            return -1;
        } else if (commonAncestor == other) {
            return 1;
        }

        // The position of the first non-common ancestor in the common ancestor list determines
        // which is greater the which.
        final WindowList<WindowContainer> list = commonAncestor.mChildren;
        return list.indexOf(thisParentChain.peekLast()) > list.indexOf(otherParentChain.peekLast())
                ? 1 : -1;
    } finally {
        mTmpChain1.clear();
        mTmpChain2.clear();
    }
}

当往 WindowContainer 放的时候,就会通过这个方法进行比较,来确定当前这个 window 要插入到什么位置;我们接着来看下 add 方法;

protected void addChild(E child, Comparator<E> comparator) {
    if (!child.mReparenting && child.getParent() != null) {
        throw new IllegalArgumentException("addChild: container=" + child.getName()
                + " is already a child of container=" + child.getParent().getName()
                + " can't add to container=" + getName());
    }

    int positionToAdd = -1;
    if (comparator != null) {
        final int count = mChildren.size();
        for (int i = 0; i < count; i++) {
            // 在这里进行排序的逻辑
            if (comparator.compare(child, mChildren.get(i)) < 0) {
                positionToAdd = i;
                break;
            }
        }
    }

    if (positionToAdd == -1) {
        mChildren.add(child);
    } else {
        mChildren.add(positionToAdd, child);
    }

    // Set the parent after we've actually added a child in case a subclass depends on this.
    child.setParent(this);
}

通过 comparator.compare(child, mChildren.get(i)) < 0 进行比较排序;

image.png

上图是 WindowContainer 的一些衍生类;

而 WindowContainer 又继承自 ConfigurationContainer,整体的一个关系图如下:

infoflow 2024-05-20 12-08-30.png

WindowContainerController


说完 WindowContainer 我们来说一下和它长的比较像的 WindowContainerController 先来上一张继承关系图

infoflow 2024-05-20 12-11-46.png

WindowContainer 和 WindowContainerController 之间的关系

infoflow 2024-05-20 12-13-08.png

系统把 Activity 等都抽象成了 WindowContainer 那么这些 container 需要一些显示逻辑,那么系统就把它们提取出一个 WindowContainerController 在这里面控制它;

就像上图的 Task 对应一个 TaskWindowContainerController,TaskStack 对应一个 StackWindowController;

整体的一个关系图就如下图:

infoflow 2024-05-20 12-20-51.png

通过上图可以看出 DisplayContent -> DisplayWindonwController -> ActivityDisplay -> ActivityRecord 等的这样的一个流程,那么我来看下 ActivityRecord 是怎么创建的;我们先来看下 ActivityDisplay

ActivityDisplay(ActivityStackSupervisor supervisor, Display display) {
    mSupervisor = supervisor;
    mDisplayId = display.getDisplayId();
    mDisplay = display;
    mWindowContainerController = createWindowContainerController();
    updateBounds();
}

这里调用了 createWindowContainerController 它在这里创建了 DisplayWindowController

protected DisplayWindowController createWindowContainerController() {
    return new DisplayWindowController(mDisplay, this);
}

为什么要创建这个 DisplayWindowController 呢?因为它管理的是我们的显示屏,我们进入这个 DisplayWindowController 看下:

public DisplayWindowController(Display display, WindowContainerListener listener) {
        super(listener, WindowManagerService.getInstance());
        mDisplayId = display.getDisplayId();

        synchronized (mWindowMap) {
            final long callingIdentity = Binder.clearCallingIdentity();
            try {
                mRoot.createDisplayContent(display, this /* controller */);
            } finally {
                Binder.restoreCallingIdentity(callingIdentity);
            }

            if (mContainer == null) {
                throw new IllegalArgumentException("Trying to add display=" + display
                        + " dc=" + mRoot.getDisplayContent(mDisplayId));
            }
        }
    }

这里调用了 mRoot.createDisplayContent 我们来看下这个 mRoot 是什么?

final RootWindowContainer mRoot;

mRoot 就是 RootWindowContainer,我们进入这个看下,以及看下它的 createDisplayContent

DisplayContent createDisplayContent(final Display display, DisplayWindowController controller) {
    // 获取屏幕
    final DisplayContent existing = getDisplayContent(displayId);
    if (existing != null) {
        existing.setController(controller);
        return existing;
   }
   // 获取不到对应的屏幕,则创建对应的屏幕
   final DisplayContent dc = new DisplayContent(display, mService, mWallpaperController, controller);
}

我们进入这个 DisplayContent 看下:

class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowContainer> {
    private static final String TAG = TAG_WITH_CLASS_NAME ? "DisplayContent" : TAG_WM;

    private final int mDisplayId;

    private final TaskStackContainers mTaskStackContainers = new TaskStackContainers(mService);// 这里面一般是指应用的Activity。实际上也就是我们常说的Activity对应着整个系统栈的管理者
 
    private final AboveAppWindowContainers mAboveAppWindowsContainers =//这里是指一切在Activity之上的窗体容器,比如说StatusBar状态栏
            new AboveAppWindowContainers("mAboveAppWindowsContainers", mService);

    private final NonAppWindowContainers mBelowAppWindowsContainers =//一切的应该在Activity之下的Window容器,比如wrapper壁纸
            new NonAppWindowContainers("mBelowAppWindowsContainers", mService);

    private final NonMagnifiableWindowContainers mImeWindowsContainers =//这里是指如Dialog,输入键盘的窗体容器
            new NonMagnifiableWindowContainers("mImeWindowsContainers", mService);


}

屏幕的创建就是在这里完成的;

同理 ActivityStack 也是这样的一个创建逻辑,它也是一个 Container,也是继承自 ConfigurationContainer;

同理 TaskRecord 也是这样的一个创建逻辑;

ActivityStarter


Activity 在启动过程中,执行到 startActivityUnChecked 方法中的 mTargetStack.startActivityLocked 的时候

void startActivityLocked(ActivityRecord r, ActivityRecord focusedTopActivity,
            boolean newTask, boolean keepCurTransition, ActivityOptions options) {
            
    if (!r.mLaunchTaskBehind && (taskForIdLocked(taskId) == null || newTask)) {
        insertTaskAtTop(rTask, r); // 1 将 task 添加到 mTaskHistory 中
    }
    
    if (r.getWindowContainerController() == null) { // 2 创建 windowContainerController
        r.createWindowContainer();
    }
}

这创建 mWindowContainerController

void createWindowContainer() {
        if (mWindowContainerController != null) {
            throw new IllegalArgumentException("Window container=" + mWindowContainerController
                    + " already created for r=" + this);
        }

        inHistory = true;

        final TaskWindowContainerController taskController = task.getWindowContainerController();

        // TODO(b/36505427): Maybe this call should be moved inside updateOverrideConfiguration()
        task.updateOverrideConfigurationFromLaunchBounds();
        // Make sure override configuration is up-to-date before using to create window controller.
        updateOverrideConfiguration();
        // 创建一个AppWindowToken,并且添加到 DisplayContent 的 mTokenMap 中
        mWindowContainerController = new AppWindowContainerController(taskController, appToken,
                this, Integer.MAX_VALUE /* add on top */, info.screenOrientation, fullscreen,
                (info.flags & FLAG_SHOW_FOR_ALL_USERS) != 0, info.configChanges,
                task.voiceSession != null, mLaunchTaskBehind, isAlwaysFocusable(),
                appInfo.targetSdkVersion, mRotationAnimationHint,
                ActivityManagerService.getInputDispatchingTimeoutLocked(this) * 1000000L);

        task.addActivityToTop(this);

        // When an activity is started directly into a split-screen fullscreen stack, we need to
        // update the initial multi-window modes so that the callbacks are scheduled correctly when
        // the user leaves that mode.
        mLastReportedMultiWindowMode = inMultiWindowMode();
        mLastReportedPictureInPictureMode = inPinnedWindowingMode();
    }

在构造方法中创建了一个 AppWindowToken

public AppWindowContainerController(TaskWindowContainerController taskController,
            IApplicationToken token, AppWindowContainerListener listener, int index,
            int requestedOrientation, boolean fullscreen, boolean showForAllUsers, int configChanges,
            boolean voiceInteraction, boolean launchTaskBehind, boolean alwaysFocusable,
            int targetSdkVersion, int rotationAnimationHint, long inputDispatchingTimeoutNanos,
            WindowManagerService service) {
        super(listener, service);
        mHandler = new H(service.mH.getLooper());
        mToken = token;
        synchronized(mWindowMap) {
            AppWindowToken atoken = mRoot.getAppWindowToken(mToken.asBinder());
            if (atoken != null) {
                // TODO: Should this throw an exception instead?
                Slog.w(TAG_WM, "Attempted to add existing app token: " + mToken);
                return;
            }

            final Task task = taskController.mContainer;
            if (task == null) {
                throw new IllegalArgumentException("AppWindowContainerController: invalid "
                        + " controller=" + taskController);
            }
            //创建了一个 AppWindowToken
            atoken = createAppWindow(mService, token, voiceInteraction, task.getDisplayContent(),
                    inputDispatchingTimeoutNanos, fullscreen, showForAllUsers, targetSdkVersion,
                    requestedOrientation, rotationAnimationHint, configChanges, launchTaskBehind,
                    alwaysFocusable, this);
            if (DEBUG_TOKEN_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(TAG_WM, "addAppToken: " + atoken
                    + " controller=" + taskController + " at " + index);
            task.addChild(atoken, index);
        }
    }

创建完成之后,最终执行到 showStartingWindow

void showStartingWindow(ActivityRecord prev, boolean newTask, boolean taskSwitch,
            boolean fromRecents) {
        if (mWindowContainerController == null) {
            return;
        }
        if (mTaskOverlay) {
            // We don't show starting window for overlay activities.
            return;
        }

        final CompatibilityInfo compatInfo =
                service.compatibilityInfoForPackageLocked(info.applicationInfo);
        // 通过 addStartingWindow 启动        
        final boolean shown = mWindowContainerController.addStartingWindow(packageName, theme,
                compatInfo, nonLocalizedLabel, labelRes, icon, logo, windowFlags,
                prev != null ? prev.appToken : null, newTask, taskSwitch, isProcessRunning(),
                allowTaskSnapshot(),
                mState.ordinal() >= RESUMED.ordinal() && mState.ordinal() <= STOPPED.ordinal(),
                fromRecents);
        if (shown) {
            mStartingWindowState = STARTING_WINDOW_SHOWN;
        }
    }

我们进入 AppWindowContainerController 的 addStartingWindow 看下,这里的启动分为两种,一种是之前启动过在 wms 中存储了快照,则直接使用快照显示,如果没有启动过,使用配置信息启动;

public boolean addStartingWindow(String pkg, int theme, CompatibilityInfo compatInfo,
            CharSequence nonLocalizedLabel, int labelRes, int icon, int logo, int windowFlags,
            IBinder transferFrom, boolean newTask, boolean taskSwitch, boolean processRunning,
            boolean allowTaskSnapshot, boolean activityCreated, boolean fromRecents) {
        synchronized(mWindowMap) {
            if (DEBUG_STARTING_WINDOW) Slog.v(TAG_WM, "setAppStartingWindow: token=" + mToken
                    + " pkg=" + pkg + " transferFrom=" + transferFrom + " newTask=" + newTask
                    + " taskSwitch=" + taskSwitch + " processRunning=" + processRunning
                    + " allowTaskSnapshot=" + allowTaskSnapshot);

            if (mContainer == null) {
                Slog.w(TAG_WM, "Attempted to set icon of non-existing app token: " + mToken);
                return false;
            }

            // If the display is frozen, we won't do anything until the actual window is
            // displayed so there is no reason to put in the starting window.
            if (!mContainer.okToDisplay()) {
                return false;
            }

            if (mContainer.startingData != null) {
                return false;
            }

            final WindowState mainWin = mContainer.findMainWindow();
            if (mainWin != null && mainWin.mWinAnimator.getShown()) {
                // App already has a visible window...why would you want a starting window?
                return false;
            }
		 //使用快照
            final TaskSnapshot snapshot = mService.mTaskSnapshotController.getSnapshot(
                    mContainer.getTask().mTaskId, mContainer.getTask().mUserId,
                    false /* restoreFromDisk */, false /* reducedResolution */);
            final int type = getStartingWindowType(newTask, taskSwitch, processRunning,
                    allowTaskSnapshot, activityCreated, fromRecents, snapshot);

            if (type == STARTING_WINDOW_TYPE_SNAPSHOT) {
                return createSnapshot(snapshot);
            }

            // If this is a translucent window, then don't show a starting window -- the current
            // effect (a full-screen opaque starting window that fades away to the real contents
            // when it is ready) does not work for this.
            if (DEBUG_STARTING_WINDOW) Slog.v(TAG_WM, "Checking theme of starting window: 0x"
                    + Integer.toHexString(theme));
            if (theme != 0) {
                //使用闪屏页面
                AttributeCache.Entry ent = AttributeCache.instance().get(pkg, theme,
                        com.android.internal.R.styleable.Window, mService.mCurrentUserId);
                if (ent == null) {
                    // Whoops!  App doesn't exist. Um. Okay. We'll just pretend like we didn't
                    // see that.
                    return false;
                }
                final boolean windowIsTranslucent = ent.array.getBoolean(
                        com.android.internal.R.styleable.Window_windowIsTranslucent, false);
                final boolean windowIsFloating = ent.array.getBoolean(
                        com.android.internal.R.styleable.Window_windowIsFloating, false);
                final boolean windowShowWallpaper = ent.array.getBoolean(
                        com.android.internal.R.styleable.Window_windowShowWallpaper, false);
                final boolean windowDisableStarting = ent.array.getBoolean(
                        com.android.internal.R.styleable.Window_windowDisablePreview, false);
                if (DEBUG_STARTING_WINDOW) Slog.v(TAG_WM, "Translucent=" + windowIsTranslucent
                        + " Floating=" + windowIsFloating
                        + " ShowWallpaper=" + windowShowWallpaper);
                if (windowIsTranslucent) {
                    return false;
                }
                if (windowIsFloating || windowDisableStarting) {
                    return false;
                }
                if (windowShowWallpaper) {
                    if (mContainer.getDisplayContent().mWallpaperController.getWallpaperTarget()
                            == null) {
                        // If this theme is requesting a wallpaper, and the wallpaper
                        // is not currently visible, then this effectively serves as
                        // an opaque window and our starting window transition animation
                        // can still work.  We just need to make sure the starting window
                        // is also showing the wallpaper.
                        windowFlags |= FLAG_SHOW_WALLPAPER;
                    } else {
                        return false;
                    }
                }
            }

            if (mContainer.transferStartingWindow(transferFrom)) {
                return true;
            }

            // There is no existing starting window, and we don't want to create a splash screen, so
            // that's it!
            if (type != STARTING_WINDOW_TYPE_SPLASH_SCREEN) {
                return false;
            }

            if (DEBUG_STARTING_WINDOW) Slog.v(TAG_WM, "Creating SplashScreenStartingData");
            mContainer.startingData = new SplashScreenStartingData(mService, pkg, theme,
                    compatInfo, nonLocalizedLabel, labelRes, icon, logo, windowFlags,
                    mContainer.getMergedOverrideConfiguration());
            scheduleAddStartingWindow();
        }
        return true;
    }

然后创建 SplashScreenStartingData 调用 scheduleAddStartingWindow 进行添加,这个SplashScreenStartingData 有一个比较重要的知识点

StartingSurface createStartingSurface(AppWindowToken atoken) {
        return mService.mPolicy.addSplashScreen(atoken.token, mPkg, mTheme, mCompatInfo,
                mNonLocalizedLabel, mLabelRes, mIcon, mLogo, mWindowFlags,
                mMergedOverrideConfiguration, atoken.getDisplayContent().getDisplayId());
    }

这里创建了一个 surface,这个 surface 就是需要显示的那块屏幕的画布;mPolicy 这个就是 PhoneWindow;

public StartingSurface addSplashScreen(IBinder appToken, int userId, String packageName,
        int theme, CompatibilityInfo compatInfo, CharSequence nonLocalizedLabel, int labelRes,
        int icon, int logo, int windowFlags, Configuration overrideConfig, int displayId) {
    // 创建 PhoneWindow,并设置一系列参数
    final PhoneWindow win = new PhoneWindow(context);
    // 设置参数代码 省略
    
    // 添加内容
    addSplashscreenContent(win, context);
    
    // 获取 decorView
    wm = (WindowManager) context.getSystemService(WINDOW_SERVICE);
    view = win.getDecorView();
    
    // 最终也是通过 wmd 的 addView 流程
    wm.addView(view, params);
}

通过 addSplashscreenContent 添加内容

private void addSplashscreenContent(PhoneWindow win, Context ctx) {
    final TypedArray a = ctx.obtainStyledAttributes(R.styleable.Window);
    final int resId = a.getResourceId(R.styleable.Window_windowSplashscreenContent, 0);
    a.recycle();
    if (resId == 0) {
        return;
    }
    // 从系统资源获取 drawable
    final Drawable drawable = ctx.getDrawable(resId);
    if (drawable == null) {
        return;
    }

    // new 一个 View 并把它通过 setContentView 设置给 PhoneWindow
    final View v = new View(ctx);
    v.setBackground(drawable);
    win.setContentView(v);
}

ActivityThread -> handleResumeActivity


执行到这里的时候,准备显示 Activity 的内容了,我们来看下怎么显示内容;

public void handleResumeActivity(ActivityClientRecord r, boolean finalStateRequest,
        boolean isForward, String reason) {
    ```
    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;
    }
    
    // 然后执行 makeVisible
    if (r.activity.mVisibleFromClient) {
        r.activity.makeVisible();
    }
    
}

makeVisible 方法看下:

void makeVisible() {
    if (!mWindowAdded) {
        ViewManager wm = getWindowManager();
        wm.addView(mDecor, getWindow().getAttributes());
        mWindowAdded = true;
    }
    mDecor.setVisibility(View.VISIBLE);
}

这里用的是 ViewManager 来接收,它是 WindowManager 的父类,是如下的一个继承关系;

image.png

主要做的三件事情就是:addView、updateViewLayout、removeView,通过这里可以了解到我们上层业务的 addView 其实都是添加到了 DecorView,而添加到 WMS 的是 DecorView,只有 DecorView 与 WMS 进行一个对接操作;

我们来看下 WindowManager 的 addView 的逻辑,最终调度到的是 WindowManagerGlobal;

public void addView(View view, ViewGroup.LayoutParams params,
        Display display, Window parentWindow, int userId) {
        
    // 创建 ViewRootImpl
    root = new ViewRootImpl(view.getContext(), display);

    view.setLayoutParams(wparams);

    mViews.add(view);
    mRoots.add(root);
    mParams.add(wparams);

    // do this last because it fires off messages to start doing things
    try {
        root.setView(view, wparams, panelParentView, userId);
    } catch (RuntimeException e) {
        // BadTokenException or InvalidDisplayException, clean up.
        if (index >= 0) {
            removeViewLocked(index, true);
        }
        throw e;
    }
}

setView 方法进入看下:

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
        int userId) {
    
    synchronized (this) {
        // 将 ViewRootImpl 与 View 的关联只能做一次,如果赋值过就不再执行
        if (mView == null) {
            mView = view; // 将顶层视图 DecorView 赋值给全局的 mView
            
            // 省略部分参数获取的逻辑
            
            // 与 wms 沟通
            res = mWindowSession.addToDisplayAsUser(mWindow, mWindowAttributes,
                    getHostVisibility(), mDisplay.getDisplayId(), userId,
                    mInsetsController.getRequestedVisibility(), inputChannel, mTempInsets, mTempControls);
        }
    }
}

这里通过 mWindowSession.addToDisplayAsUser 与 wms 进行沟通绑定,最终调用到 Session 的 addToDisplay

public int addToDisplay(IWindow window, WindowManager.LayoutParams attrs,
        int viewVisibility, int displayId, InsetsState requestedVisibility,
        InputChannel outInputChannel, InsetsState outInsetsState,
        InsetsSourceControl[] outActiveControls) {
    return mService.addWindow(this, window, attrs, viewVisibility, displayId,
            UserHandle.getUserId(mUid), requestedVisibility, outInputChannel, outInsetsState,
            outActiveControls);
}

通过 WindowManagerService 的 addWindow 方法进行添加;

public int addWindow(Session session, IWindow client, LayoutParams attrs, int viewVisibility,
        int displayId, int requestUserId, InsetsState requestedVisibility,
        InputChannel outInputChannel, InsetsState outInsetsState,
        InsetsSourceControl[] outActiveControls) {
    // 创建 wms ams 通信用的 token
    token = new WindowToken.Builder(this, binder, type)
        .setDisplayContent(displayContent)
        .setOwnerCanManageAppTokens(session.mCanAddInternalSystemWindow)
        .setRoundedCornerOverlay(isRoundedCornerOverlay)
        .setFromClientToken(true)
        .setOptions(options)
        .build();
        
    // 新的 windowState 对象在其构造函数中根据窗口类型初始化了其主序mBaseLayer和mSubLayer
    WindowState win = new WindowState(this, session, client, token, parentWindow,
        appOp[0], attrs, viewVisibility, session.mUid, userId,
        session.mCanAddInternalSystemWindow);
    
    // 执行 attach
    win.attach();
}

我们来看下 attach

void attach() {
    if (DEBUG) Slog.v(TAG, "Attaching " + this + " token=" + mToken);
    // wms 并没有真正的去管理绘制,真正的绘制
    mSession.windowAddedLocked();
}

真正的绘制逻辑在 windowAddedLocked 中

void windowAddedLocked() {
    if (mPackageName == null) {
        final WindowProcessController wpc = mService.mAtmService.mProcessMap.getProcess(mPid);
        if (wpc != null) {
            mPackageName = wpc.mInfo.packageName;
            mRelayoutTag = "relayoutWindow: " + mPackageName;
        } else {
            Slog.e(TAG_WM, "Unknown process pid=" + mPid);
        }
    }
    if (mSurfaceSession == null) {
        if (DEBUG) {
            Slog.v(TAG_WM, "First window added to " + this + ", creating SurfaceSession");
        }
        mSurfaceSession = new SurfaceSession();
        ProtoLog.i(WM_SHOW_TRANSACTIONS, "  NEW SURFACE SESSION %s", mSurfaceSession);
        mService.mSessions.add(this);
        if (mLastReportedAnimatorScale != mService.getCurrentAnimatorScale()) {
            mService.dispatchNewAnimatorScaleLocked(this);
        }
    }
    mNumWindow++;
}

当我们要绘制的时候,会创建一个 SurfaceSession 对象,执行一系列的 native 方法来进行绘制;

到这里 window 就绘制上去了,但是显示层次是怎么控制的呢?我们接着回去看下 WindowState 的构造函数;

WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token,
        WindowState parentWindow, int appOp, WindowManager.LayoutParams a, int viewVisibility,
        int ownerId, int showUserId, boolean ownerCanAddInternalSystemWindow,
        PowerManagerWrapper powerManagerWrapper) {
    // 如果是子窗口,使用它依附的窗口类型进行计算
    if (mAttrs.type >= FIRST_SUB_WINDOW && mAttrs.type <= LAST_SUB_WINDOW) {
        // The multiplier here is to reserve space for multiple
        // windows in the same type layer.
        // 计算 mBaseLayer
        mBaseLayer = mPolicy.getWindowLayerLw(parentWindow)
                * TYPE_LAYER_MULTIPLIER + TYPE_LAYER_OFFSET;
        // 计算 mSubLayer
        mSubLayer = mPolicy.getSubWindowLayerFromTypeLw(a.type);
     } else {
         // 非子窗口
         // 
        mBaseLayer = mPolicy.getWindowLayerLw(this)
                * TYPE_LAYER_MULTIPLIER + TYPE_LAYER_OFFSET;
        // 仅在子窗口中有效        
        mSubLayer = 0;
     }
}

在构造方法中会计算两个 layer,一个是 mBaseLayer 一个是 mSubLayer,通过 getWindowLayerLw 计算 Z 轴顺序,这个方法最终调用到 WindowManagerPolicy 的 getWindowLayerFromTypeLw 方法进行计算

default int getWindowLayerFromTypeLw(int type, boolean canAddInternalSystemWindow,
        boolean roundedCornerOverlay) {
    // 根据 type 返回 层级值
    switch (type) {
        case TYPE_WALLPAPER:
            // wallpaper is at the bottom, though the window manager may move it.
            return  1;
        case TYPE_PRESENTATION:
        case TYPE_PRIVATE_PRESENTATION:
        case TYPE_DOCK_DIVIDER:
        case TYPE_QS_DIALOG:
        case TYPE_PHONE:
            return  3;
        case TYPE_SEARCH_BAR:
        case TYPE_VOICE_INTERACTION_STARTING:
            return  4;
        case TYPE_VOICE_INTERACTION:
            // voice interaction layer is almost immediately above apps.
            return  5;
        case TYPE_INPUT_CONSUMER:
            return  6;
        case TYPE_SYSTEM_DIALOG:
            return  7;
        case TYPE_TOAST:
            // toasts and the plugged-in battery thing
            return  8;
        case TYPE_PRIORITY_PHONE:
            // SIM errors and unlock.  Not sure if this really should be in a high layer.
            return  9;
        case TYPE_SYSTEM_ALERT:
            // like the ANR / app crashed dialogs
            // Type is deprecated for non-system apps. For system apps, this type should be
            // in a higher layer than TYPE_APPLICATION_OVERLAY.
            return  canAddInternalSystemWindow ? 13 : 10;
        case TYPE_APPLICATION_OVERLAY:
            return  12;
        case TYPE_INPUT_METHOD:
            // on-screen keyboards and other such input method user interfaces go here.
            return  15;
        case TYPE_INPUT_METHOD_DIALOG:
            // on-screen keyboards and other such input method user interfaces go here.
            return  16;
        case TYPE_STATUS_BAR:
            return  17;
        case TYPE_STATUS_BAR_ADDITIONAL:
            return  18;
        case TYPE_NOTIFICATION_SHADE:
            return  19;
        case TYPE_STATUS_BAR_SUB_PANEL:
            return  20;
        case TYPE_KEYGUARD_DIALOG:
            return  21;
        case TYPE_VOLUME_OVERLAY:
            // the on-screen volume indicator and controller shown when the user
            // changes the device volume
            return  22;
        case TYPE_SYSTEM_OVERLAY:
            // the on-screen volume indicator and controller shown when the user
            // changes the device volume
            return  canAddInternalSystemWindow ? 23 : 11;
        case TYPE_NAVIGATION_BAR:
            // the navigation bar, if available, shows atop most things
            return  24;
        case TYPE_NAVIGATION_BAR_PANEL:
            // some panels (e.g. search) need to show on top of the navigation bar
            return  25;
        case TYPE_SCREENSHOT:
            // screenshot selection layer shouldn't go above system error, but it should cover
            // navigation bars at the very least.
            return  26;
        case TYPE_SYSTEM_ERROR:
            // system-level error dialogs
            return  canAddInternalSystemWindow ? 27 : 10;
        case TYPE_MAGNIFICATION_OVERLAY:
            // used to highlight the magnified portion of a display
            return  28;
        case TYPE_DISPLAY_OVERLAY:
            // used to simulate secondary display devices
            return  29;
        case TYPE_DRAG:
            // the drag layer: input for drag-and-drop is associated with this window,
            // which sits above all other focusable windows
            return  30;
        case TYPE_ACCESSIBILITY_OVERLAY:
            // overlay put by accessibility services to intercept user interaction
            return  31;
        case TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY:
            return 32;
        case TYPE_SECURE_SYSTEM_OVERLAY:
            return  33;
        case TYPE_BOOT_PROGRESS:
            return  34;
        case TYPE_POINTER:
            // the (mouse) pointer layer
            return  35;
        default:
            Slog.e("WindowManager", "Unknown window type: " + type);
            return 3;
    }
}

到这里 window 的添加 显示次序,尺寸计算就讲解结束了;

下一章预告


聊一聊 Fragment;

欢迎三连


来都来了,点个关注点个赞吧,你的支持是我最大的动力~

数字IC面试核心:从MUX基础到Glitch-Free时钟切换电路深度剖析 本文深入剖析数字IC设计中的MUX基础原理及其在Glitch-Free时钟切换电路中的应用。从二选一MUX的门级实现到时钟切换中的毛刺问题,详细讲解了同步切换机制和工程优化技巧,帮助工程师掌握高速时钟电路设计的核心要点。 阅读详情

相关推荐

(十)安卓开发中的Activity之间的通信使用详解

下面将结合代码和具体在 Android 开发中,Activity 之间的通信是非常常见且核心的功能之一使用场景,详细讲解这几种方式。

cuijiying的博客 1280

Android:This usually happens when two views of different type have the same id in the same hierarchy

这个崩溃于旋转屏幕的时候,从纵向布局切换到横向布局的时候发生: java.lang.IllegalArgumentException: Wrong state class, expecting View State but received class android.os.Bundle instead. This usually happens when two views of different type have the same id in the same hierarchy. This .

Crystal_xing的专栏 1581

EtherCAT-1 技术概述

1.EtherCAT简介①开放的技术EtherCAT全称EtherNet Control Automation Technology,是由德国倍福(Beckhoff)公司提出的一种实时以太网技术。EtherCAT是一种开放但不开源的技术,意味着您可以任意使用这项技术,但若要进行相关设备的开发,则需要向倍福公司获取相关授权。②快速性相比传统现场总线,EtherCAT的数据传输速率有了极大的提升,可选10Mbit/s或100Mbit/s,甚至依托补充

tianya_xuan的博客 3543

Android T startingwindow流程梳理

startingwindow的一些流程梳理

yimelancholy的博客 7189

unity打包流程整理-Windows/Mac/Linux平台

1.Platform(平台)功能: 选择目标构建的目标平台选项PC, Mac & Linux Standalone: 适用于 Windows、Mac 和 Linux 平台2.Target Platform(目标平台)功能: 选择具体的操作系统版本选项Windows: 针对 Windows 操作系统Mac: 针对 macOS 操作系统Linux: 针对 Linux 操作系统3.Architecture(架构)功能: 选择应用程序的目标处理器架构选项x86: 针对 32 位系统。

weixin_42333247的博客 5135

数据资产目录(SailCatalog)和数据地图(SailMap)

笔者接下来准备开发大数据平台(XSailboat)中的数据资产目录(SailCatalog)和数据地图(SailMap)相关的功能,本文用以记录笔者关于此部分功能的概念梳理和主体功能设计。

OkGogooXSailboat的博客 2326

StartingWindow显示流程 - 安卓R

1

SSSxCCC的博客 1911

Unidbg逆向阿里系App签名:从环境补全到算法还原实战

在移动安全与协议分析领域,理解应用层签名机制是核心挑战之一。签名算法作为保障通信安全与身份验证的关键技术,其原理通常涉及哈希、加密及密钥协商等基础密码学概念。在Android平台,核心签名逻辑常被加固并隐藏于Native层SO库中,以对抗动态调试与静态分析,这为安全研究和协议兼容带来了技术障碍。Unidbg作为一种基于模拟执行的逆向工程工具,通过模拟ARM指令集和系统调用,允许研究者在脱离真机环境的情况下,可控地执行和调试SO文件,为解决此类问题提供了创新思路。其技术价值在于实现了对加固代码的确定性分析与算

weixin_42525738的博客 937

Android View添加到Window的过程

Android 界面显示的过程可以分为两个步骤 1.是将我们要显示的布局添加到window上 2.在进行测量、布局、绘制 通过这两步我们想看到的View就显示在Window上了今天说下View是怎么添加到Window上的首先要从Activity的setContentView开始public void setContentView(View view) { getWindow().set

hjiangshujing的博客 3834

三相电机控制入门:端电压、相电压、线电压到底怎么测?(附实测波形对比)

本文为三相电机控制初学者提供了端电压、相电压和线电压的完整测量指南。通过厘清三者的物理定义与测量方法,详细讲解了如何搭建虚拟星形点电路,并对比分析了方波、SPWM和SVPWM三种主流控制模式下关键电压波形的核心特征与差异,帮助工程师掌握正确的示波器测量与波形解读技巧。

oauth7security的博客 648

045、镜头模组公差链分析:从 Lens 到 Sensor 的装配公差累积与良率优化

本文深入分析了镜头模组从镜片到传感器各环节的公差累积问题及优化策略。文章首先剖析了四级公差链(镜片单体、镜筒装配、VCM定位、传感器贴装)的误差传递机制,指出各环节典型公差值及其影响(如5微米镜片偏差可导致0.02度光线偏折)。其次对比了最坏情况法与均方根法的差异,强调需考虑公差相关性(如镜片曲率与镜筒间隙的正相关性)。最后提出良率优化"三板斧":通过蒙特卡洛仿真优化公差分配、采用主动对准工艺在线补偿、建立数据反馈闭环系统,并结合"阴阳脸"案例说明如何定位根因(模具磨损+贴片机老化导致的误差叠加)。作者总结

sundong_81的博客 59

启动窗口 [Android R]

一、启动窗口的类型 private static final int STARTING_WINDOW_TYPE_NONE = 0; //不使用启动窗口 private static final int STARTING_WINDOW_TYPE_SNAPSHOT = 1; //应用热启动,启动窗口是activity的一张截图 private static final int STARTING_WINDOW_TYPE_SPLASH_SCREEN = 2; //应用冷启动,启动窗口是应用配置的一

weixin_41126214的博客 2523

安卓新奇玩机工具全攻略___联发科MTK v6 限时免费 刷写固件工具操作

摘要:该文介绍一款限时免费的联发科V6刷机工具,支持小米、Vivo、Moto等多品牌设备,包括红米Note14Pro等机型(Klimt/Goya等代码)。工具需注册使用,提供EFS分区备份功能。作者提示工具可能触发杀毒警报,强调禁止非法用途,并说明部分联网工具可能失效。

小马哥的专栏 1316

如何应对Android面试官 -> WindowManagerService 启动流程分析

本章主要从上面几个角度来讲解 WindowManagerService;

IT_Android的博客 1490

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

android9-wms-startingwindow

注:系统级应用(如Launcher)可绕过策略直接控制StartingWindow显示逻辑。

summerkissyou1987的专栏 805

VB摄像头监控

该VB模块可对摄像头捕获的画面进行录像等,可在创建的窗口进行控制

StartingWindow Type

StartingWindow Type

我的博客 193

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

Android 如何实现欢迎界面(Splash Screen)

启动界面  先记一下   代码报错:SegmentFault 参考文章:http://blog.youkuaiyun.com/shaoduo/article/details/48351585   安卓判断是否首次进入app,通过判断跳转欢迎引导界面和主界面 http://www.jb51.net/article/36190.htm Android笔记之:App应用之启动界面SplashA

liumce的博客 2万+

Activity启动窗口(StartingWindow)的添加流程

本篇基于Android Q分析 在Activity启动的时候,Android系统会它一个启动窗口,作用是在应用程序主Activity还没有显示出来的时候,它作为一个预览窗口先让用户能看到一个画面,起到缓冲的作用,我们来分析下启动窗口的添加流程 ActivityStack.startActivityLocked void startActivityLocked(ActivityRecord r, A...

qq_34211365的博客 4940

winform 使用SplashScreen窗口

SplashScreen,就是平时我们说的溅射屏幕,任何一个做过客户端程序的coder应该对它都不陌生,因为它能提升用户体验,让软件看上去更美。SplashScreenForm通常进入程序时是打开,主窗体加载完毕后退出。一般来说,SplashScreenForm比较简洁,窗体的内容只是显示程序主题、版权等信息;复杂些的,可以显示主程序的加载项目情况。   下面是我实现的一个SplashScree...

刘欢的博客 6630

Android Q Go Apk热启动白屏/显示LOGO

问题点: 部分apk热启动会显示一下白屏/LOGO,导致apk热启动比对比机慢. 分析: 拍摄apk(日历或者设置apk)的热启动过程.慢动作播放,可以明显的看到apk启动过程中先显示白屏/LOGO.而对比机则没有这一过程. 一般情况下,只有冷启动会显示白屏或者LOGO,用LOGO图片掩盖白屏.而热启动直接加载apk界面,不会显示白屏或者LOGO. 可能原因: 1.查看log未发现异常. 2.在app info里面,apk的Storage & cache与对比机没有差异. ..

wende32的博客 913

ActivityManagerService解读之Activity启动闲聊--Activity启动窗口和TaskSnapshot

什么是Activity的启动窗口?为什么存在这样的一个窗口?什么是TaskSnapshot?啥也不说了,直接带着这些个问题开始本篇的闲聊。 什么是Activity的启动窗口,为什么存在这样的一个窗口? 我们先来一个动图直观的了解一下。 首先我们点击启动我们的测试demo,看见蓝色的界面没有,那就是Activity的启动窗口。Android在Activity启动的时候,当应用界面还未显示出来...

为码消得人憔悴 3963

Android 14+ 模拟器抓包实录:把 Burp CA 装成系统 CA,让 targetSdk≥34 的 App 乖乖解密

本文记录了在 Android 14+ 环境下,通过将 Burp CA 安装为系统信任锚,成功抓取 targetSdk=35 App 的 HTTPS 明文流量的完整实操过程。针对“浏览器可用、App 不可用”的典型现象,精准识别出根本原因是系统级证书信任策略变更(用户安装的 CA 被屏蔽),而非 SSL Pinning。仅通过 adb 命令将 CA 推送至系统证书存储并重启生效,无需 Frida、Objection 或修改 APK,实现零侵入解密。全文命令均基于 Windows + MuMu 模拟器环境实测通

JIANqiao19931029的博客 341

具身智能 2026:启元人形机器人开发者生态的“安卓时刻”来了吗

一个贯穿全文的问题:人形机器人开发者生态,是否正从「封闭垂直」走向「开源平台化」?本文以开源底座的公开数据为标尺,盘点 SDK、URDF、仿真与技能分发的开放程度,并落回启元机器人的开源路径。所有关键数据均标注来源,文末附社区与学堂链接。

在北风吹起的时候加入我们的队伍 在秋雨到来的时候成为彼此的温暖 744

android-ad-cleaner:android免 Root 批量关闭系统开屏与营销推送

橘子,计算机视觉方向工程师,深耕 AIGC 与多媒体处理领域,专注技术实用化落地。本文为原创内容,转载请注明来源与作者。优快云 首发 | 版权所有 © 2026。

下载资源或查看教程前,请务必阅读置顶的《免责声明》 501

Android 14 SELinux策略深度解析

init 的全局公开约束(如 neverallow init execute_no_trans,放 public 才能约束所有分区的规则)这就是"哪些可见"的强制机制。alps_s0(MTK 工程)的 device/mediatek/sepolicy/basic/ 提供。所有分区(system/system_ext/product/vendor)都可引用。init的公开约束、跨分区共用的 type/attribute/neverallow。——vendor 的 .cil 只能引用 plat 的。

新时代农民工 102

Android 开发入门实践(二):Kotlin 空安全,从可空类型到安全访问

延续 NewsApp 项目中的新闻标题练习,记录 Kotlin 安全调用。

Easyhood的博客 276
上一篇: 如何应对Android面试官 -> WindowManagerService 启动流程分析
下一篇: 如何应对Android面试官 -> 玩转 Fragment
老A说
博客等级 码龄14年 1181粉丝 85原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值