作者简介 原创微信公众号郭霖 WeChat ID: guolin_blog
本篇来自程序媛fanfan_story的投稿,透过Android6.0源码,分析了Activity加载View的过程,希望大家喜欢。
fanfan_story的博客地址:
http://blog.csdn.net/zrf1335348191
认识Activity的布局
对于研究布局这种东西,必须要掌握一些视图工具,在这里推荐一个sdk查看视图的工具sdk\tools\hierarchyviewer,随意找一个界面去查看 activity 的 view视图:
在这个 activity 界面中我把导航栏给隐藏了,所以不存在导航栏,根据这张图的话大致可以看到一个 activity 的布局,再结合对 setContentView 的研究,可以总结出 activity 的布局图如下:
从这张 activity 的布局图可以看到:一个 activity 对应一个应用窗口 mWindow,应用窗口 mWindow 包括 activity 的 顶级view是 mDecorView,mDecorView 包括 状态栏statusbar和 导航栏navigationbar 以及要加载 activity 布局的view一一mDecorContentParent,该 view 又包括一个 标题栏titlebar 和 activity的内容布局 contentparent。在 contentParent 中就是该 activity 的 view树。
1. mWindow:Window对象,Window是一个抽象类,是 activity 的顶层外观和行为的代理。会往 windowmanage 中添加该类的一个实例作为 顶层view。window 提供基本的UI代理,比如背景啊,标题区域啊,按键处理啊等等,Window 只有一个实现类 PhoneWindow,所以 mWindow对象 实际是 PhoneWindow对象。当启动一个activity的过程中会初始化一个属于 Phonewindow 的 window对象。Phonewindow对象 的创建在activity 的 attach方法 中。
2. mDecor:DecorView对象,继承自 framelayout,是 window窗口 的 顶级view,包含 window 的装饰。类的定义位于 PhoneWindow.java 中
3. mDecorContentParent:DecorContentParent对象,实现类是 ActionbarOverlayLayout,属于 activity 布局的 最外层view,包括标题栏和activity的内容布局
4. mContentParent:activity 的内容布局,继承自 ViewGroup,用来加载存放 activity 的 view树,如果没有标题栏,那么 mContentparent 的大小会和 mDecorContentParent 相等,以此类推
5. 导航栏:statusbar,对应的 id 为 statusBarBackground,在 PhoneWindow 中会加载,当 window属性 发生改变时会刷新导航栏。但不论是导航栏和状态栏,从这个id也可以看出,PhoneWindow 只是加载他们的 background,即相当于只加载一个view的占位,先告诉应用窗口,系统窗口要求将状态栏和导航栏布局在这里,你不要占用,但此时不会加载导航栏和状态栏的view,只是绘制背景而已。
6. 状态栏:navigationbar,对应的 id 为 navigationBarBackground,在 PhoneWindow 中会加载,当 window属性 发生改变时会刷新状态栏
7. 标题栏:titlebar,对于导航栏,状态栏和标题栏的存在与否,与 window 的属性特征有关,在加载 view 时所以会去判断 window 的属性特征,进而决定是否要加载这三者。
对 activity 的布局大致有个了解之后,就开始去分析 activity 启动后加载view的流程
Activity加载View布局
对于 activity 的布局的加载大致分为两部分,一部分是加载view,另一部分是将view绑定到应用窗口Window。其中这两个步骤中将 view 绑定到 window 是在 启动activity时 完成的操作,是将 mDecor 绑定到 window。然后再往 mDecor 中添加 各种view。对于 activity的启动过程 留待以后进行分析,现在分析加载view一一始于 Activity.Java 的setContentView方法,看一下加载view的流程。
可以看到代码流程很简单,从 Activity.java 的 setContentView方法 进入,到 PhonewWindow.java 的 setContentView方法 进行一系列处理,接下来进入代码进行分析
Activity.java 的 setContentView 方法
代码路径\Android\frameworks\base\core\java\android\app
源码中对该方法的解释是,从一个 layout 文件中取出 view 设置成 activity 的 content,该资源文件会被填充,并遍历文件中的 所有view 添加到 activity。意思就是填充一个资源文件,加载view。做了两件事儿:
一是 getWindow 获取到 Window对象,然后去调用 Window 的 setContentView方法。
二是 initWindowDecorActionbar(),创建 actionbar对象,填充 mDecor 下的 actionbarView,并把 view 加载上去(博主猜测是在 Window 的 setContentView方法 中只是填充一个 actionbar 的占位,然后 initWindowDecorActionbar() 完成 view 的加载)
重点研究第一步:getWindow().setContentView方法。
首先一个问题,为什么我要说 getWindow.setContentView 调用的是 PhoneWindow 中的setContentView方法?
解疑:查看getWindow方法:
publicWindowgetWindow() {
returnmWindow;}
返回的是 activity 的 mWindow对象,对于 mWindow对象 的创建也是在 Activity.java 中的 attach方法 中:
同时,进入到 Window.java 中也可以看到这一点:
源码中对于 Window类 的说明是:
Window 是一个抽象类,是最顶层的窗口的外观和行为的代理,window 的实例应该被作为最顶层的UI添加到 WindowManage 中。Window 提供了基本的ui,比如背景,标题区域,默认的按键处理过程等等。Window 只有一个唯一的实现类 PhoneWindow,当需要 Window对象 时需要去初始化 PhoneWindow。
至此,对于 Activity 中的 mWindow对象 大致有了一个清晰的认识了:他是个 PhoneWindow对象,Window.java 中方法的实现在 PhoneWindow.java 中
PhoneWindow.java中的setContentView方法
代码路径\android\frameworks\base\core\java\com\android\internal\policy
先来总结一下代码的流:
在新启动一个 activity 时 mContentParent 还未绑定id,此时 mContentParent 为null。从代码流程图中可以看出 setContentView 做了三件事:
installDecor 实例化 DecorView对象 和 mContentParent对象
填充 layout 文件
通知 activity 布局已经改变
为什么说是通知 activity 布局已经改变呢?这是因为在 Activity.java 的 attach方法 中 mWindow对象 设置了 callback 为this,所以在 getCallback 时获取到的 cb 为当前与该 window 对应的 activity。
PhoneWindow.java中的installDecor方法分析
实例化 DecorView对象 和 mContentParent对象
在创建一个 activity 时 mDecor 和 mContentParent 均为null
借助 generateDecor 方法实例化 mDecor,即获取到 activity 的 最顶级的view
借助 generateLayout 方法实例化 mContentParent对象,并且根据 window 的不同 Feature 来选择对应的布局文件(总之,generatelayout 其实就是根据当前的 window 的特征属性 feature 来加载内容布局,并获取到当前布局的 最外层view。也就是说
generatelayout 本质就是根据 activity 的 theme主题 来找到对应的xml布局并且获取到id为 content 的 ViewGroup赋给 mContentParent
获取到 mDecorParent对象,并且根据 getLocalFeature 获取到的 Feature 来设置(这也就说明了在自定义Activity时为什么要将 getWindow.requestFeature方法 写在 setContentView方法 之前)
对 title 进行隐藏或者是设置内容的操作
如果需要切入切出动画,那么就获取到各种动画资源
接下来对 installDecor 中某些代码做一些分析:
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
用于对焦点的传递设置:只有当 子view 不想获取焦点时 mDecor 才会去获取焦点
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable)
去开启初始化menu菜单的线程
在这里说明一句,为什么 requestFeature 要写在 setContentView 前面,这是因为在调用 setContentView 时会获取到 window 的各种 feature 进行一些判断设置。
PhoneWindow.java中的generateLayout方法研究
第一步:首先是获取到 window 的布局style
TypedArraya=getWindowStyle();
第二步:获取到各种属性并进行 requestFeature 的设置
第三步:通过获取到的 window 的布局去获取 window 的各种属性,并根据 window 的各种属性去选择不同的 layout 的文件,比如标题栏是否隐藏,window 是悬浮窗还是全屏等等问题。当然因为在3.0和4.0以及5.0对于menukey的支持不同,所以会有一个与版本相关的一个判断。至于这个版本之间有什么不同可以参考总结说明中列出来的文件。
其实 generatelayout 就做了一件事,那就是根据 window 的各种属性去获取不同的xml文件。
总结
setContentView 执行流程中主要涉及到3个类 PhoneWindow.java,Activity.java 和 Window.java
Window 和 windowmanager 中的 各种feature 和 flag的style 对应的各种含义以及动画 style在\android\android\frameworks\base\core\res\res\values\attrs.xml 文件中有注释说明
在 menu键 的设置中涉及到了版本问题,包括 3.0,4.0 和 5.0 分别有对应的不同处理,参考 \android\android\frameworks\base\core\java\android\os\Build.java 可以看到注释有说明各版本有什么不同
至于为什么说 mDecor 是 最外层view,是因为在 generateLayout 方法中 mDecor 将填充该xml文件的 view一一mContentRoot 添加了进来。
Activity在启动加载布局的操作
创建 DecorView 的布局:setContentView 的流程基本是用来创建 DecorView 的布局
将布局添加到 window 窗口:在 Activity 的启动过程中,会将应用窗口添加到 WindowManager 中进行统一管理,以及绑定 DecorView
对于状态栏和导航栏,是在每次 window 属性发生变化时会去更新,但是只是设置了一个背景色,只是占位用,没有加载这些view
文章原创作者GuoLin 书籍推荐
郭林大神原创android 书籍:《第一行代码 android》