起因
昨天被人问起Activity的启动过程,我一阵心虚,说实话,N年前看过一回别人写的文章,但是自己从来没有跟着源码去研究过Activity的启动过程,所以别人问到后,我只能把从PhoneWindow到DecorView到SetContentView到ViewRootImpl调用performTraversals()方法,再调用其内部的performMeasure()、performLayout()、performDraw(),从而将布局文件绘制并加载到父类为FrameLayout的DecorView上,这个过程虽然没什么打错,但是这个其实只是View的绘制流程分支,与Activity的界面加载有部分重合,真正的Activity启动后,界面加载的流程是要比这个复杂的,怀着惭愧的心情,今天赶紧打开AndroidStudio,从源码开始,一点一点的扣整个启动过程。
不过这里先说一下,我无法从Activity的StartActivity()方法开始讲,因为这部分内容特别多,而且相当复杂,我还没有完全吃透,所以我的源码分析过程是从ActivityThread的StartActivityNow开始讲解并分析的,如果这个过程很熟悉,只是为了看前半部分的朋友,可以转战到这篇文章下:
过程
现在开始分析,首先打开android.app.ActivityThread类,找到startActivityNow()
1 | package android.app.ActivityThread |
这个方法主要的作用就是初始化一些参数后,并调用同类的performLaunchActivity()
1 | package android.app.ActivityThread |
首先第7行,先创建一个activity的实例;
然后第10行调用这个activiy实例的attach方法;
然后第16行开始,通过判断是否启用了PersistableBundle,来判断Instrumentation对象mInstrumentation调用哪个Activity的onCreate()方法,不了解PersistableBundle的可以看这篇文章:
android-1.0-四大组件-PersistableBundle
这里主要看一下attach方法:
1 | package android.app.Activity; |
要了解这里,首先要了解一下Activity的组成结构:
一个Activity的内部包含一个PhoneWindow,PhoneWindow有包含一个DecorView,DecorView其实就是一个FrameLayout的子类,它的内部又包含了TitleActionBar和ContentView,而attach这个方法,其中一个重要目的就是初始化PhoneWindow对象。
现在回到源码部分,上面这个方法,我罗列出的代码主要做了三件事:
1.将Activity.mWindow对象初始化
2.给mWindow设置WindowManager
3.给mWindowManager赋值。
好了,attach方法看完后,我们回到performLaunchActivity方法里,现在该mInstrumentation调用callActivityOnCreate方法了:
1 | package com.app.Instrumentation; |
这里我们主要看activity.performCreate(icicle);这行代码,进入performCreate方法:
1 | package com.app.Activity; |
最后发现,执行的就是onCreate方法,而我们写Activity方法的时候,一般都会写一个setContentView(layoutId)来设置界面布局,这时我们再看看setContentView方法:
1 | package com.app.Activity; |
这个方法其实做两件事,第一件事是调用getWindow()的setContentView方法,而getWindow()返回的是一个android.app.Window对象,这个对象就是刚刚在attach()中赋值的mWindow成员变量。
后面的initWindowDecorActionBar方法看名字就是初始化DecorView的ActionBar,这也印证了前面我们讲的Activity的框架结构,这个分支就走到这里,我们还是继续看getWindow().setContentView,由于Window是一个抽象类,而Window的setContentView方法其实是一个抽象方法,并没有具体的实现,所以我们要看的是window的子类:PhoneWindow的setContentView方法:
1 | package com.android.internal.policy.PhoneWindow; |
知识点:FEATURE_CONTENT_TRANSITIONS是标记当前内容加载有没有使用过渡动画,也就是转场动画。
首先我们注意一个变量,mContentParent是一个ViewGroup,而学过自定义View的同学,肯定知道ViewGroup就是FrameLayout,LinearLayout,RelativeLayout等布局文件的父类,所以这里有一个执行判断就是,如果mContentParent不为空,并且没有过度动画就执行mContentParent.removeAllViews();来清理界面,之后通过判断,没有过渡动画后,给mContentParent这个ViewGroup中添加view和布局文件。
现在我们来看看installDecor方法:
1 | package com.android.internal.policy.PhoneWindow; |
上面的方法主要做的其实就是初始化DecorView,并将DecorView和PhoneWindow进行关联,并初始化mContentParent.
看看generateDecor方法:
1 | package com.android.internal.policy.PhoneWindow; |
这里注意一下最后new DecorView的时候传入了this,这就说明DecorView与PhoneWindow确实关联了,并返回了一个DecorView的实例。
我们再看看generateLayout方法:
1 | package com.android.internal.policy.PhoneWindow; |
这个方法很长,注意看onResourcesLoaded方法:
1 | if (mBackdropFrameRenderer != null) { |
细节太多就不细说了,这个方法主要是创建mDecorCaptionView,然后将传递进来的布局文件inflate到这个DecorView中去。
再回到generateLayout方法,ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
而ID_ANDROID_CONTENT的常量值就是:com.android.internal.R.id.content;
然后根据:layoutResource = R.layout.screen_title;我们打开此布局文件:
1 | <?xml version="1.0" encoding="utf-8"?> |
发现最下面的FrameLayout的id属性就是android:id=”@android:id/content”,也就是说这个FrameLayout其实就是我们的变量contentParent。
最后放一个前辈对于DecorView的总结:
综合以上的探究,加上自己的一些思考和猜测。对PhoneWindow做一下小小的总结:
1.一个Activity对应着一个PhoneWindow对象,是一对一的关系,如果从Activity A启动到Activity B,那么Activity B会创建一个自己的PhoneWindow对象。
2.PhoneWindow管理着整个屏幕的内容,不包括屏幕最顶部的系统状态条。所以,PhoneWindow或者Window是与应用的一个页面所相关联。
3.PhoneWindow同时管理着ActionBar和下面的内容主题,setContentView()方法是用来设置内容主体的,而setTitle()等其他方法就是操作ActionBar的,Window中定义的requestFeature()等方法,有很多与ActionBar属性相关的设置。另外这些方法都是公有方法,显然是为了给客户端程序员调用的,也进一步佐证了这些操作的意义与作用。
4.PhoneWindow自己并不是一个视图(View),它的成员变量mDecor才是整个界面的视图,mDecor是在generateLayout()的时候被填充出来的,而actionBar和contentParent两个视图都是通过findViewById()直接从mDecor中获取出来的。
讲到这里,算是把方法installDecor讲完了,现在继续回到代码块:com.android.internal.policy.PhoneWindow的setContentView中去继续从installDecor方法往下看,mContentParent.removeAllViews();简单的说过了,这里就不复述了,之后PhoneWindow类的setContentView方法最后通过调用mLayoutInflater.inflate(layoutResID, mContentParent);或者mContentParent.addView(view, params);将我们的xml或者java View插入到了mContentParent(id为content的FrameLayout对象)ViewGroup中,最后setContentView还会调用一个Callback接口的成员函数onContentChanged来通知对应的Activity组件视图内容发生了变化。
现在重新放一下setContentView这个代码段:
1 | package com.android.internal.policy.PhoneWindow; |
注意最后几行代码,是由Callback的实例对象调用的onContentChanged方法,进入Callback的源码我们得知,Callback就是定义在抽象类Window中的一个接口,而getCallback()也仅仅是获取Callback接口的实例,但是这个Callback具体在哪里实现的,我们还得继续查,这里分享一下我的查询方式,我是通过在对应接口上按Ctrl+T的方式罗列出该接口的实现类,如下:
这时我们就注意到了,一个亲切的家伙就出现在我们面前了, Activity呀!对呀,如果PhoneWindow没有实现这个接口,那么作为组合类的Activity应该就会实现呀,而且我们回忆一下Activity的attach方法,呃,不用回忆了,直接贴再贴一次源码:
1 | package android.app.Activity; |
注意,mWindow.setCallback(this);这行代码,这个this不就代表的是Activity本身吗?那么cb.onContentChanged();方法不就是Activity的onContentChanged()方法吗?我们看一下:
1 | package android.app.Activity; |
Activity的onContentChanged()是一个空方法,这就是说,etContentView()或者addContentView()方法执行完毕时就会调用该方法,那么我们知道这个逻辑后,以后有什么布局二次变化的需求后,就可以将组件初始化的代码,如:findViewById()或者一些参数的初始化等业务代码放在我们App对应的Activity重写的onContentChanged()方法中去,让系统帮忙回调。
现在来总结一下setContentView做的事:
创建一个DecorView的对象mDecor,该mDecor对象将作为整个应用窗口的根视图。
依据Feature等style theme创建不同的窗口修饰布局文件,并且通过findViewById获取Activity布局文件该存放的地方(窗口修饰布局文件中id为content的FrameLayout)。
将Activity的布局文件添加至id为content的FrameLayout内。
题目叫做Activity的加载和显示,前面讲的都是Activity的加载,现在讲讲Activity的显示吧,至于为什么会是这个调用顺序或执行过程,那个需要单开一篇文章细说,这里只分析具体的加载和显示的源码过程,现在我们来看Activity中的handleResumeActivity方法:
1 | package android.app.ActivityThread; |
performResumeActivity()这个方法追到底,其主要就是Instrumentation调用了Acitivy的onResume()方法,我们了解就好,然后主要要看的是wm.addView(decor, l);,这里就是要动真格的了,我们继续往下追:
1 | package android.view; |
addView是接口ViewManager的一个方法,但是我们很诧异的是wm没记错的话应该是windowManager的实例啊,怎么成了ViewManager了?我们看一下handleResumeActivity的这行代码:
1 | ViewManager wm = a.getWindowManager(); |
通过追踪这个方法的调用,发现其实这里的设计是这样的:
1 | package android.view; |
WindowManager接口继承了ViewManager接口,从而在加载View时就使用了ViewManager中的addView方法,现在可以知道的是addView只是一个抽象方法,我们需要找到WindowManager的实现类,查看addView的源码而WindowManagerImpl就是WindowManager的实现类,我们查看这个类的addView方法:
1 | package android.view; |
其实本质上调用的是WindowManagerGlobal的addView方法,我们进去看一下:
1 | package android.view; |
这段代码主要的一个作用是调用了ViewRootImpl的setView方法,我们继续追踪:
1 | package android.view.ViewRootImpl; |
首先,此方法会把之前传进来的参数view赋值给mView,mView其实就是handleResumeActivity中的wm.addView时传进来的DecorView,而DecorView又是一个FrameLayout,这里其实就是将setContentView所做的一切初始化操作的DecorView设置成这个Activity的最基础的视图框架,具体见代码:
view.assignParent(this);
然后调用了requestLayout()方法来显示界面内容:
1 | package android.view.ViewRootImpl; |
先判断当前线程是不是主线程,然后就调用了scheduleTraversals()方法,继续跟进:
1 | package android.view.ViewRootImpl; |
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);通过跟进源码得知其本质就是通过handler发送消息,那么我们关注的重点就应该是mTraversalRunnable这个Runnable接口:
1 | package android.view.ViewRootImpl; |
进doTraversal方法看看:
1 | void doTraversal() { |
继续跟进performTraversals方法:
1 | private void performTraversals() { |
这个方法的逻辑非常长,反正我是没看完,但是我们可以注意到,这三个方法和两个属性的初始化,其主要作用其实就是对基础的根节点View进行View的初始化操作,也就是我们常说的onMeasure(测量)、onLayout(布局)和onDraw(绘制),而childWidthMeasureSpec和childHeightMeasureSpec主要的作用就是为测量提供测量规格,这里具体的内容可以看我的另一篇文章:Android自定义View的测量过程详解
总结
千言万语的总结不如最后绘成一张图来的清晰明了: