Android 高级UI解密 (六) :结合Activity启动源码剖析View的诞生

自定义控件的UI绘制流程,势必继承View或者ViewGroup,即Android系统提供的控件或Layout容器,接下来就是喜闻乐见的layout摆放、measure测量、draw绘制三件套。看似简单的过程,但开发中总会遇到一些奇奇怪怪的bug:位置摆放有问题?设置属性不起作用?控件显示出错?

以上类型的问题,不同于之前文章篇幅讲解的绘制绚丽UI效果类型,更注重自定义整个过程,除了图形绘制之外,你需要去考虑图形之间的测量、摆放,此部分在自定义Layout中体现的淋漓尽致。因此完成一个全面的自定义控件,View绘制原理(onMeasure、onLayout、onDraw)流程掌握必不可少,擒贼先擒王,每个开发者心里对于绘制流程必须有个大致的轮廓,到时候出现bug时你才能对症下药,知道问题出在哪个地方,debug过程对此要求极高。从此篇文章开始将从源码的角度入手分析View绘制渲染的原理。

(此系列文章知识点相对独立,可分开阅读,不过笔者建议按照顺序阅读,理解更加深入清晰)

Android 高级UI解密 (五) :PathMeasure截取片段 与 切线(新思路实现轨迹变换)
Android 高级UI解密 (四) :花式玩转贝塞尔曲线(波浪、轨迹变换动画
Android 高级UI解密 (三) :Canvas裁剪 与 二维、三维Camera几何变换(图层Layer原理)
Android 高级UI解密 (二) :Paint滤镜 与 颜色过滤(矩阵变换)
Android 高级UI解密 (一) :Paint图形文字绘制 与 高级渲染

此篇涉及到的知识点如下:

  • Activity中ContentView布局加载流程;
  • Window、PhoneWindow、DecorView顶级View概念解析;
  • 结合Activity启动流程分析布局加载过程;
  • Activity与Window,DecorView与WindowManager的联系?
  • ViewRoot、ViewRootImpl开启三大流程绘制performTraverslas过程;

一. Activity的setContentView视图加载

UI绘制流程,将其分成两个部分来解析,首先以Activity的角度入手,它是如何渲染出界面UI的?

1. 源码步骤

Step 1. 调用setContentView加载布局

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
}

首先回到加载UI界面的源点,也就是探索的入口:setContentView方法,追踪其源码,此方法有多个重载的方法,任意查看其中一个即可:

Step 2. Activity的setContentView方法

【Activity.java】

    public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }

    public Window getWindow() {  
    return mWindow;  
    } 

可以发现,Activity实现的setContentView方法非常简单,调用getWindow方法获取一个Window类型的对象,并调用其setContentView方法,将资源id传入其中。


Step 3. Window抽象类的setContentView方法

【Window.java】

/**
 * <p>The only existing implementation of this abstract class is
 * android.view.PhoneWindow, which you should instantiate when needing a
 * Window.
 */
public abstract class Window {
    public static final int FEATURE_OPTIONS_PANEL = 0;
    public static final int FEATURE_NO_TITLE = 1;
    ......
    public abstract void setContentView(int layoutResID);

    public abstract void setContentView(View view);

    public abstract void setContentView(View view, ViewGroup.LayoutParams params);
}

点进去详细查看这个Window对象,发现其setContentView方法只是一个抽象方法,不仅如此,它本身就是一个抽象类,注释为“顶级窗口外观和行为策略的抽象基类。它提供标准的UI策略,如背景、标题区域、默认密钥处理等。”后面的注释也指出了: 唯一的实现类就是android.view.PhoneWindow

因此可以理解为Activity呈现出的页面就是一个PhoneWindow,即Window的一种呈现显示,例如弹出的窗口也是Window的一种形式。Window中的静态final成员变量有十几个,定义了多种不同窗口类型。


Step 4. PhoneWindow(DecorView内部类)实现父类的setContentView方法

之前提到PhoneWindow是Window窗体唯一的实现类,那它必定实现了父类的三个setContentView抽象方法,来查看其具体实现,揭开界面渲染的奥秘:

【PhoneWindow.java】

public class PhoneWindow extends Window implements MenuBuilder.Callback {
    ......
    @Override
    public void setContentView(int layoutResID) {
        if (mContentParent == null) {
            installDecor();
        } else {
            mContentParent.removeAllViews();
        }
        mLayoutInflater.inflate(layoutResID, mContentParent);
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
    }

