UI绘制流程分析(源码级分析)

来源:互联网 发布:2016年网络电影票房 编辑:程序博客网 时间:2024/06/05 06:36
一、从setContenView开始,了解view的加载过程
疑问:setContentView到底做了些什么,为什么调用后就可以显示出我们想要的布局页面?
(1)setContentView -> getWindow().setContentView()的方法;(Window显示顶层,抽象类,包括一些行为的封装,如setContentView、dispatch...,window的实现类是需要添加到windowManager里面来的,window提供标准UI背景、标题区域、默认key事件处理等。window仅有一个实现类-phoneWindow)

(2)phoneWindow的setContentView 方法
首先对mContentParent判null,(mContentParent是ViewGroup容器,Activity要显示的内容要放到这里面来,要么是DecorView本身,要么是DecorView的Child)
DecorView是Window的根节点,它继承FrameLayout;

紧接着,对PhoneWindow的setContentView方法往下看,当mContentParent不为null(并且!hasFeature(FEATURE_CONTENT_TRANSITIONS)时移除mContentView的所有View;执行一些动画(如Activity的转场动画);然后,mLayoutInflater.inflate(layoutResID,mContentParent),将layout添加到mContentParent容器中来(inflater里面做了什么样的操作?为什么xml生成对应的View)

返回来,查看mContentParent(DecorView或其孩子)为空时的初始化操作:installDecor()方法:
(1)先new Decor,然后generateLayout(mDecor)并且返回值赋给mContentParent;
generateLayout方法执行内容:
首先拿mWindowStyle(com.android.internal.R.styleable.Window),我们在xml中设置一些window的style属性,都会在这个方法中加载进来。其次,查看window是否是windowFloating的(浮窗类型的,如dialog);然后,style是否是requestFeature(FEATURE_NO_TITLE)方法,对Feature的一些状态位进行设置;紧接着,根据不同的Feature去加载不同的DecorView的xml布局:
如一个简单的xml布局:screen_simple.xml
xml中的id为content是显示真正布局的地方。然后,
mDecor.startChanging();
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);加载DecorView的布局,同时
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
(ID_ANDROID_CONTENT就是xml中对应的id:content)
最终,将这个contentParent 返回,也就是说mContentParent最终代表的是DecorView中的一个id为content的FrameLayout。

Activity加载UI-类图关系和视图结构:
11


PhoneWindow是什么东西?window和它是什么关系?
每一个Activity都有一个显示UI的窗口,phoneWindow是window的唯一实现类。


DecorView是干什么用的?和我们的布局又有什么关系?
窗口底层的一个View;我们的布局其实是加载到DecorView下面的一个FrameLayout中去的。

RequestFeature为什么要在setContentView之前调用?
requestFeature后,加载windowDecor时候,要去拿requestFeature设置的Feature属性,根据拿到的属性,加载不同的Decor布局的xml;即在setContentView的时候,就要根据Feature属性,去拿不同的布局文件


二、LayoutInflater到底怎么把xml添加到DecorView里面来?
include为什么不能作为xml资源布局的根节点?
merge为什么可以作为xml资源布局的根节点?

从上面的mLayoutInflater.inflate(layoutResID,mContentParent)开始,查看inflate方法,它调用的是另外的inflate方法:
11
该方法去拿xml解析器:XmlResourceParser;再去调用重载的inflate(XmlPullParser parser, @Nullable ViewGroup root,booleanattachToRoot) 方法:会先拿xml的属性,然后讲传进来的contentParent赋值给result
11
找到根节点,首先进行判断:是否是merge标签,(root 为空,并且attachToRoot为false时抛出异常:merge必须要添加到ViewGroup里面的)
当是常见节点时,首先会createViewFromTag生成一个根节点;再调用root(id为content的FrameLayout)的generateLayoutParams方法把对应的属性解析出来,设置到对应的child上来。
解析完根节点之后,继续解析里面的child
// Inflate all children under temp against its context.
rInflateChildren(parser, temp, attrs, true);
rInflateChildren还是调用的rInflate方法(为merge的时候也会调用),rInflate方法在解析merge时finisihInflate参数传false,非merge时传true,最终该参数控制是否执行parent.onFinishInflate方法。rInflate方法是一个遍历解析的过程,在解析的过程中发现如果include标签里面getDepth() == 0时抛出异常:
if(TAG_INCLUDE.equals(name)) {
if(parser.getDepth() ==0) {
throw newInflateException("<include /> cannot be the root element");
}
parseInclude(parser, context, parent, attrs);
}
如果解析到merge时,会抛出merge必须为根节点的异常
if(TAG_MERGE.equals(name)) {
throw newInflateException("<merge /> must be the root element");
}
这就回答了刚开始提出的两个问题

