在上一篇探究Android View 绘制流程,Xml 文件到 View 对象的转换过程我们了解了setContentView(resId)
如何把 xml 文件转换成 Java 中的 View 对象。本篇文章在此基础上继续探究,View 是如何展示到 Activity 上的。
很多 Android 开发者都知道一个事情
当 Activity 执行 onResume() 方法后,代表 Activity 显示到前台
这句话很短,但是背后隐藏了多少方法的调用呢?下面我们将一层一层的剥开源码寻找真相。
先从 setContentView(resId) 入手
先说明一下,从 Android 的 Launcher 上点击应用的 Icon 的启动过程比较复杂,本人仍在学习。如果想了解如何启动一个 Activity 的过程可以参考Android Launcher 启动 Activity 的工作过程,这里我们只从关注 Activity 中的 View 显示出来。所以直接从 Activity 的一些方法入手。
在 Activity 的 onCreate(savedInstanceState)
中调用 setContentView(resId)
,而setContentView(resId)
则会调用 PhoneWindow.setContentView(layoutResID)
源码并不是太长
@Overridepublic void setContentView(int layoutResID) { // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window // decor, when theme attributes and the like are crystalized. Do not check the feature // before this happens. 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(); } }
这里忽略转场动画
和一些回调相关的逻辑代码后如下
if (mContentParent == null) { installDecor(); } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) { mContentParent.removeAllViews(); } mLayoutInflater.inflate(layoutResID, mContentParent); mContentParent.requestApplyInsets();
其中 mContentParent 是一个 ViewGroup
引用
private ViewGroup mContentParent;
这样开代码比较简单明了
1. 判断 mContentParent 是否为空,如果为空执行 installDecor()2. 如果 mContentParent 不为空,清除 mContentParent 的所有子 View3. 把传入的布局文件转换为 View 对象添加到 mContentParent
分析 installDecor()
然后我们再看下 installDecor()
,因为源码比较长,我们分成几个部分解读
第一部分
if (mDecor == null) { mDecor = generateDecor(); mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS); mDecor.setIsRootNamespace(true); if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) { mDecor.postOnAnimation(mInvalidatePanelMenuRunnable); } }
这几行代码最重要的是调用了方法 generateDecor()
其实就是创建一个 DecorView
。这里是不是能想到探究Android View 绘制流程,Canvas 的由来中最后的那张图,我们做个类似的截图截个图
public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(new TextView(getApplicationContext())); } @Override protected void onResume() { super.onResume(); } }
我们看到一个 Activity 页面最底层的 View 就是我们刚看到的 DecorView
第二部分
if (mContentParent == null) { mContentParent = generateLayout(mDecor); …… }
这里看到了对 mContentParent
的赋值操作,调用了 generateLayout(mDecor)
protected ViewGroup generateLayout(DecorView decor) { // Apply data from current theme. TypedArray a = getWindowStyle(); //设置 Windows Style ,title 、action_bar 、设置键盘弹出方式之类的属性 //…… //…… int layoutResource; int features = getLocalFeatures(); // System.out.println("Features: 0x" + Integer.toHexString(features)); if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) { layoutResource = R.layout.screen_swipe_dismiss; } else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) { …… } else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0 && (features & (1 << FEATURE_ACTION_BAR)) == 0) { …… } else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) { …… } else if ((features & (1 << FEATURE_NO_TITLE)) == 0) { …… } else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) { …… } else { // Embedded, so no decoration is needed. layoutResource = R.layout.screen_simple; // System.out.println("Simple!"); } mDecor.startChanging(); View in = mLayoutInflater.inflate(layoutResource, null); decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); mContentRoot = (ViewGroup) in; //…… //…… mDecor.finishChanging(); return contentParent; }
这里把 generateLayout(mDecor)
做了很大的简化,大部分都是设置一些窗体属性,软键盘弹出方式之类的东西。我们关心的 View 相关的就以下几行
View in = mLayoutInflater.inflate(layoutResource, null); decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); mContentRoot = (ViewGroup) in; mDecor.finishChanging();
layoutResource
是什么呢?我们随便选择一个 R.layout.screen_simple
在 AndroidSdk 中搜到这个文件,内容如下
<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>
这个时候再回到我们刚的那个截图,我们找到了第二层内容 LinearLayout
的来源,这一层LinearLayout
包含两个部分
1. id 为 action_mode_bar_stub 的 ViewStub ,用来设置 actionBar 之类的2. id 为 android.R.id.content 的 FrameLayout。里面会存放我们在 Activity.setContentView(resId) 传入的文件布局
然后再看下最后 mDecor.finishChanging()
public void finishChanging() { mChanging = false; drawableChanged(); } private void drawableChanged() { if (mChanging) { return; } //…… //…… requestLayout(); invalidate(); //…… //…… }
根据我们对 View 的了解,requestLayout()
和 invalidate()
会引发 View 的重新布局和重新绘制,难道这个时候就绘制 View 了。 这不科学
而事实上,这个真的不科学。此时并不会执行绘制和计算。 原因是此时的 View 还没有和 ViewRootImpl 关联上 。留个悬念,这个我们在后面的章节会讲解。
第三部分
第三部分就是第二部分省略的代码,代码特别长,这里也缩减一下。
final DecorContentParent decorContentParent = (DecorContentParent) mDecor.findViewById( R.id.decor_content_parent); if (decorContentParent != null) { //…… } else { mTitleView = (TextView)findViewById(R.id.title); //…… } if (mDecor.getBackground() == null && mBackgroundFallbackResource != 0) { mDecor.setBackgroundFallback(mBackgroundFallbackResource); } // Only inflate or create a new TransitionManager if the caller hasn't // already set a custom one. if (hasFeature(FEATURE_ACTIVITY_TRANSITIONS)) { //…… }
这里简单的归纳一下代码做的事情
1. 设置 title2. 设置背景色3. 处理 FEATURE_ACTIVITY_TRANSITIONS 属性
requestLayout()
和 invalidate()
源码追踪
requestLayout()
和 invalidate()
的源码都在 View 类里面
先看 requestLayout()
public void requestLayout() { if (mMeasureCache != null) mMeasureCache.clear(); if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) { // Only trigger request-during-layout logic if this is the view requesting it, // not the views in its parent hierarchy 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 会调用 mParent.requestLayout()
。mParent
会是 ViewGroup
吗?我们看下声明变量的地方
protected ViewParent mParent;
然后再搜下mParent
赋值的地方,发现只有一处
void assignParent(ViewParent parent) { if (mParent == null) { mParent = parent; } else if (parent == null) { mParent = null; } else { throw new RuntimeException("view " + this + " being added, but" + " it already has a parent"); } }
那接下来就看 assignParent(parent)
被谁调用了,发现 View
中只有声明,没有调用。所以我们就去 ViewGroup
看看。发现也只有一处调用
private void addViewInner(View child, int index, LayoutParams params, boolean preventRequestLayout) { …… // tell our children if (preventRequestLayout) { child.assignParent(this); } else { child.mParent = this; } …… }
顺着这个方法追溯一下,如下图
这时候我们又疑问了:
DecorView 的 mParent 是谁呢???
答案只有一个,是 NULL
我们刚说了 mDecor.finishChanging()
不会执行绘制和计算相。 原因是此时的 View 还没有和 ViewRootImpl 关联上 。
先看 invalidate()
public void invalidate() { invalidate(true); }public void invalidate(boolean invalidateCache) { invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true); }
void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache, boolean fullInvalidate) { …… if (p != null && ai != null && l < r && t < b) { final Rect damage = ai.mTmpInvalRect; damage.set(l, t, r, b); p.invalidateChild(this, damage); } …… } }
我们又在跟踪 invalidate()
方法时发现了 p.invalidateChild(this, damage)
这里似乎又是一层一层的向上迭代。为了确保,我们去看下 ViewGroup 的 invalidateChild()
public final void invalidateChild(View child, final Rect dirty) { …… ViewParent parent = this; if (attachInfo != null) { …… do { …… parent = parent.invalidateChildInParent(location, dirty); …… } } while (parent != null); } }public ViewParent invalidateChildInParent(final int[] location, final Rect dirty) { if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID)) != 0) { …… return mParent; } return null; }
所以和 requestLayout()
一样层层追溯,又到了 DecorView
中。我们可以准确的说 DecorView
的 mParent
其实是 ViewRootImpl
。但是怎么证明呢???
DecorView
和 ViewRootImpl
的关系
本文开盘就已经说了 当 Activity 执行 onResume() 方法后,代表 Activity 显示到前台,这是为什么呢?
我们都是 Activity
的由 ActivityManager
管理,Activity 页面的操作必须在主线程中,而主线程就是 ActivityThread 。在 ActivityThread 的源码中,找到了一个 H
类,该类继承 Handler
。在 H
的 handleMessage(Message msg)
发现以下代码
public void handleMessage(Message msg) { if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what)); switch (msg.what) { …… case RESUME_ACTIVITY: Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityResume"); handleResumeActivity((IBinder) msg.obj, true, msg.arg1 != 0, true); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); break; …… }
然后看下 handleResumeActivity
final void handleResumeActivity(IBinder token, …… if (r.window == null && !a.mFinished && willBeVisible) { r.window = r.activity.getWindow(); View decor = r.window.getDecorView(); decor.setVisibility(View.INVISIBLE); ViewManager wm = a.getWindowManager(); WindowManager.LayoutParams l = r.window.getAttributes(); a.mDecor = decor; l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION; l.softInputMode |= forwardBit; if (a.mVisibleFromClient) { a.mWindowAdded = true; wm.addView(decor, l); } …… }
这里我们看到了 DecorView
被添加到了 ViewManager
之中。
ViewManager
只是一个接口,它的实现类为 WindowManagerImpl
。在 WindowManagerImpl
我查找 addView()
方法
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) { applyDefaultToken(params); mGlobal.addView(view, params, mDisplay, mParentWindow); }
这里的 mGlobal
又是 WindowManagerGlobal
的实例。所有我们又要跳转到 WindowManagerGlobal.addView()
。
O__O "… 这时千万别放弃,胜利就在眼前,同志们要坚持往下看啊。
public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) { …… ViewRootImpl root; View panelParentView = null; synchronized (mLock) { …… root = new ViewRootImpl(view.getContext(), display); view.setLayoutParams(wparams); mViews.add(view); mRoots.add(root); mParams.add(wparams); } // do this last because it fires off messages to start doing things try { root.setView(view, wparams, panelParentView); } …… }
然后再看下 ViewRootImpl.setView()
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) { synchronized (this) { if (mView == null) { mView = view; …… view.assignParent(this); } } }
亲人啊!终于看到 root.setView(view, wparams, panelParentView),我们上面一直说的 View 和 ViewRootImpl 的关系终于在这关联上了。为了更清晰一点我们画一个时序图
ViewRootImpl
绘制 View
现在进入了本文的压轴部分,View 绘制的核心源码。
通过以上的讲解,我们也知道要去找 ViewRootImpl
的 requestLayout()
和 invalidateChildInParent()
方法
ViewRootImpl.requestLayout()
public void requestLayout() { if (!mHandlingLayoutInLayoutRequest) { checkThread(); mLayoutRequested = true; scheduleTraversals(); } }
scheduleTraversals()
又是什么鬼
void scheduleTraversals() { if (!mTraversalScheduled) { mTraversalScheduled = true; mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier(); mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); if (!mUnbufferedInputDispatch) { scheduleConsumeBatchedInput(); } notifyRendererOfFramePending(); pokeDrawLockIfNeeded(); } }
这里我们看到了一个任务 mTraversalRunnable
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
mTraversalRunnable
是一个 Runnable 的子类
final class TraversalRunnable implements Runnable { @Override public void run() { doTraversal(); } }
这个时候我们又要去看下 doTraversal()
的源码。
void doTraversal() { if (mTraversalScheduled) { mTraversalScheduled = false; mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier); if (mProfile) { Debug.startMethodTracing("ViewAncestor"); } performTraversals(); if (mProfile) { Debug.stopMethodTracing(); mProfile = false; } } }
最后我们找到了 performTraversals()
方法, 注意 performTraversals() 里面有重大内容该方法很长(真的是特别长),我们这里看一下简化后的
private void performTraversals() { …… if (!mStopped || mReportNextDraw) { …… performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); …… } …… if (didLayout) { performLayout(lp, desiredWindowWidth, desiredWindowHeight); …… } …… performDraw(); …… }
看到了 performMeasure
、 performLayout
、 performDraw
这里就不用多说了吧。也就解释了为啥 View 的绘制顺序是 measure -> layout -> draw
了吧
ViewRootImpl.invalidateChildInParent()()
这里我们不啰嗦太多,直接上源码
public ViewParent invalidateChildInParent(int[] location, Rect dirty) { checkThread(); …… invalidateRectOnScreen(dirty); return null; } private void invalidateRectOnScreen(Rect dirty) { …… if (!mWillDrawSoon && (intersected || mIsAnimating)) { scheduleTraversals(); } }
看到这里就不用多说了,下面的执行顺序 ViewRootImpl.requestLayout()
已经分析过了。
这个时候大家再看下网上很多分析 requestLayout() 和 invalidate() 方法区别的,大家可以去先去查一下,等后面有时间我也会写一篇分析这两个方法区别的文章。
View 到底什么时候绘制到屏幕上?
通过以上分析我们知道
1. setContentView() 只是把 View 添加到 DecorView 上 2. onResume() 中 ViewRootImpl 和 DecorView 做了关联 3. requestLayout() 和 invalidate() 会触发 ViewRootImpl 绘制 View
但是!setContentView() 中调用了 requestLayout() 和 invalidate() 不会触发绘制,我们上面只讲了 onResume() 中 ViewRootImpl 和 DecorView 做了关联 。到底什么时候又调用了 requestLayout() 或者 invalidate() ???
往上翻我们发现在 ViewRootImpl.setView() 中有一个 requestLayout
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) { synchronized (this) { …… requestLayout(); …… view.assignParent(this); …… } } }
但是!居然在 view.assignParent(this)
这尼玛逗我吧!
我们在回头看下 requestLayout()
void scheduleTraversals() { if (!mTraversalScheduled) { mTraversalScheduled = true; mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier(); mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); if (!mUnbufferedInputDispatch) { scheduleConsumeBatchedInput(); } notifyRendererOfFramePending(); pokeDrawLockIfNeeded(); } }
这里重点看一下这句
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
了解 Android Handler Looper 都知道 postSyncBarrier 是创建一个障碍,阻止后面的 Message 对象被执行。那这里也就解决了我刚刚的疑问, 虽然request()
在 view.assignParent(this) 之前被调用,但是会被阻塞。 doTraversal() 执行的时候 DecorView 和 ViewRootImpl 已经关联了
这里留个坑
我没有找到 ViewRootImpl 怎么执行到 removeSyncBarrier(mTraversalBarrier)
的代码。
总结
对以上内容做个总结
1. View 在 Activity 的 onCreate() 方法中通过 setContentView() 方法添加到 Activity 的 DecorView 上2. 此时 ViewRootImpl 和 DecorView 没有关联上,不会绘制 View3. 在 Activity 的 onResume() 方法执行后,DecorView 会被添加带 ViewRootImpl 中。然后执行 requestlayout()
参考资料
共同学习,写下你的评论
评论加载中...
作者其他优质文章