    @Override
    public void setContentView(View view) {
        setContentView(view, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
    }

    @Override
    public void setContentView(View view, ViewGroup.LayoutParams params) {
        if (mContentParent == null) {
            installDecor();//处理布局渲染的重点方法
        } else {
            mContentParent.removeAllViews();
        }
        mContentParent.addView(view, params);
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
    }
    ......

可以发现这是哪个方法内的逻辑都是先判断mContentParent是否为null(注意mContentParent是一个ViewGroup):

  • 若不为null,则移除所有的View再将我们传入的View添加进去(若传入的是资源ID可通过LayoutInflater转化)
  • 若为null,在添加我们传入的View到mContentParent之前,需要先创建mContentParent,重点究其创建过程:
    • 调用installDecor()方法,其内部又创建了一个DecorView(继承于FrameLayout,是PhoneWindow的内部类),相当于在PhoneWindow内又嵌套了一层View。
    • 接着调用generateLayout(mDecor)方法,传入上一步骤创建好的DecorView参数,(此方法可谓是一个神级方法,内部处理的逻辑量大到不亚于ViewRoot的performTraversals方法,此处只重点讲解DecorView部分)此方法内部根据属性判断创建了一个LinearLayout的布局,布局内分为标题栏和内容栏,DecorView添加此布局,并将其视为“顶级View”;
    • 接着findViewById获取内容栏部分返回,内容栏也就是“ID_ANDROID_CONTENT”部分会用来显示我们调用setContentView中传入的布局。(这也是为何我们传入布局时调用的是setContentView方法而不是setView方法命名的缘由)

获取ViewGroup赋值给mContentParent成员变量,此处的ViewGroup就是我们传入的布局,也是ID_ANDROID_CONTENT

【PhoneWindow.java】

        private void installDecor() {
        if (mDecor == null) {
            mDecor = generateDecor();//⭐️生成一个DecorView(继承的FrameLayout)
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
            ......
        }
        if (mContentParent == null) {
            mContentParent = generateLayout(mDecor);//⭐️将生成的布局赋值给示页面
            mDecor.makeOptionalFitsSystemWindows();
            final DecorContentParent decorContentParent = (DecorContentParent)mDecor.findViewById(R.id.decor_content_parent);
            ......
    }
}

    protected DecorView generateDecor() {
        return new DecorView(getContext(), -1);
    }

    //⭐️⭐️⭐️⭐️⭐️处理了大量逻辑
    protected ViewGroup generateLayout(DecorView decor) {
        //① 获取当前窗体的style
        TypeArray a = getWindowStyle();
        ......
        //② 根据设置的属性指定style(判断当前窗体是否需要显示标题栏、ActionBar)
        if(a.getBoolean(R.styleable.Window_windowNoTitle, false)){
            requestFeature(FEATURE_NO_TITLE);
        } else if(a.getBoolean(R.styleable.Window_winodwActionBar, false){
            requestFeature(FEATURE_ACTION_BAR);
        }
        ......
        //③根据判断初始化DecorView的布局ID
        int layoutResource;
        int features = getLocalFeatures();
        if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0){
            layoutResource = R.layout.screen_swipe_dismiss;
        } else if(...){
            ...
        }

        //④LayoutInflater加载资源ID为View,该View是一个LinearLayout,内部分为标题栏和状态栏;decor添加此布局,并将此布局赋值为“顶级View”。
        View in = mLayoutInflater.inflate(layoutResource, null);//⭐️DecorView给自己创建布局Layout
        decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
        mContentRoot = (ViewGroup) in;

        //⑤ 获取DecorView布局中的Content部分赋值给contentParent
        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);  
        ......
        return contentParent;
    }

generateLayout方法逻辑

以上方法的重点流程在上面已经总结过,此处要特地强调generateLayout方法,其内部处理大量的逻辑,例如设置StatusBar、NavigationBar、状态栏显示、输入法类型、根据当前Style类型为当前Window选择不同的布局文件,为“根布局”指定一个用来存放我们自定义布局文件(“根布局”是个ViewGroup,例如常用的LinearLayout)。

上述代码示例中调用的requestFeature方法,大家应该不陌生,在早期会在Activity的onCreate中调用getWindow().requestFeature(featureId)去除ToolBar,此处有一个重点就是一定要在setContentView之前设置此方法,不然去除ToolBar没有效果!而原因就在generateLayout源码中,内部首先会去判断这些相关属性,即DecorView刚被创建,内部ViewGroup(即mContentParent成员变量)还未创建,在此之前设置NO_TITLE相关属性才有作用,否则等到布局都创建好之后再去设置Window的相关属性已无用。

DecorView

继承于FrameLayout的DecorView,可见generateLayout方法中创建的布局:new了一个LinearLayout,并传入了layoutResource,这个layoutResource是前部分style判断而决定的layout布局,有R.layout_screen_action_bar R.layout.screen_titleR.layout_screen_simple_overlay_action_modeR.layout_screen_simple等等选择,通常情况下布局则是如下图所示,一个简单的titlebar和content内容布局。
这里写图片描述
后续几行代码的目的更是显而易见,DecorView将这个LinearLayout布局添加进来,并将LinearLayout布局这个作为mContentRoot 顶级View

