问题1:一直搞不清楚从activity的setContentView到加载我们自己定义的xml布局,这期间加载了几个view?嵌套了多少层级?每一层都是是谁?
思考:一直听说顶层view是一个DecorView,那第二层是哪个?
首先解决的问题是DecorView是怎么被创建的。
先看看Activity的setContentView()调用的是getWindow().setContentView(layoutResID);
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
getWindow()返回的是一个 Window对象,Window是一个抽象类,他的子类只有一个就是PhoneWindow。
从而可知getWindow().setContentView(layoutResID);调用的是PhoneWindow的setContentView(layoutResID)。
public void setContentView(int layoutResID) {
if (mContentParent ==null) {
installDecor();//这里进行了DecorView的创建
}
//下面是把我们自定义的xml布局加载到mContentParent中。mContentParent是在installDecor()中创建
mLayoutInflater.inflate(layoutResID,mContentParent);
}
接下来我们看看installDecor()方法做了哪些事。下面是裁剪的部分代码。
private void installDecor() {
if (mDecor ==null) {
mDecor = generateDecor(-1);//这里创建的DecorView
|
if (mContentParent ==null) {
mContentParent = generateLayout(mDecor);//这里创建的mContentParent.
}
继续看generateDecor(-1)这个方法做了什么?
protected DecorView generateDecor(int featureId) {
return new DecorView(context, featureId,this, getAttributes());//直接创建一个DecorView并返回。
}
得到mDecor之后,看看mContentParent怎么创建的。看 generateLayout(mDecor)方法。
protected ViewGroup generateLayout(DecorView decor) {
//这里省略了一车对于不同的flags和Feature的判断,做这些判断的目的是要确定接下来要加载的是哪种系统预制的布局。
int layoutResource;//这个layoutResource是系统已经预制有一些布局了,比如有一些有actionBar的有一些没有的。
//得到layoutResource之后会调用下面这个方法去把布局加载出来,并addView进mDecor中。
mDecor.onResourcesLoaded(mLayoutInflater,layoutResource);
//layoutResource加载出来的view中查找contentParent (ID_ANDROID_CONTENT)ID_ANDROID_CONTENT =com.android.internal.R.id.content;
//这个R.id.content对应的是一个id为content的FrameLayout。由此可得contentParent 是一个FrameLayout
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
return contentParent;
}
onResourcesLoaded(mLayoutInflater,layoutResource);的实现:
void onResourcesLoaded(LayoutInflater inflater,int layoutResource) {
mDecorCaptionView = createDecorCaptionView(inflater);
final View root = inflater.inflate(layoutResource,null);//第二层View的加载
if (mDecorCaptionView !=null) {
if (mDecorCaptionView.getParent() ==null) {
addView(mDecorCaptionView,
new ViewGroup.LayoutParams(MATCH_PARENT,MATCH_PARENT));
}
mDecorCaptionView.addView(root,
new ViewGroup.MarginLayoutParams(MATCH_PARENT,MATCH_PARENT));
}else {
// Put it below the color views.
addView(root,0,new ViewGroup.LayoutParams(MATCH_PARENT,MATCH_PARENT));//把加载出来的xml布局添加到DecorView中。
}
mContentRoot = (ViewGroup)root;
}
从上可知,layoutResource加载出来的布局,作为DecorView的子View。至此,可以得出,第一层View为DecorView,第二层View为(layoutResource加载出来的布局)root。
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);//是从root中查找得到的。
所以第三层View为contentParent 即mContentParent =contentParent 。
再由PhoneView.java的setContentView中的 mLayoutInflater.inflate(layoutResID,mContentParent);这句是把我们自定义的xml文件加载的布局添加到mContentParent中。可以知道我们传入的layoutResID对应的xml布局为第四层View。
总结:setContentView第一步先加载DecorView,第二步再加载系统预置的模板布局root(一个LinearLayout),第三步从预置的模板布局中加载mContentParent(一个FrameLayout),第四步把setContentView传入的View或者layoutResID(我们在res中自己定义的xml布局)加载出来的布局添加到mContentParent中。
至此所有的view都加载到了DecorView中了。但是到这还没有显示到屏幕上。
下面是把这些加载到DecorView里面的View绘制到屏幕上的思考。
问题2:,既然view都全部添加到DecorView中了,那怎么说还没绘制到屏幕上呢?我一直以为addView之后就算把View绘制完成了。
view是什么时候开始测量、布局、绘制的? 由于android view的刷新机制16.67ms刷新一次,这个16.67ms依赖于vsync垂直同步信号,当接收到vsync来的时候才开始刷新有变化的view。如果要接收这个vsync信号,就要先注册到发出vsync信号的管理列表中,告诉vsync信号发出的地方说,我这里可以接收vsync信号,vsync 16.67ms时间到之后会通过回调的方式通知接收方。这个注册方式是在ViewRootImpl.java里面scheduleTraversals()方法注册的。vsync发出的地方frameworks\native\services\surfaceflinger\surfaceflinger.cpp中
网上都说view的绘制流程是从activity的onResume()开始的。所以跟踪activity的onResume调用。Activity的onResume()方法是从ActivityThread.java的handleResumeActivity()方法开始的:
public void handleResumeActivity(IBinder token,boolean finalStateRequest,boolean isForward,
String reason) {
View decor =r.window.getDecorView();//通过Window对象获取到顶层的DecorView
ViewManager wm =a.getWindowManager();//获取到WindowManager
WindowManager.LayoutParams l =r.window.getAttributes();//获取到LayoutParams
l.type =WindowManager.LayoutParams.TYPE_BASE_APPLICATION;//设置type的值,基础的窗口,其他窗口都显示在它之上。
if (!a.mWindowAdded) {
a.mWindowAdded =true;
wm.addView(decor,l);//如果没有添加过就把view添加到windowManager.。
}
}
解析:
1.ViewManager 是一个接口里面有三个方法 addView,updateViewLayout,removeView,它有一个子类 WindowManager 这个也是一个接口,它继承了ViewManager 。a.getWindowManager()其实就是拿到了WindowManager 的实现类。WindowManagerImpl.java
2.wm.addView(decor,l)实际调用是在WindowManagerImpl.java中。
3.WindowManagerImpl.addview调用的是 mGlobal.addView ,WindowManagerGlobal →mGlobal。
4.mGlobal.addView → root.setView(view,wparams, panelParentView, userId); root是ViewRootImpl.java
5.setView()里面有一个重要的方法就是 requestLayout(); 然后requestLayout()→scheduleTraversals();至此view就做好了更新的准备了,等到vsync信号来了就可以进行更新了。
顺序:ActivityThread.handleResumeActivity() → ViewManager.addView() → WindowManagerImpl.addView()→WindowManagerGlobal .addView() → ViewRootImpl.setView() → ViewRootImpl.requestLayout()→ ViewRootImpl.scheduleTraversals()。
Vsync信号来了之后的时序:ViewRootImpl.doTraversal() → ViewRootImpl.performTraversals() → performMeasure() → performLayout() →performDraw() → draw()→ drawSoftware() → mView.draw(Canvas canvas) 结束。
思考:draw方法的入参canvas是哪里来的?ViewRootImpl. drawSoftware()中有一句 canvas =mSurface.lockCanvas(dirty);就是从这里来的,所以平时我们自定义View使用的canvas 就是这个mSurface提供的,我们可以在上面画自己想画的东西。至此View就显示到屏幕上了。
问题3,子线程更新UI
这种更新view的ui需要依赖vsync信号,有16.67毫秒的限制,而且是在主线程更新的View的UI。如果想在子线程做UI的绘制,可以使用SurfaceView或者TextrueView,他们两个提供了在子线程绘制UI的能力,因为他们可以通过SurfaceHolder拿到Surface 进而可以使用surface.lockCanvas()取到Canvas画布,我们只要在Canvas画布上画图就可以直接更新了,不用等vsync信号。