Android中View可以说是最为重要的几个地方之一,包括事件分发,测量,绘制等等,都是非常常见的情况。那么我们要想好好掌握这些知识,就得深入了解Andorid整个View从开始到完成所经历的一系列工作。本文分析的源代码均来自Android API 24。
在Android中,Activity并不负责视图控制,它只是控制生命周期和处理事件,真正控制视图的是Window。一个Activity包含了一个Window,Window才是真正代表一个窗口,也就是说Activity可以没有Window,那就和Service没多大差别了。
Window第一次出现在Activity中是在ActivityThread调用Activity的attach()方法时,通过PolicyManager创建window,实现callback方法,所以,当window接收到
外界状态改变时,会调用activity的方法:
final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, int ident,
Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
Configuration config, String referrer, IVoiceInteractor voiceInteractor) {
....
mWindow = new PhoneWindow(this, window); //在这里直接new一个Window
mWindow.setCallback(this);//设置回调接口,当window接收系统发送给它的例如键盘和触摸屏事件,就可以通知Activity,Activity做出相应的处理
.....
//设置窗口管理器
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
.....
}
在attach()中,Activity新建一个PhoneWindow实例作为成员变量,这是Window的唯一一个子类。然后设置mWindow的WindowManager。
首先简单介绍一下DecorView,这里引用任玉刚大神的《Android开发艺术探索》书中的介绍:它是一个顶级View,内部会包含一个竖直方向的LinearLayout,这个LinearLayout有上下两部分,分为titlebar和contentParent两个子元素,contentParent的id是content,而我们自定义的Activity的布局就是contentParent里面的一个子元素。View层的所有事件都要先经过DecorView后才传递给我们的View。那么DecorView是何时出现的呢??又是如何和window产生联系呢??
首先,如上图我们可以看到Activity中包含了Window,DecorView,WindowManager等成员变量,那么我们就从Activity的创建过程中去逐步寻找它们。
首先在Activity中的onCreate方法中我们都会写一句setContentView的方法调用,将我们自定义的Activity布局传入。那么我们跟进该方法去看一下具体实现。
通过getWindow来获取成员变量mWindow。
然后调用window的setContentView()方法。该方法来进行我们Activity的布局设置。然后又接着调用了initWindowDecorActionBar()方法来进行对ActionBar的新建和初始化。我们暂时不管该方法。
mWindow类型是Window,是一个接口,而在安卓中实现Window接口的实现类只有PhoneWindow,所以进入PhoneWindow查看setContentView()方法的具体实现。先上源代码:
首先会在上图标记1处判断mContentParent是否为空,如果为空,会执行installDecor()方法,那么mContentParent是什么呢?
通过定义可以看出,mContentParent是一个ViewGroup类型的变量,它是Activity的一个成员变量。我们所写的setContetView()所设置的布局文件就加到这个视图中。根据官方解释它可能是mDecor自身,也可能是mDecor的子View。
继续回到setContentView中看代码,如果mContentParent为空,那么会执行installDecor()方法,跟进这个方法,顾名思义,新建DecorView。下面给出源代码:
在installDecor方法中标注1处,如果mDecor为空(mDecor是Window持有的一个成员变量,指的就是DecorView),那么在 generateDecor()中去实例化新建DecorView,我们继续跟进generateDecor()查看源代码:
在这里前面一段代码都是初始化所需要的context,最后返回一个实例化的DecorView()。在DecorView构造方法中进行初始化工作。
上图是DecorView的一些初始化工作,就不再展开,有兴趣的同志们可以研究。
返回到installDecor()方法中继续往下,到标记处2,会判断mContentParent是否为空,如果为空,通过 generateLayout()来将我们具体的布局加载到DecorView中。跟入 generateLayout()方法查看源代码(由于方法中代码过长不便于截图,我贴出关键代码):
...//代码前面都是在获取主题相关
//在这个方法里将mContentParent的布局转换并添加到DecorView中
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);//具体代码在下一个代码框中
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);// contentParent就是root的父布局。
return contentParent;
* * *
final View root = inflater.inflate(layoutResource, null);//将mContentParent布局转换为View
mDecor.addView(root,new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT));//将mContentParent加入到DecorView中
mContentRoot = (ViewGroup) root;//mContentRoot就是mContentParent视图
至此generateLayout()方法分析完毕,返回contentParent变量,赋值给mContentParent成员变量。
随后上述步骤也是installDecor()方法的主要代码,因此继续返回到setContentView方法继续查看(把setContentView的图继续贴下来避免返回去翻图):
图中标注处2的代码,则是将我们的自定义布局加入到mContentParent(也就是id为R.id.content)布局中。
最后标记处3则是通过回调来通知ActivityContent已经发生了变化,由Activity来做出相应的处理。
至此已经setContentView已经完成,DecorView也已经新建,我们自定义布局也加入到mContentParent布局中,但是此时mDecorView还没有被WindowManager正式添加到Window中。因此Window此时还无法接受外界的信息,也无法提供具体的功能。
所以在activity中的onResume()方法中调用了makeVisible()方法来进行添加,同时也是把Window加入到WindowManager中
void makeVisible() {
if (!mWindowAdded) {
ViewManager wm = getWindowManager(); //获得WindowManager(WindowManager extends ViewManager)
wm.addView(mDecor, getWindow().getAttributes()); //将DecorView加入到Window中
mWindowAdded = true;
}
mDecor.setVisibility(View.VISIBLE);
}
至此整个decorView的加载过程和Activity的Window创建过程已经完成,Decor将会显示出来呈现在手机屏幕上.
ps:由于是大二萌新第一次写东西,所以很多地方还很模糊,希望前辈们多多指点错误,也希望能继续加油记录自己的点点滴滴
再ps:本文参考和学习了任玉刚大神的《Android开发艺术探索》和陈育大神的文章(http://www.jianshu.com/p/687010ccad66)