  • 如何得到content呢?
ViewGroup content = (ViewGroup)findViewById(android.R.id.content) 
  • 如何得到我们设置的View呢?
content.getChildAt(0);

最后补充一点: View层的事件都要经过DecorView,再传递给View.



2. 总结

这里写图片描述

  • Window:一个抽象类,提供了一组绘制窗口的通用API;
  • PhoneWindow:是Window的唯一实现类,处理了大量视图加载相关逻辑。
  • DecorView:是PhoneWindow的内部类,是FrameLayout的子类,是对FrameLayout进行功能的修饰,是所有应用窗口的根View。

这里写图片描述
以上就是Activity加载布局的大致过程,做一下总结:

  1. 首先调用Activity的setContentView方法,其内部则是获取Window成员变量,调用它的setContentView方法。
  2. Window类实则是一个抽象类,其setContentView方法也是抽象方法,它唯一的实现类就是PhoneWindow。
  3. 继而又将重点转换到PhoneWindow类,它实现了三个重载的setContentView方法,内部逻辑中创建了一个继承于FrameLayout的DecorView,并根据指定style创建了LinearLayout布局添加进来,将其视为“顶级View”,此布局中又分为标题栏和内容栏,而后者用于存放我们传入的布局。



二. 结合Activity启动流程分析布局加载过程

以上第一大点源码分析了Activity加载xml布局的一个过程,并引入了“窗体”的概念,让我们了解Activity –> PhoWindow –> DecorView这样一个嵌套View的流程。但心细的你会发现上一部分丝毫未提及绘制相关事件,意味着界面UI还未渲染出来,呈不可见状态。

此时我们已经了解Activity加载xml布局的一个过程,得知其中涉及到了Window、WindowPhone类,但这只是View加载原理的中间一节,我们不禁有以下疑问:

  • Winodow的唯一实现类PhoneWindow是何时被创建的?Activity是如何附属在Window上的?
  • 身为顶级View的DecorView是如何与WindowManager联系起来的?
  • 开启View三大绘制流程的ViewRootImpl何时被创建?是如何与DecorView联系起来的?
  • ViewRoot的performTraverslas又是何时被调用的?

要想解决以上问题,还是得回归到根本,我们最初调用加载布局的setContentView方法是在Activity的onCreate生命周期中调用的,需要追溯到Activity启动相关源码,彻头彻尾捋清楚,开始!

(安利米娜桑去看笔者之前写的Android:图解Activity启动流程源码(整体流程),对后续理解有帮助)


Step 1. ActivityThread的handleLaunchActivity方法

Activity启动后到一连串不重要调用在此不在叙述的,建议读者阅读上面链接文章,直袭Activity启动重点 —— ActivityThread。

【ActivityThread.java】

private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        ......
        //⭐️⭐️⭐️⭐️⭐️调用Activity的onCreate方法,开启生命周期
        Activity a = performLaunchActivity(r, customIntent);

        if (a != null) {
            r.createdConfig = new Configuration(mConfiguration);
            Bundle oldState = r.state;
            //⭐️⭐️⭐️⭐️⭐️调用Activity的onResume方法,关联decor和WindowManager
            handleResumeActivity(r.token, false, r.isForward,
                    !r.activity.mFinished && !r.startsNotResumed);

            ......
        }
    }

可见ActivityThread的handleLaunchActivity方法中做了两件重要的事,从Activity的生命周期角度来看的话,分别调用了performLaunchActivityhandleResumeActivity方法,对应着Activity的onCreateonResume方法。

