Activity 的setContent()流程
以前的Activity,都是直接继承Activity.java,而现在的Activity则基本都是继承AppCompatActivity.java,自然setContent()是不一样的,那么先捋一捋旧的
Activity.java
先从Activity.java开始看起。
1 | public void setContentView(@LayoutRes int layoutResID) { |
可以看到,getWindow()方法获取了一个自己Activity持有的Window对象的引用,再调用这个对象的setContent(),之后做一个初始化流程。Window类是一个抽象类:
1 | /** |
看注释,这大概是一个Activity所呈现界面的顶层Window。他的实现类只有一个,是PhoneWindow。那么就来看看这个PhoneWindow类的setContentView()方法实现:
1 |
|
再看看mContentParent的定义:
1 |
|
可以看到,这个mContentParent其实就是一个ViewGroup
所以在setContent()中主要做了两件事:
- 初始化一个Window持有的ContentParent(即ViewGroup)对象
- 将布局文件加载到ContentParent上
那么现在看看这个所谓的初始化过程做了什么,即installDecor():
1 | private void installDecor() { |
再看看这个mDecor是何方神圣:
1 | // This is the top-level view of the window, containing the window decor. |
1 | ** */ |
原来mDecor就是一个FrameLayout了。
那么这个初始化过程就分为了两步:
- 初始化mDecor(一个FrameLayout)
- 借助mDecor初始化mContentParent
再来分别看看两者是如何初始化的,显示mDecor:
1 | protected DecorView generateDecor(int featureId) { |
先是用想办法和获取一个context,然后再调用新的构造器,这里的featureId传进来的是-1。然后看构造器:
1 |
|
至此一个DecorView就初始化完成了,他实际上是一个FrameLayout。接下来看看这个mContentParent是如何通过DecorView来生成的:
1 | protected ViewGroup generateLayout(DecorView decor) { |
总结一下,就是给这个framelayout————DecorView设置了一种布局,然后通过findviewbyid的方式获取一个contentparent的。那么这两者有什么关系呢?观察到前面提到了两个id,联系就在这里!所以接下来看看具体设置布局的逻辑。
首先看看加载布局:
1 | void onResourcesLoaded(LayoutInflater inflater, int layoutResource) { |
看到layoutInflater就知道了,这里果然是加载layoutResource指向的那个布局,这里加载后为一个叫做root的View,然后通过调用addView()方法————我们知道DecorView本身是一个FrameLayout————将root加载到自己这个FrameLayout中。
接下来看看layoutResource所引用的布局R.layout.screen_simple,即screen_simple.xml的内容:
1 | <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" |
嗯,一个LinearLayout,包含了一个ViewStub占位和一个FrameLayout。做一个猜测,这就是我们Activity最普通的初始界面,即一个状态栏+一个主界面。然后发现下面那个FrameLayout的id是content,再回到刚才方法中,通过findviewbyid初始化找到contentParent的时候用的id是哪个?
1 | /** |
根据注释,我们知道了,这个id引用的view就是我们的主布局要加载的地方,也就是在刚才那个xml文件中的FrameLayout!
到此为止,一个installDecor()的过程基本完成了,来捋一捋。
首先要初始化一个叫做DecorView的FrameLayout,他是和当前Window息息相关的。我们知道一个Activity,他的显示界面这个模块是交给了Window管理,而在Window中则是交给了DeocrView。这个DecorView会根据不同的需求(主题)来给自己填上一个不同的布局。然后在加载进来的这个布局中,有一个ViewGroup是专门用来显示我们编写的界面的,这个ViewGroup会通过findViewById()的形式在DecorView中找到,然后交给mContentParent,这样我们要将自己写的布局加载进来的时候,就是直接加载到mContentParent中就可以了。
回过头来看看setContentView:
1 |
|
至此,Activity的setContent()流程就是走完了,大致知道了布局是怎么加载进来的。接下来看看新的AppCompatActivity是如何加载布局的
AppCompatActivity
接下来再看看AppCompatActivity是如何加载布局的
先看AppCompatActivity.java的setContentView()方法:
1 |
|
这里通过getDelegate()方法获取了一个对象的引用,再调用他的setContentView()方法,相当于做了一个代理。
那么现在问题拆分为两步:
- 代理的对象是如何创建的
- 代理对象的setContentView()是如何执行的
先看第一个问题:
1 |
|
这里用来做代理的,是一个AppCompatDelegate对象,叫mDelegate,他是通过一个静态方法create()创建的,那么先看看这个类是什么:
1 | <p>An { Activity} can only be linked with one { AppCompatDelegate} instance, |
再来看看他的create()方法:
1 | /** |
1 | private static AppCompatDelegate create(Context context, Window window, |
可以看到,这里最终是根据不同的sdk版本来创建不同的AppCompatDelegateImplxxx对象,分别点进去看看后会发现,最终都是到了AppCompatDelegateImplV9.java,然后:
1 | class AppCompatDelegateImplV9 extends AppCompatDelegateImplBase |
1 | AppCompatDelegateImplV9(Context context, Window window, AppCompatCallback callback) { |
所以最终是调用了父类的构造器:
1 | AppCompatDelegateImplBase(Context context, Window window, AppCompatCallback callback) { |
这样就完成了。接下来看看setContentView()是如何执行的。
进入AppCompatDelegateImplV9.java的setContentView():
1 |
|
再看看这个SubDecor是什么:
1 |
|
所以我们自己写的布局文件最终是被加载到了一个id为content的ViewGroup上,而这个ViewGroup是通过subDecor来找到的,而这个SubDecor也是一个ViewGroup。那么重点就是ensureSubDecor()了,他的作用应该就是初始化一个SubDecor了:
1 | private void ensureSubDecor() { |
现在就分为了两步:
- mSubDecor是如何被创建的
- 创建成功之后做了什么
第一个问题,看createSubDecor()方法:
1 |
|
再来看看那个重点标记的方法:
1 |
|
哦?原来window的getDecorView()方法其实就是前面提到过的installDecor()方法诶!之前说过,installDecor()方法是什么作用来着?
首先要初始化一个叫做DecorView的FrameLayout,他是和当前Window息息相关的。我们知道一个Activity,他的显示界面这个模块是交给了Window管理,而在Window中则是交给了DeocrView。这个DecorView会根据不同的需求(主题)来给自己填上一个不同的布局。然后在加载进来的这个布局中,有一个ViewGroup是专门用来显示我们编写的界面的,这个ViewGroup会通过findViewById()的形式在DecorView中找到,然后交给mContentParent,这样我们要将自己写的布局加载进来的时候,就是直接加载到mContentParent中就可以了。
马上接触到真相了,再随便找个刚才所引用到的布局文件看看,比如R.layout.abc_screen_simple:
1 | <android.support.v7.widget.FitWindowsLinearLayout |
还有abc_screen_content_include.xml:
1 | <merge xmlns:android="http://schemas.android.com/apk/res/android"> |
看看那个id:action_bar_activity_content,而且他还是很个ContentFrameLayout 发现没有?这里的SubDecorView和以前的DecorView逻辑是很像的!都是以自己作为一个大的ViewGroup,里面放另一个小ViewGroup,在这个小ViewGroup中,还有一个ViewGroup作为根布局。
捋一捋刚才的流程:
- 首先创建了两个DecorView,一个就是以前Activity直接用的那个DecorView,另一个叫做SubDecorView
- 将旧DecorView的content内容交给SubDecorView的content
- 将SubDecorView作为一个整体,交给DecorView
总之,就是一个替换的过程。
再回到前面看看:
1 |
|
至此,Activity的AppCompatActivity的setContent()的流程都分析完了,总结一下:
- 一个Activity,有很多功能,其中的“展示东西给别人看”这个功能,是交给自己的一个Window对象来管理的。
- Window包含一个ViewGroup,作为根ViewGroup
- 根ViewGroup,根据不同的需求(即主题定义等)会加载不同的布局文件
- 以最基本的一种布局来讲,他包含一个title和一个content
- 我们setContent()时传入的布局文件id所指向的那个布局文件,会被加载到这个content中
- Activity和AppCompatActivity在这里的区别在于,Activity单纯的用一个DecorView,AppCompatActivity则是在原来的基础上,加了一个SubDeocrView,将旧的DecorView的内容放到SubDecorView的content中,然后将SubDecorView作为整体放入旧的DecorView的content中,也就是说,一个DecorView包裹着一个SubDecorView