从onResume()分析,ViewRootImpl在视图渲染中扮演什么角色?

2,249 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第28天,点击查看活动详情

接下来会出三篇文章详细介绍,View视图如何经过一遍遍的流程渲染到界面上的,从ActivityonResume(),经过Choreographer处理,经过FrameDisplayEventReceiver处理,最终触发View的measure、layout、draw三大流程,将视图绘制到界面上。

历史文章

你可能需要了解下的Android开发技巧(一)

你可能需要了解下的Android开发技巧(二)

你可能需要了解下的Android开发技巧(三)

onResume()生命周期说起

总说周知,视图渲染到界面上的时机就是从onResume()生命周期开始的,接下来我们细细分析:

ActivityonResume生命周期的分发是来自于ActivityThread的方法handleResumeActivity(),这个方法中就除了分发onResume()生命周期外,还帮助我们将视图渲染到界面上.

1. ActivityThread.handleResumeActivity()

public void handleResumeActivity(ActivityClientRecord r, boolean finalStateRequest,
        boolean isForward, String reason) {
 
    if (!performResumeActivity(r, finalStateRequest, reason)) {
        return;
    }      
    ViewManager wm = a.getWindowManager();
    wm.addView(decor, l);
}

这段代码精简了很多 ,一开始就是分发resume周期 ,接下来的addView()方法就要开始真正的绝活了:渲染视图到界面。

先说下这个ViewManager是一个接口,包含了下面三个方法

image.png

这三个方法大家应该很熟悉,依次分别代表着界面中添加View、更新View、移除View的三个最基本的功能,这里的ViewManager其最终的实现类为WindowManagerImpl,其父接口WindowManager实现了ViewManager

所以addView()这个方法的具体实现就得看下WindowManagerImpl这个类了。

2. WindowManagerImpl.addView()

image.png

可以看到啥也没有,实现给我们隐藏了,这里直接进行解密,最终交给了WindowManagerGlobal的方法addView()进行处理:

public void addView(View view, ViewGroup.LayoutParams params,
        Display display, Window parentWindow, int userId) {
        
    ViewRootImpl root;
    
    synchronized (mLock) {
        root = new ViewRootImpl(view.getContext(), display);
        view.setLayoutParams(wparams);

        mViews.add(view);
        mRoots.add(root);
        mParams.add(wparams);

        try {
            root.setView(view, wparams, panelParentView, userId);
        } catch (RuntimeException e) {
            throw e;
        }
    }
}

这个上面的代码也是经过精简后的处理,下面我们一步步进行分析:

  1. 创建ViewRootImpl对象 ,这个对象大家应该非常熟悉,当我们在非UI线程更新UI时经常报的那个错误就来自于这个对象的方法checkThread()检测。

    这个对象是连接DecorViewWindow窗口(不要被窗口所迷惑,其实其唯一实现类PhoneWindow就是提供了真正窗口的一些操作,比如转发触摸事件、设置背景等等)的桥梁。

  2. 将视图、ViewRootImpl、视图的Param保存到mViewsmRootsmParams三大集合进行管理;

  3. 通过ViewRootImpl的方法setView()开始将View添加到Window窗口中,开始和渲染核心类Choreographer的交互。

我们继续看下ViewRootImpl方法setView()

3. ViewRootImpl.setView()

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
        int userId) {
    requestLayout();
}

这个方法中的代码非常长,目前我们只需要关注上面的方法requestLayout()即可。这里顺便说下,这个setView()方法还有其他非常重要的逻辑,比如注册接受底层传来的触摸事件、构造触摸事件分发对象责任链等等,以后会出其他的文章专门讲解。

4. ViewRootImpl.requestLayout()

我们继续看下方法requestLayout():

public void requestLayout() {
    if (!mHandlingLayoutInLayoutRequest) {
        checkThread();
        mLayoutRequested = true;
        scheduleTraversals();
    }
}
  1. checkThread()上面有提到,校验当前线程是否合法;

    image.png

  2. mLayoutRequested置为true标识开始渲染开始,标识之后视图需要测量、布局等等操作;

  3. scheduleTraversals()开始于Choreographer打交道;

5. ViewRootImpl.scheduleTraversals()

void scheduleTraversals() {
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
        mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        notifyRendererOfFramePending();
        pokeDrawLockIfNeeded();
    }
}

这个方法主要的处理逻辑如下:

  1. mTraversalScheduled置为true,主要是在本次渲染没结束前来接之后的渲染请求;

  2. postSyncBarrier()添加异步屏障消息,方便优先处理异步消息,接下来的渲染就会包装成一个异步消息分发执行;

  3. Choreographer.postCallback()注册渲染回调,在底层脉冲信号收到之时,触发该回调执行,真正开始View视图的三个流程measure、layout、draw。

总结

本篇文章主要分析了ViewRootImpl这个类在视图渲染中扮演了什么角色:

  1. 沟通DecorViewWindow的桥梁,使得两者之间建立联系,即将View绘制的区域映射到WMS中,通过匿名共享内存的方式使得绘制区域在二者之间共享;

  2. 添加屏障消息,注册渲染回调,同时触发Choreographer注册接受下一次底层的垂直脉冲,以便于触发之前注册的渲染回调。

其中,与Choreographer打交道的讲解会放到下一篇文章中,在这一篇文章中,你会真正了解到Choreographer如何注册渲染回调、如何注册脉冲信号监听、如何触发界面真正的渲染流程的等等,请敬请期待。