从大方向看就这么点回事,可我们的重点在于Window、DecorView、ViewRootImpl这些与界面渲染有关的部分,因此首先深入查看performLaunchActivity方法。


Step 2. ActivityThread的performLaunchActivity方法

【ActivityThread.java】

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        ......
        //① ClassLoader创建Activity对象
        Activity activity = null;
        try {
            java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
            StrictMode.incrementExpectedActivityCount(activity.getClass());
            r.intent.setExtrasClassLoader(cl);
            r.intent.prepareToEnterProcess();
            if (r.state != null) {
                r.state.setClassLoader(cl);
            }
        }
        ......
        try {
            //② 创建应用程序Application
            Application app = r.packageInfo.makeApplication(false, mInstrumentation);
            ......
            if (activity != null) {
                //③ 创建Activity专属的Context上下文环境
                Context appContext = createBaseContextForActivity(r, activity);
                ...... 
                Window window = null;
                if (r.mPendingRemoveWindow != null && r.mPreserveWindow) {
                    window = r.mPendingRemoveWindow;
                    r.mPendingRemoveWindow = null;
                    r.mPendingRemoveWindowManager = null;
                }
                //④⭐️⭐️⭐️⭐️⭐️ 调用Activity的`attach`方法,完成一些重要数据的初始化。(Activity与ContextImpl关联,Activity创建Window并建立自己和Window的关联)
                activity.attach(appContext, this, getInstrumentation(), r.token,
                        r.ident, app, r.intent, r.activityInfo, title, r.parent,
                        r.embeddedID, r.lastNonConfigurationInstances, config,
                        r.referrer, r.voiceInteractor, window);
                ......        
                //⑤ 调用Activity的onCreate方法,正式启动Activity
                if (r.isPersistable()) {
                    mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);

       ......
        return activity;
    }

笔者已经将ActivityThread的performLaunchActivity方法重点源码都贴出来,主要逻辑再次列举:

  1. ClassLoader创建Activity对象;
  2. 创建应用程序Application;
  3. 创建Activity专属的Context上下文环境;
  4. 调用Activity的attach方法,完成一些重要数据的初始化;(Activity与ContextImpl关联,Activity创建Window并建立自己和Window的关联)
  5. 调用Activity的onCreate方法,正式启动Activity;

以上列举中的重点显而易见,得知Activity的attach方法有关渲染UI重点类的初始化,深入源码:


Step 3. Activity的attach方法

其中创建流程后期会调用Activity的attach方法来完成一些重要数据的初始化:

【Activity.java】

final void attach(Context context, ActivityThread aThread,
    ......
    //创建PhoneWindow对象
    mWindow = PolicyManager.makeNewWindow(this);
    //设置相关属性
    mWindow.setCallback(this);
    mWindow.getLayoutInflater().setPrivateFactory(this);
    if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
        mWindow.setSoftInputMode(info.softInputMode);
    }
    if (info.uiOptions != 0) {
        mWindow.setUiOptions(info.uiOptions);
    }
    ......
}

以上代码发现在Activity被创建时,就已经初始化好PhoneWIndow对象,也就是界面呈现的载体已准备好。此处作为拓展,继续查看PhoneWIndow被创建的过程,查看以下代码:

【PolicyManager.java】

public final class PolicyManager {
    private static final String POLICY_IMPL_CLASS_NAME =
        "com.android.internal.policy.impl.Policy";

    private static final IPolicy sPolicy;

