Activity Window 创建及添加过程

976 阅读3分钟

setContentView WindowManagerImpl WindowManagerGlobal ViewRootImpl WMS 流程梳理

1. setContentView

Activity 中将 setContentView 工作交给了 Window 对象去处理:

public void setContentView(@LayoutRes int layoutResID) {
    getWindow().setContentView(layoutResID);
}

每个 Activity 内部都持有一个 Window 对象,而 Window 对象是在创建 Activity 流程中 attach 时就实例化的:

final void attach(...){
    mWindow = new PhoneWindow(this, window, activityConfigCallback);
}

PhoneWindow 中会实际去 inflate 布局:

public void setContentView(int layoutResID) {
    if (mContentParent == null) {
        installDecor();
    }
    mLayoutInflater.inflate(layoutResID, mContentParent);
}

而 installDecor 中伪代码逻辑如下:

private void installDecor() {
    if (mDecor == null) {
        mDecor = new DecorView(...);
    }
    if (mContentParent == null) {
        mContentParent = (ViewGroup)mDecor.findViewById(R.id.content);
    }
}

2. WindowManagerImpl

setContentView 只是创建好了 View 视图结构,还没告知 WMS。在 ActivityThread handleResumeActivity 方法中,才会与 WMS 通信开始添加 Activity 窗口,代码如下:

@Override
public void handleResumeActivity(IBinder token, ...) {
    final ActivityClientRecord r = performResumeActivity(token, ...);
    final Activity a = r.activity;
    if (r.window == null && !a.mFinished && willBeVisible) {
        r.window = r.activity.getWindow();
        View decor = r.window.getDecorView();
        ViewManager wm = a.getWindowManager();
        a.mDecor = decor;
        wm.addView(decor, l);
    }
    r.activity.makeVisible();
}

其中 performResumeActivity 方法会回调 onResume 生命周期,之后调用 WindowManager 的 addView 方法并将 DecorView 传入。这里的 WindowManager 为 WindowManagerImpl 对象,与 Activity 内部的 window 一样,都是在 Activity attachContext 时创建。

WindowManagerImpl 中 addView 方法如下:

@OverrideActivity Window 创建及添加过程
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
    applyDefaultToken(params);
    mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}

其进一步交给了 mGlobal 即 WindowManagerGlobal 去处理.

3. WindowManagerGlobal

接着来看 WindowManagerGlobal 的 addView 方法:

public void addView(View view, ViewGroup.LayoutParams params,
        Display display, Window parentWindow) {
    int index = findViewLocked(view, false);
    if (index >= 0) {
        if (mDyingViews.contains(view)) {
            mRoots.get(index).doDie();
        } else {
            throw new IllegalStateException("View " + view
                    + " has already been added to the window manager.");
        }
    }
    ViewRootImpl root = new ViewRootImpl(view.getContext(), display);
    view.setLayoutParams(wparams);
    mViews.add(view);
    mRoots.add(root);
    mParams.add(wparams);
    root.setView(view, wparams, panelParentView);
}

由于 WindowManagerGlobal 为进程单例,其内部的 mViews 则记录了全局添加的 View。当重复添加 View 时,就会抛出 "View has already been added to the window manager" 异常。

接着创建一个与 View 对应的 ViewRootImpl,将 View、ViewRootImpl 记录在 WindowManagerGlobal 中后,调用了 ViewRootImpl 的 setView 方法。

4. ViewRootImpl

先来看 ViewRootImpl 的构造函数:

public ViewRootImpl(Context context, Display display) {
    mContext = context;
    mWindowSession = WindowManagerGlobal.getWindowSession();
    mWindow = new W(this);
    ...
}

其中 mWindowSession 是通过 WMS openSession 获取的匿名 binder,用于应用调用 WMS;mWindow 也是一个 binder 接口,用于 WMS 调用应用端。 接着看 ViewRootImpl setView 方法,关键代码如下:

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    synchronized (this) {
        if (mView == null) {
            mView = view;
            requestLayout();
            res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                    getHostVisibility(), mDisplay.getDisplayId(), mTmpFrame,
                    mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                    mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel,
                    mTempInsets);
        }
    }
}

值得注意的是,不需把 view 传给 WMS,这是因为 WMS 并不关心 View 树所表达的具体 UI 内容,它只要知道各应用进程显示界面的大小、窗口层级值即可。

5. WMS

到达 WMS 所在 system_server 进程后,WindowSession addToDisplay 会进一步调用 WindowManagerService 的 addWindow 方法,执行添加用户窗口工作,包括:

(1)对用户窗口进行权限检查,比如 TYPE_PHONE 等窗口类型需要 SYSTEM_ALERT_WINDOW 权限

(2)检查 mWindow,窗口最终会记录到 <IBinder,WindowState> HashMap 中,其中 IBinder 为应用端的 mWindow,即一个 mWindow 只允许添加唯一的窗口

(3)检查窗口类型,比如子窗口必须依赖于一个父窗口

(4)按照窗口层级添加合适的位置

6. 流程梳理

(1)每个 Activity 内部都持有一个 window 对象,其实现为 PhoneWindow,在 Activity attachContext 时创建

(2)PhoneWindow 的根布局为 DecorView,其包括 TitleView 和 ContentView,Activity setContentView 就是把布局添加到 ContentView

(3)当 Activity 第一次回调 onResume 后,开始将 Activity 的窗口添加到 WMS,首先调用了 WindowManagerImpl,WindowManagerImpl 进一步调用进程单例的 WindowManagerGlobal

(4)WindowManagerGlobal 中创建了与 DecorView 对应的 ViewRootImpl,并将 DecorView 和 ViewRootImpl 记录下来

(5)在 ViewRootImpl 中与 WMS 发生交互,应用端通过 WindowSession 调用 WMS,WMS 通过 IWindow 调用应用端

(6)WMS 中会对窗口进行权限、类型等检查,最终将应用窗口信息记录下来