Android Activity 创建 Window 及添加 View 流程分析

694 阅读7分钟
原文链接: www.glumes.com

Glumes 技术博客

纸上得来终觉浅,绝知此事要躬行

在之前有分析过 Android 6.0 Launcher 启动 Activity 过程,文章的链接如下:

1、Android 6.0 Launcher 启动 Activity 过程源码分析(一)
2、Android 6.0 Launcher 启动 Activity 过程源码分析(二)
3、Android 6.0 Launcher 启动 Activity 过程源码分析(三)
4、Android 6.0 Launcher 启动 Activity 过程源码分析(四)

大致的总结一下:

ContextImplstartActivity方法开始,通过ActivityManagerProxy代理对象向ActivityManagerService发起 IPC 调用。

ActivityManagerService会对startActivity方法进行响应,处理传递过来的Intent。这其中包括,解析Intent信息,创建或分配待启动的 Activity 的 Task任务,将启动者 Activity 运行至Paused状态,创建待启动 Activity 的进程。

完成一系列准备工作之后,就该启动 Activity 了。启动的入口是ActivityThread类的main方法。在main方法中会开启主线程的消息循环,并调用attch方法,之后就是创建 Application,然后再创建 Activity。

创建 Activity 是 ActivityManagerService 进程向应用程序进程发送一个进程间通信,应用程序进程通过handleLaunchActivity方法进行响应,在其方法内部执行了performLaunchActivity方法和handleResumeActivity来完成启动。

performLaunchActivity方法内部,通过反射创建了 Activity 类,并且调用了 Activity 类的 attach 方法,为其关联运行过程中所依赖的一系列上下文环境变量,比如 Activity 的 Context 就是在 attach 方法内部调用attachBaseContext方法添加上的,同时,还调度了待启动的 Activity 的生命周期,从 OnCreate 到 OnStart 状态 。

performLaunchActivity方法后,就执行了handleResumeActivity方法,将 Activity 的状态运行至 onResume 状态,此时 Activity 就处于可见的状态了。Activity 也处于能够正常工作的状态了。

而 Activity 类的 attach 方法也就是本文要追踪的方法了,在此方法内如何为 Activity 创建 Window 的,之后才是向 Window 中添加 View。

Window 的创建

attach 方法内创建 Window 的代码示例如下:

        mWindow = new PhoneWindow(this); // 创建 Window,实例是 PhonwWindow
                mWindow.setCallback(this);    // 设置回调
                mWindow.setOnWindowDismissedCallback(this);  // 设置回调
                mWindow.getLayoutInflater().setPrivateFactory(this);
                if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
                    mWindow.setSoftInputMode(info.softInputMode);    // 调整键盘
                }
                if (info.uiOptions != 0) {
                    mWindow.setUiOptions(info.uiOptions);
                }
                mWindow.setWindowManager(    // 设置 WindowManager
                        (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
                        mToken, mComponent.flattenToString(),
                        (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
                if (mParent != null) {
                    mWindow.setContainer(mParent.getWindow());
                }
                mWindowManager = mWindow.getWindowManager();

在 Android 6.0 的源码中,创建 Window 不再采用策略模式了,直接创建了 PhoneWindow。Window 是一个抽象类,它的唯一实现就是 PhoneWindow。

接下来就是为 Window 设置回调,回调是实现都是由 Activity 来实现的,回调方法有很多,就不一一列举了,常见的有如下一些:
* onAttachedToWindow
* onDetachedFromWindow
* dispatchTouchEvent
* dispatchKetEvent

从 dispatchTouchEvent 回调可以看出,Activity 响应触摸事件,实际上的响应了 Window 的回调方法,说明触摸事件是传递给我们的 Window,然后再又 Activity 进行分发的。

然后就是为 Window 设置 WindowManager 。

至此,Activity 的 Window 就算是创建完成的,剩下的就是往 Window 中添加 View 了。

Window 中 View 的添加

Android 中的所有视图都是通过 Window 来呈现的,不管是 Activity、Dialog 还是 Toast,它们的视图实际上都是附加在 Window 上的。

因此,我们的 Activity 中的所有 View 也是添加在 Window 上的,而这一切入点就是setContentView方法。

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

getWindow 方法返回的就是上面创建的 PhoneWindow 对象,而重点就在于setContentView方法内。

是时候展现一张经典的图了:

activity-window

在上图中,DecorView 就是一个 FrameLayout 的 ViewGroup 容器,也是 Activity 中的顶级 View,一般来说它内部包含标题栏和内部栏,但还是会随着主题发生变化的,但不管怎么样,内容栏是肯定有的。

当创建了一个 DecorView 对象时,还得把内容栏和标题栏给添加进去才行。

同时,DecorView 是依附于 Window 之上的,没有了 Window,DecorView 也无从谈起了。当我们创建好了 DecorView 时,还得把 DecorView 添加到 Window 上。

setContentView方法就是对上述图片创建 DecorView 的解释了。

    @Override
            public void setContentView(int layoutResID) {
                if (mContentParent == null) {    // 代表图中 ContentView 的内容
                    installDecor();    // 创建 DecorView
                } 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);    // 将布局文件加载到 mContentParent 中去,注意 mContentParent 就是上图中的内容栏
                }
                mContentParent.requestApplyInsets();
                final Callback cb = getCallback();
                if (cb != null && !isDestroyed()) {
                    cb.onContentChanged();    // 在 Activity 内部回调 onContentChanged 方法,表示视图已经发生改变
                }
            }

首先要明确的是mContentParent指的就是图中的ContentView内容栏,当内容栏为 null 时,则去为 Window 创建 DecorView 。

当 DecorView 创建完成后,则直接调用 LayoutInflater 的 inflate 方法将我们自己的布局文件添加到mContentParent中,由此完成了 View 的添加。关于 LayoutInflater 如何将布局添加到父布局中,可以参考之前写的文章:Android 布局加载之LayoutInflater

DecorView 的创建和初始 View 的添加

下面就是分析 DecorView 是如何创建并添加初始的布局内容的。

    private void installDecor() {
                if (mDecor == null) {
                    mDecor = generateDecor();    // 直接 new 一个 DecorView 对象
                }
                if (mContentParent == null) {
                    mContentParent = generateLayout(mDecor);    // 创建内容栏
                }
            }
            

在 installDecor 方法中直接 new 了一个 DecorView 对象,此时它还是只是一个 FrameLayout 容器,内部还没有东西。接着就是去创建内容栏。

        int layoutResource ;
                // 省略掉根据各自 Feature 参数来初始化不同的 layoutResource 过程
                mDecor.startChanging();    // 标识 DecorView 中的内容开始改变
                View in = mLayoutInflater.inflate(layoutResource, null);    // 加载 XML 布局文件,可能没有标题栏,但一定有内容栏
                decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));    // 为 DecorView 添加内容
                mContentRoot = (ViewGroup) in;
                ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
                mDecor.finishChanging(); // 标识 DecorView 中的内容改变完成