    static {
        try {
            Class policyClass = Class.forName(POLICY_IMPL_CLASS_NAME);
            sPolicy = (IPolicy)policyClass.newInstance();
        } catch (ClassNotFoundException ex) {
            ......
    }

    public static Window makeNewWindow(Context context) {
        return sPolicy.makeNewWindow(context);
    }
    ......
}
【Policy.java】

public Window makeNewWindow(Context context) {
    return new PhoneWindow(context);
}

以上两段代码即可了解PhoneWindow的创建过程,首先在PolicyManager中通过反射的方式而获取com.android.internal.policy.impl.Policy的实例,再调用Policy的makeNewWindow方法创建,即new了一个PhoneWindow对象。

————————————————————
问:第一个问题,PhoneWIndow何时被创建的?如何与Activity关联上?
答:在Activity初始化时的attach方法中。
————————————————————

在解决第一个问题后,查看第二个问题:Window又是如何与Activity联系起来的呢?继续研究源码,仍旧回到最初的ActivityThread的handleLaunchActivity方法中,分析完启动Activity的onCreate生命周期的performLaunchActivity方法后,接下来分析handleResumeActivity方法。


Step 4. ActivityThread的handleResumeActivity方法

【ActivityThread.java】

final void handleResumeActivity(IBinder token,
            boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
        ......
        if (r != null) {
            final Activity a = r.activity;

            ......
            if (r.window == null && !a.mFinished && willBeVisible) {
                //① 获取Activity依附的Window对象
                r.window = r.activity.getWindow();
                //② 获取Window中的DecorView对象
                View decor = r.window.getDecorView();
                decor.setVisibility(View.INVISIBLE);

                //③ 获取此Activity联系的WindowManager对象
                ViewManager wm = a.getWindowManager();
                //④ 将Window里的DecorView对象赋值于Activity上
                a.mDecor = decor;
                l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
                l.softInputMode |= forwardBit;
                if (r.mPreserveWindow) {
                    a.mWindowAdded = true;
                    r.mPreserveWindow = false;

                    // 通常,ViewRoot使用addView-> ViewRootImpl#setView中的Activity设置回调。 如果我们要重新使用装饰视图,则必须通知视图根据回调可能已经改变。

                if (a.mVisibleFromClient && !a.mWindowAdded) {
                    a.mWindowAdded = true;
                    //⑤ 将DecorView与WindowManager关联上。⭐️⭐️⭐️
                    wm.addView(decor, l);
                }
            }
            ......
        }
        ......
    }

照例,在此列举一下此方法的逻辑:

  1. 获取Activity依附的Window对象;
  2. 获取Window中的DecorView对象;
  3. 获取此Activity联系的WindowManager对象;
  4. 将Window里的DecorView对象赋值于Activity上;
  5. 将DecorView与WindowManager关联上;

这初学者看下来估计已经懵了,我们来好好捋一捋,首先列举上面出现的重点类:Activity、Window、DecorView、WindowManager、ViewRootImpl。来理清这5个大佬之间的关系:

  • 首先第一大点已经讲解过,DecorView是Window子类PhoneWindow的内部类,ViewRootImpl对象又是从DecorView中获取的。其实ViewRootImpl才是View真正的实现类,具体的绘制操作都是由它执行,因此一个Window对应一个View和一个ViewRootImpl;
  • WindowManager与View同理,具体的操作实现都是由WindowManagerImpl实现的;
  • Window和View是通过ViewRootImpl联系起来的;

最后归纳一下:handleResumeActivity方法最大的作用就是将顶层视图DecorView通过WindowManager挂载到Window中,便于后续DecorView中的ViewRootImpl实行界面绘制操作。

————————————————————
问:第二个问题,身为顶级View的DecorView是如何与WindowManager联系起来的?
答:在ActivityThread的handleResumeActivity方法中。
————————————————————

此部分的重点那是相当多,好好消化一下,明白这些之后相信很多人的重点还是在绘制三大流程的ViewRootImpl上,第三个问题:它是何时被创建的?何时才开始绘制?重点查看此方法的第5个步骤wm.addView(decor, l);,将DecorView与WindowManager关联上,深入研究。


Step 5. WindowManagerImpl、WindowManagerGlobal的 addView方法

【WindowManagerImpl.java】

public void addView(View view, ViewGroup.LayoutParams params){
    mGlobal.addView(view, params, misplay, mParentWindow);
}

可以发现WindowManagerImpl的addView方法中并没有实际处理逻辑,而是托付给mGloba成员变量处理,此处的mGloba就是WindowManagerGlobal的一个内部实例,继续查看其具体实现:

【WindowManagerGlobal.java】

public void addView(View view, ViewGroup.LayoutParams params){
    ......
    //开启三大绘制的BOSS
    ViewrootImpl root;
    View panelParentView = null;

    synchronized(mLock){
        ......
        //⭐️⭐️⭐️通过DecorView对象的上下文环境Context实例化new了一个ViewrootImpl对象
        root = new ViewrootImpl(view.getContext(), display);
        view.setLayoutParams(wparams);
        mViews.add(view);
        mRoots.add(root);
        mParams.add(wparams);
    }
    try{
        //⭐️⭐️⭐️调用ViewrootImpl的setView方法,将DecorView与ViewrootImpl关联起来
        root.setView(view, wparams, panelParentView);
    }
    ......
}

可喜可贺,我们找到了控制View三大绘制流程的BOSS诞生点,即ViewrootImpl的创建实例化,创建时传入其构造方法的参数是DecorView的上下文环境Context,后续调用ViewrootImpl的root.setView方法,将DecorView与自己关联起来。因此此处可以证实Step 4总结的一个理论:一个Window对应一个View和一个ViewRootImpl

————————————————————
问:第三个问题,开启View三大绘制流程的ViewRootImpl何时被创建?是如何与DecorView联系起来的?
答:在WindowManagerGlobal的 addView方法中。
————————————————————

amazing,真相已经呼之欲出,继续深入最后一个步骤,即ViewRootImpl 的 setView方法,揭开绘制流程开启入口的面纱~


Step 6. ViewRootImpl 的 setView方法 —— 即将开启绘制流程⭐️

【ViewRootImpl.java】

 public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
            if (mView == null) {
                ......
                //⭐️⭐️⭐️绘制view的入口
                requestLayout();
                ......
                try {
                    mOrigWindowType = mWindowAttributes.type;
                    mAttachInfo.mRecomputeGlobalAttributes = true;
                    collectViewAttributes();
                    //⭐️涉及到IPC,WindowSession完成Window的添加,此处牵扯过多,读者感兴趣可自行研究
                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(),
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mInputChannel);
                }
                ......
            }
        }
  }

