本文是依据Android 8.1.0的相关源码进行分析。
一、setContentView的如何加载布局?
通常Activity的onCreate里面都需要写上setContentView,那么就从Activity入手,找到setContentView,如下:
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
从上面可以看出,调用的是Window的setContentView方法,这个Window是一个抽象类,他是子在attach方法中被创建的:
mWindow = new PhoneWindow(this, window, activityConfigCallback);
可以看出,PhoneWindow是Window的实现类,这里可以明确出他是Window唯一实现类,找到实现类的setContentView方法:
@Override
public void setContentView(int layoutResID) {
if (mContentParent == null) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
} else {
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
如果mContentParent是否为空,则执行installDecor,移除了一些不重要的方法:
private void installDecor() {
//创建DecorView,将DecorView添加至Window
if (mDecor == null) {
mDecor = generateDecor(-1);
} else {
mDecor.setWindow(this);
}
//创建mContentParent,并且添加至DecorView
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
}
}
创建DecorView:
protected DecorView generateDecor(int featureId) {
return new DecorView(context, featureId, this, getAttributes());
}
这里需要知道的是DecorView继承自FrameLayout布局。
创建ContentParent,代码很长,只保留核心内容:
protected ViewGroup generateLayout(DecorView decor) {
//1.获取layoutparams
WindowManager.LayoutParams params = getAttributes();
//2.加载根布局
if (根据设置主题加载对应的xml,很多逻辑判断) {
layoutResource = R.layout.screen_simple;
}
//3.设置DecorView变化监听
mDecor.startChanging();
//4.将根布局加载到DecorView中
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
//5.在Window中找到id ID_ANDROID_CONTENT,即下面的FrameLayout
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
return contentParent;
}
在上面第二步中加载的根布局如下,其中FrameLayout就是设置setContentView的根布局,后面会提到,ViewStub是与actionbar相关属性。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:orientation="vertical">
<ViewStub android:id="@+id/action_mode_bar_stub"
android:inflatedId="@+id/action_mode_bar"
android:layout="@layout/action_mode_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="?attr/actionBarTheme" />
<FrameLayout
android:id="@android:id/content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:foregroundInsidePadding="false"
android:foregroundGravity="fill_horizontal|top"
android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>
在上面的WindowPhone中有段如下代码:
mLayoutInflater.inflate(layoutResID, mContentParent);
layoutResID就是setContentView的xml布局mContentParent就是上面创建的contentParent,这行代码就是将xml父布局FrameLayout中。
总结下:
为了验证上面的情况新建一个项目打开Android Studio 新建一个项目,XML页面如下:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.demo.myapplication.MainActivity">
<TextView
android:layout_width="match_parent"
android:layout_height="200dp"
android:text="啦啦啦"/>
</RelativeLayout>
这是运行项目,打开Android Device Monitor——>hierarchy viewer:
这时候我们可以更新清晰直观的了看到整个页面View的结构图:
二、DecorView如何添加View?
DecorView是通过Window的setContentView的mDecor.setWindow(this);添加至Window中,布局的细节后面在讲。这里主要说的是上面的布局文件(R.layout.screen_simple)如何加到DecorView中。在上面的generateLayout方法中第四个备注的onResourcesLoaded方法就是加载的View布局,在DecorView找到对应的方法:
void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
mDecorCaptionView = createDecorCaptionView(inflater);
final View root = inflater.inflate(layoutResource, null);
if (mDecorCaptionView != null) {
} else {
//直接添加到DecorView中
addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
mContentRoot = (ViewGroup) root;
initializeElevation();
}
使用addView直接DecorView中,DecorView继承自FrameLayout,addView是ViewGroup中的方法,如下核心代码:
public void addView(View child, int index, LayoutParams params) {
//请求View重新绘制
requestLayout();
//刷新UI
invalidate(true);
//添加View动画和监听相关
addViewInner(child, index, params, false);
}
addView的详细部分下面再说明,现在对上面一、二部分进行一个大总结:
三、DecorView如何显示View?
这里按照上面的三步进行分析。
1、请求View重新绘制(requestLayout)
public void requestLayout() {
if (mMeasureCache != null) mMeasureCache.clear();
if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {
//获取ViewRootImpl,该对象是在内部类AttachInfo中被实例化
ViewRootImpl viewRoot = getViewRootImpl();
if (viewRoot != null && viewRoot.isInLayout()) {
if (!viewRoot.requestLayoutDuringLayout(this)) {
return;
}
}
mAttachInfo.mViewRequestingLayout = this;
}
mPrivateFlags |= PFLAG_FORCE_LAYOUT;
mPrivateFlags |= PFLAG_INVALIDATED;
if (mParent != null && !mParent.isLayoutRequested()) {
mParent.requestLayout();
}
if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
mAttachInfo.mViewRequestingLayout = null;
}
}
这里判断当前View是否正在进行布局流程,并且设置标志位,该标志位用于标记当前的View是否需要重新布局,接着调用 mParent.requestLayout();方法,进行该View的父布局的布局请求,层层递归,直到最顶层的DecorView,而DecorView又会把事件传递给ViewRootImpl,这是一种责任链设计模式,即不断向上传递该事件,直到找到能处理该事件的上级,这里的最终上级就是ViewRootImpl。DecorView和ViewRootImpl的关系是ViewRootImpl是实现View的绘制的类,里面有三个方法是关键,DecorView 是一个顶层 View,是应用的整个界面。ViewRootImpl中找到对应的方法:
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
//检查是否在主线程
checkThread();
//设置标识位
mLayoutRequested = true;
//该方法如下
scheduleTraversals();
}
}
在这里,调用了scheduleTraversals方法,这个方法是一个异步方法,最终会调用到ViewRootImpl的performTraversals方法,这也是View工作流程的核心方法,在这个方法内部,分别调用measure、layout、draw来进行View的三大工作流程。详情如下:
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
这里重点关注mTraversalRunnable这个类,他implements Runnable这个类,执行doTraversal()方法,在该方法内部有调用了performTraversals()方法:
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
performTraversals代码十分长,核心方法如下:
private void performTraversals() {
performMeasure() ——> mView.measure
performLayout() ——> mView.layout
performDraw() ——> draw ——> drawSoftware ——> mView.draw ——> mView.draw(canvas);
}
如下图:
总结:
requestLayout的流程如下:
requestLayout ——> scheduleTraversals ——> mTraversalRunnable ——> doTraversal ——> performTraversals ——> performMeasure ——> performLayout ——> performDraw
2、刷新UI(invalidate)
public void invalidate(boolean invalidateCache) {
invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
}
invalidateInternal核心代码如下,删除了其余代码:
void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
boolean fullInvalidate) {
final ViewParent p = mParent;
if (p != null && ai != null && l < r && t < b) {
//向父View进行进行刷新
p.invalidateChild(this, damage);
}
}
}
由此可知,View的invalidate方法实质是将要刷新区域直接传递给了【父ViewGroup的invalidateChild方法】,这是一个从当前View向上级父View回溯的过程。找到ViewGroup的invalidateChild方法:
public final void invalidateChild(View child, final Rect dirty) {
ViewParent parent = this;
do {
//循环层层上级调用,直到ViewRootImpl会返回null
parent = parent.invalidateChildInParent(location, dirty);
} while (parent != null);
}
这里会层层循环,直到parent为null就会跳出该循环,什么情况下为null呢?遍历到最顶层ViewGroup即ViewRootImpl,他最终返回null,如下:
@Override
public void invalidateChild(View child, Rect dirty) {
invalidateChildInParent(null, dirty);
}
@Override
public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
if (dirty == null) {
invalidate();
return null;
} else if (dirty.isEmpty() && !mIsAnimating) {
return null;
}
//内部会调用scheduleTraversals();
invalidateRectOnScreen(dirty);
return null;
}
invalidateRectOnScreen内部会调用scheduleTraversals(),最终调用performTraversals执行重绘,上面分析有。所以,View调用invalidate方法的实质是:层层上传到父级,直到传递到ViewRootImpl后会触发scheduleTraversals方法,然后整个View树就开始重新按照View的绘制流程进行重绘任务。在invalidate的过程中,如果View的大小没有发生改变,就不会执行Draw方法,如果是在子线程中请求绘制刷新UI需要使用postInvalidate方法。
3、添加View相关属性(addViewInner)
addViewInner是addView的最后一步,主要设置了一些回调和动画、焦点、监听等。
private void addViewInner(View child, int index, LayoutParams params,
boolean preventRequestLayout) {
//将View添加到数组中,获取mChildrenCount
addInArray(child, index);
//将View添加到Window上
child.dispatchAttachedToWindow(mAttachInfo, (mViewFlags&VISIBILITY_MASK));
//当有View被添加进来时候,会回调onViewAdded方法,可用于View添加监听
dispatchViewAdded(child);
}
这样经过以上三步就玩成了View的添加显示,整个加载显示过程流程如下:
下一篇将分析View具体的绘制过程,也就是performTraversals的具体流程。