本文的思路:Android程序启动-->Activity加载并完成生命周期-->setContentView-->图形绘制。
疑惑:
1.Android程序是如何启动,Activity生命周期如何调用?
2.在Activity onCreate当中我们的setContentView是如何将UI文件加载?
3.UI是如何绘制的?
答案
1.Android程序流程
众所周知,我们的java程序想要开启需要依赖于main方法,也就是我们的程序入口(主线程)进入,但是在我们日常开发android程序的过程当中我们并没有发现main方法的存在,那么android当中的是如何开始进行的?
熟悉的朋友们可能都知道在android当中存在一个叫做ActivityThread类,这个类代表的是android当中的主线程,而在这个类当中我们看到了比较熟悉的main方法,那么现在是否可以认为我们的android在打开app时是首先调用的是当前这个类的main,也就是此处为我们程序的启动点:
main入口方法
在此处可以看到ActivityThread调用了一个attach方法:
image.png
在这里我们可能首先要考虑的是getService拿出来的是什么?
进去之后,我们会发现:
image.png
在这个当中,里面调用了系统的ActivityManagerService这个服务,并且给出了一个Binder接口,那么在这里,我们可以联想到,在android当中的binder通信机制,那么实际上我们的ActivityManager是由系统服务所调用管理,并且通过在binder接口当中进行调用,这也是为什么我们讲Activity是跨进程访问的原因:
image.png
那么明白了这个事件能够得到ActivityManager之后,我们接着回到attach当中继续看下去,这个时候会发现,我们调用了一个attachApplication方法,这个方法又是干嘛的?attachApplication在这里的作用其实实际上是ActivityThread通过attach获取到,然后将applicationThread将其关联,把activity相关信息存储到applicationThread里面,applicationThread的类为activity的各种状态做了相应的准备工作:
image.png
这个时候我们需要关注,ApplicationThread当中做了什么?
当我们打开ApplicationThread中我们会看到一堆的schedle方法,这些方法的名称其实就可以给我们表明,代表的是在执行Activity的某种状态时调用的计划执行方法。
这时我们会看到一个scheduleLaunchActivity方法,表示计划加载时调用的。
这里我们发现了一个很有意思的事情:
image.png
image.png
这个上面我们会看到一个ActivityClientRecord对象,这个对象其实实际上就是我们的Activity,而且似乎每一个方法还干了一件让我们非常熟悉的一件事,进行了一次sendMessage()将当前创建的Activity发送了出去:
image.png
当走到这里我们会发现最终我们调用的是Handler的消息通信机制,也就是说,在这里我们可以总结一下:
当Activity状态改变是,都会有对应的一个消息发送出去,而接收这里,我们能发现通过发送不同的状态,这里调用了不同的HandlerXXXActivity方法:
image.png
在这里,我们貌似发现了Activity的生米周期的调用痕迹,那么其实到此为止,我们可以得出一个结论,Application运行的过程当中,对于Activity的操作,状态改变,其实实际上是通过Handler消息机制来完成的,Application当中只管去发,由消息机制负责调用,因为在main方法当中我们的Looper轮循器是一直在进行轮循的,而当我们在加载Activity的时候,当中调用了一个performLaunchActivity()方法,在这个中间我发现了onCreate的调用痕迹:
image.png
也就是说,当目前为止我们能够明白,整个Application加载Activity的整套流程是怎么回事,那么接下来我们需要关注的是,在onCreate当中我们所写的setContentView到底干了什么。
2.setContentView
在onCreate当中,我们往往会使用setContentView去进行设置我们自己的布局文件或者view,那么在这当中他到底是怎么做的?通过观察源码,这个时候通过一系列线索我找到了最终的位置PhoneWindow类:
这个时候我们会看到他做了两件事情,一个是installDecor,另一个是inflate,这两个后一个不难猜出他是在进行布局文件的解析,前面的我们认为她是在初始化某个东西:
进来之后发现他初始化了两个东西,一个叫做mDecor,一个叫做mContentParent:
我们看到了mDecor是一个DecorView;
mContentParent是一个ViewGroup
透过注释的翻译,其实我们就能明确知道这两个是用来干嘛的
//This is the view in which the window contents are placed, It is either(这是窗口内容放置的视图)
//mDecor itSelf , or a child of mDecor where the contents go.(它那么是mDecor本身,要么是mDecor的子类的内容)
//This is the top-level view of the window, containing the window decor.(这是在窗口当中的顶层View,包含窗口的decor)
mDecor代表是顶层view,mContentParent用来装他下面的视图内容。
在接着往下看的时候,我们发现,generateLayout方法中,发现了此处进行了大量的requestFeature的调用,也就是说,我们的requestFeature设置其实是在setContentView方法当中就开始了,这也是为什么我们要自己去getWindow.requestFeature时必须在setContentView之前的原因:
然后在下面,我们会发现在做了一件事情:
当前这里竟然在加载布局文件,并且生成了一个view,但是好像貌似不是我们自己的,所以我们需要去探寻他到底加载了一个什么东东?
这是我找到的一个比较有意思的组件,
在这个上面我们看到了一句这样的注释
//This is an optimized layout for a screen, with the minimum set of features enabled.
这是一个屏幕的优化布局,具有最小的特征及启用
通过注释和一些资料分析,得到一个比较坑的结果。
这是DecorView默认的一个渲染,然后我们自己的布局都是渲染到她的FrameLayout上的,那么在这里我们现在能够明白,installDector其实实际上是在初始化两个视图容器,然后加载系统的R资源及特征,产生了一个基本的布局
那么接着回到之前我们关注的另外一个方法mLayoutInflate.inflate(layoutResID, mContentParent);这个方法就比较好理解了:
这个方法就是加载我们自己的资源。
那么现在我们就能够明白,setContentView其实做了两件比较核心的事情,就是加载环境配置,和自己的布局,那么接下来我们需要考虑的事情就是,它到底是怎么画到界面上的。
3.UI是如何绘制的?
通过前面两个章节,我们了解到,程序对于activity生命周期的调用,以及我们的视图资源的由来,这时我们需要找到的是绘制的起点在哪?
DecorView添加到窗口Window的过程
在ActivityThread启动时,我发现在加载handleLaunchActivity方法调用performLaunchActivity方法后,又调用了一个handleResumeActivity,在这里我发现了绘制流程的开始
绘制流程入口
通过前面的流程,我们知道,onCreate完成之后,所有资源交给WindowManager保管,在这里,将我们的View交给了WindowManager,此处调用了addView
ViewRootImpl的addView 三个容器
进入addView之后,我们发现了一段这样的代码,他将视图和参数还有我们的一个ViewRoot对象都用了容器装了起来,那么在此处我们可以得出,是将所有相关对象保存起来
mViews保存的是View对象,DecorView
mRoots保存和顶层View关联的ViewRootImpl对象
mParams保存的是创建顶层View的layout参数。
而WindowManagerGlobal类也负责和WMS通信
而在此时,有一句关键代码root.setView,这里是将我们的参数,和视图同时交给了ViewRoot,那么这个时候我们来看下ViewRoot当中的setView干了什么
在这里我们会看到view.assignParent的设置是this,那么也就是说在view当中的parent其实实际上是ViewRoot。
那么在setContentView当中调用了一个setLayoutParams()是调用的ViewRoot的,
而在ViewRoot当中发现了setLayoutParams和performLayout对requestLayout方法的调用,
在requestLayout当中发现了对scheduleTraversals方法的调用而scheduleTraversals当中调用了doTraversal的访问,最终访问到了performTraversals(),而在这个里面,我发现了整体的绘制流程的调用。
当前这里依次是调用了:
performMeasure
performLayout
performDraw
UI绘制先会去测量布局,然后再进行布局的摆放,当所有的布局测量摆放完毕之后,进行绘制。
至此整体的UI绘制过程我们就已经非常清楚了。
我们可以根据这种绘制流程来操作自己的自定义组件。
网友评论