最后,他是ViewGroup是,再去遍历出来,添加到ViewGroup中去,(这就是深度优先遍历ViewTree的过程):
else{
finalView view = createViewFromTag(parent, name, context, attrs);
finalViewGroup viewGroup = (ViewGroup) parent;
finalViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
rInflateChildren(parser, view, attrs,true);
viewGroup.addView(view, params);
}

layout的解析过程:
11

三 小结:
每一个Activity都关联了一个Window对象,这个window是用来描述应用程序的窗体口;每一个应用程序的窗口又包含了一个DecorView,DecorView用来加载View的视图--xml布局。


上述是创建DecorView的过程,那么DecorView如何添加到Window上呢?

Extra:
AppCompatActivity在setContentView时,与在Activity中有什么差别?
其setContentView方法调用的是getDelegate().setContentView(代理:代理了一些封装兼容的类);
mDelegate= AppCompatDelegate.create(this,this)

AppCompatDelegate是一个抽象类,其实现类有很多:

private staticAppCompatDelegate create(Context context, Window window,
AppCompatCallback callback) {
final intsdk = Build.VERSION.SDK_INT;
if(BuildCompat.isAtLeastN()) {
return newAppCompatDelegateImplN(context, window, callback);
}else if(sdk >= 23) {
return newAppCompatDelegateImplV23(context, window, callback);
}else if(sdk >= 14) {
return newAppCompatDelegateImplV14(context, window, callback);
}else if(sdk >= 11) {
return newAppCompatDelegateImplV11(context, window, callback);
}else{
return newAppCompatDelegateImplV9(context, window, callback);
}
}
AppCompatDelegateImplV9继承了其setContentView方法:
@Override
public void setContentView(View v) {
ensureSubDecor();
ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
contentParent.removeAllViews();
contentParent.addView(v);
mOriginalWindowCallback.onContentChanged();
}

SubDecor与Decor本质上是一样的,只是做了兼容;
final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById(
R.id.action_bar_activity_content);

final ViewGroup windowContentView = (ViewGroup) mWindow.findViewById(android.R.id.content);
if (windowContentView != null) {
// There might be Views already added to the Window's content view so we need to
// migrate them to our content view
while (windowContentView.getChildCount() > 0) {
final View child = windowContentView.getChildAt(0);
windowContentView.removeViewAt(0);
contentView.addView(child);
}

// Change our content FrameLayout to use the android.R.id.content id.
// Useful for fragments.
windowContentView.setId(View.NO_ID);
contentView.setId(android.R.id.content);
//做了替换,为了做兼容,封装了过程,对外提供了统一的id(content)

// The decorContent may have a foreground drawable set (windowContentOverlay).
// Remove this as we handle it ourselves
if (windowContentView instanceof FrameLayout) {
((FrameLayout) windowContentView).setForeground(null);
}
}
11
Activity的View布局结构_HierarchyView:
11

四 DecorView如何添加到Window
要知道DecorView的添加,得先知道Activity的启动过程;Activity通过ActivityThread启动;
->handleLaunchActivity
-> performLaunchActivity:通过反射拿到Activity,调用Activity的attach方法;phonewindow在Activity的attach方法里面进行初始化的,初始化完成后调用callActivityOnCreate->activity.performCreate;Activity的生命周期执行过程、调用顺序都是在ActivityThread中依次进行的;
->handleResumeActivity
->获取window:r.window = r.acitivity.getWindow(),又拿到Decor:View decor = r.window.getDecorView();拿到windowManger:ViewManger wm = a.getWindowManger();将Decor添加到windowManger中来(addView),windowManger是一个实现类,其实现类windowMangerImple的addView方法调用的又是WindowMangerGlobal的addView方法—> ViewRootImpl的setView方法,传入DecorView,调用了他的requestLayout方法,然后将DecorView
添加显示出来:
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mInputChannel);

同时,继续向下执行,又调用了一个关键方法:
view.assignParent(this);//view是DecorView,给view设置parent,ViewRootImpl将自己设置为DecorView的parent,这也就是为什么View再用requestLayout方法的时候最终会走到ViewRootImpl的requestLayout

DecorView添加至窗口的过程:
11


View的requestLayout方法会发起重新绘制?
View的requestLayout会一直调用parent的requestLayout,最终调用到DecorView的requestLayout方法,而DecorView的parent是ViewRootImpl(ViewRootImpl的setView中,通过调用 view.assignParent(this)设置),所有最终调用ViewRootImpl的requestLayout发起绘制流程:scheduleTraversals()->runnable->performTraversals:UI的遍历绘制
1 0
原创粉丝点击