在上面曾提到了,在 DecorView 中,标题栏可能没有,则内容栏是一定要有的,这是因为用户可能在 Actiivty 设置了相应的参数,比如设置 Window 类型为 FEATURE_NO_TITLE 的类型。

而 DecorView 添加内容 View 时,这个 View 是从 XML 布局文件中加载的,针对不同的 FEATURE, XML 布局文件中的内容也不相同,有的可能就没有标题栏了,只有一个内容栏,而且不管怎么样,所有的内容栏的 ID 都是固定的,为 android.R.id.content

至此,DecorView 就完成了创建过程,并且还向其中添加了标题栏和内容栏。

DecorView 添加至 Window 上

众所周知,在 Activity 的 onCreate 和 onStart 阶段,View 还是处于不可见的状态,只有在 onResume 状态时,View 才是可见的。

而我们现在只是分析完了 DecorView 的添加,还并没将它依附到 Window 上去。只有将 View 添加到 Window 上之后,它才能够拥有具体的功能,处理外界的输入信息等等。

handleResumeActivity方法内的处理代码如下:

    // 变量 r 指的是 ActivityClientRecord 类,变量 a 指的是 Activity,也就是 r.activity
                 if (r.window == null && !a.mFinished && willBeVisible) {    // if 判断成立
                        r.window = r.activity.getWindow();
                        View decor = r.window.getDecorView();    // 设置 DecorView 可见
                        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 (a.mVisibleFromClient) {
                            a.mWindowAdded = true;
                            wm.addView(decor, l);    // WindowManager 添加 DecorView
                        }

截取的代码片段,其中r指的就是ActivityClientRecord类型,存储了 Activity 的状态,此时rwindow变量还是为 null 的。于是为ActivityClientRecord相应的变量设置值,以记录当前 Activity 的状态,然后在设置 DecorView 为可见,并把 DecorView 添加到 WindowManager 中去,以便 DecorView 能够响应各种事件,进行事件分发等等操作。

至此,就分析完了 Activity 的 Window 的创建以及 View 的添加过程。

总结

最后再来一发小小的总结,Activity 创建 Window 并添加 View 的流程大致如下:

  • ActivityThread是应用程序的入口,它的attach方法会开启主线程消息循环,然后创建ApplicationActivity等。
  • 创建Activity时,又有调用Activityattach方法,在此方法内将为 Activity 创建一个 Window,此时 Window 内还是空白的。
  • Instrumentation类开始回调 Activity 的一系列生命周期方法,当回调onCreate方法时,主要做的就是创建 DecorView,并将我们传递过来的 View 添加到 DecorView 的内容栏中,而此时,DecorView 和 Window 两者还是处于分离的状态。
  • 在 Activity 生命周期的onResume方法内,将 DecorView 添加进了 Window 中,并设置 DecorView 可见,此时,就完成了 Activity Window 的创建和 View 的添加过程。

原创文章,转载请注明: 转载自www.glumes.com/android-act…

本文链接地址: www.glumes.com/android-act…

知识共享许可协议
本作品采用 知识共享署名-相同方式共享 4.0 国际许可协议 进行许可