ViewRootImpl 的 setView方法内部处理了大量重点逻辑,例如注册DisplayManager、添加布局到WindowManager、设置Input管道等等,但此处只重点关注绘制部分,查看五角星标记处,内部调用了requestLayout()方法,令人激动的来了,此方法就是开启View绘制的入口,继续查看:

【ViewRootImpl.java】

    @Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            //⭐️⭐️⭐️准备开启绘制流程
            scheduleTraversals();
        }
    }

    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            //⭐️⭐️⭐️发起一个异步消息mTraversalRunnable
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            ......
        }
    }

requestLayout()中调用了scheduleTraversals方法,后者方法中会发起一个异步消息mTraversalRunnable,挺住!继续查看其实现:

【ViewRootImpl.java】

    final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            //⭐️⭐️⭐️
            doTraversal();
            }
        }

        void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

            ......
             //⭐️⭐️⭐️开启绘制流程
            performTraversals();
            ......
        }
    }

在异步消息TraversalRunnable中调用了doTraversal()方法,最后一步了,在此方法中调用了performTraversals(),即绘制View的入口。吐血ing,连环调问你怕不怕,可见逻辑分离的多么细致,要参透ViewRootImpl这个类不简单~

————————————————————
问:第四个问题,ViewRoot的performTraverslas又是何时被调用的?
答:在ViewRootImpl类中scheduleTraversals()方法中调用的异步消息TraversalRunnable中。
————————————————————


Step 7. ViewRootImpl 的 performTraversals方法 —— 开启绘制流程

【ViewRootImpl.java】
 private void performTraversals() {
         ......
        if (!mStopped || mReportNextDraw) {
                ......
                    //① 获取顶级Layout的宽高MeasureSpec
                    int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
                    int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
                    ......
                    //②执行测量工作
                    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
                    }
          ......
        if (didLayout) {
            //③执行布局工作
            performLayout(lp, mWidth, mHeight);
            }
        ......
        if (!cancelDraw && !newSurface) {
            ......
            //④执行绘制工作
            performDraw();
        }
        ......

    }

撒花~终于找到你,还好我没放弃~上图所示的代码意图再明显不过了,是启动三大绘制流程的方法调用。




总结

这里写图片描述

上图是结合了Activity启动流程源码,分析出的视图加载相关的时序图,其重点逻辑在于:在ActivityThread中,当Activity对象被创建完毕后,会将DecorView添加到Window中,同时会创建ViewRootImpl对象,并将ViewRootImpl对象和DecorView建立关联,此过程可参考以下源码:

root = new ViewRootImpl(view.getContext(), display);
root.setView(view, wparams, panelParentView);

ViewRoot,它对应于ViewRootImpl类,是连接WindowManager和DecorView的纽带,View最重要的三大流程都是通过ViewRoot来完成的!(ViewRoot的performTraversals方法开始三大流程,具体在第二部分讲解)

注:此篇文章暂不深入解析三大流程绘制具体细节,留给下一篇,此篇文章的重点就是讲解Android界面绘制的“世界观”环境。


若有错误,虚心指教~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值