基于 Android API 26 Platform 源码

写作背景

在上一篇探究Android View 绘制流程,Xml 文件到 View 对象的转换过程我们了解了setContentView(resId) 如何把 xml 文件转换成 Java 中的 View 对象。本篇文章在此基础上继续探究,View 是如何展示到 Activity 上的。

很多 Android 开发者都知道一个事情

当 Activity 执行 onResume() 方法后,代表 Activity 显示到前台

这句话很短,但是背后隐藏了多少方法的调用呢?下面我们将一层一层的剥开源码寻找真相。

onion.jpg

先从 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_01.png

我们看到一个 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 的来源,这一层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();        //……        //……           }
nani.jpg

根据我们对 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;    }    ……}

顺着这个方法追溯一下,如下图

activity_view_02.png

这时候我们又疑问了:

DecorView 的 mParent 是谁呢???

question.png

答案只有一个,是 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 中。我们可以准确的说 DecorViewmParent 其实是 ViewRootImpl。但是怎么证明呢???

DecorViewViewRootImpl 的关系

本文开盘就已经说了 当 Activity 执行 onResume() 方法后,代表 Activity 显示到前台,这是为什么呢?

我们都是 Activity 的由 ActivityManager 管理,Activity 页面的操作必须在主线程中,而主线程就是 ActivityThread 。在 ActivityThread 的源码中,找到了一个 H 类,该类继承 Handler 。在 HhandleMessage(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()

keep.jpg

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 的关系终于在这关联上了。为了更清晰一点我们画一个时序图

activity_view_03.png

ViewRootImpl 绘制 View

现在进入了本文的压轴部分,View 绘制的核心源码。

通过以上的讲解,我们也知道要去找 ViewRootImplrequestLayout()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();        …… }
success.jpg

看到了 performMeasureperformLayoutperformDraw 这里就不用多说了吧。也就解释了为啥 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) 这尼玛逗我吧!

nani.jpg

我们在回头看下 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()

参考资料

Android Launcher 启动 Activity 的工作过程

Activity到底是什么时候显示到屏幕上的呢?

更多相关文章

  1. Android(安卓)2.2 源码结构分析
  2. 【Android】 NDK开发基础
  3. [转]Android(安卓)3D 编程:HelloArrow(用 OpenGL ES 2.0 实现)
  4. 【读书笔记】【Android(安卓)开发艺术探索】第 7 章 Android(安
  5. SQLite的使用
  6. Android(安卓)手把手教你写EventBus
  7. Android列表小部件(Widget)开发详解
  8. Android获取和设置系统环境变量指南
  9. android ApiDemos里的Transition3d翻转修复完善

随机推荐

  1. Linux下安装Android的adb驱动-解决不能识
  2. Android下实现图片缓存的实例
  3. Android中so使用知识和问题总结以及插件
  4. BAT的android应用会用到的第三方框架
  5. 【转】android内存管理机制
  6. 本年度最令人期待的5款Android智能手机!
  7. Android智能电视开发之明星UI---Recycler
  8. android实现电子数字显示
  9. Android(安卓)Studio的一些配置(去拼写检
  10. MWC2012开幕在即:看现场 猜趋势