View的绘制流程之二:View的绘制入口源码分析

一、回顾

由上一篇笔记 《View的绘制流程之一:setContentView()方法源码分析》,我们知道了 Activity 的 Layout 布局最后会保存在 DecorView 中的 Layout 布局中的 FrameLayout 中,但是还没有进行绘制,接下来,我们就来分析 DecorView 的布局以及 Activity 的布局是在什么时候进行绘制的

我们知道 Activity 的启动顺序如下:

--> 栈顶的Activity的onPause() 
--> Instrumentation的newActivity() /*创建Activity*/
--> 待启动Activity的attach()
--> 待启动Activity的onCreate()
--> 待启动Activity的onResume()
--> ActivityThread.handleResumeActivity() /*将DecorView添加到Window*/

也就是说 DecorView 的布局以及 Activity 的布局都是在 Activity 的 onResume() 方法之后进行绘制的,具体位置在 ActivityThread.handleResumeActivity() 方法中将 DecorView 添加到 Window 中,同时进行绘制



二、源码分析

Step 1:

现在我们看到 ActivityThread 的 handleResumeActivity() 方法中
★ ActivityThread # handleResumeActivity()

final void handleResumeActivity(IBinder token,
                                boolean clearHide, boolean isForward, boolean reallyResume) {
    ....
    // 这个方法最终会调用 Activity的 onResume()方法
    ActivityClientRecord r = performResumeActivity(token, clearHide);
    if (r != null) {
        final Activity a = r.activity;
        ....

        // 如果该Activity是第一次启动,而不是重新调用 onResume()方法重新启动,那么下面的条件为true
        if (r.window == null && !a.mFinished && willBeVisible) {
            // 1、获取当前 Activity的 PhoneWindow对象
            r.window = r.activity.getWindow();

            // 2、获取 DecorView,该DecorView已经在Activity的onCreate()方法的setContentView()初始化了
            View decor = r.window.getDecorView();
            decor.setVisibility(View.INVISIBLE);

            // 3、获取WindowManager的实现类WindowManagerImpl;WindowManagerImpl实现了ViewManager接口
            ViewManager wm = a.getWindowManager();

            WindowManager.LayoutParams l = r.window.getAttributes();
            // 4、将DecorView保存到Activity中
            a.mDecor = decor;
            l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
            l.softInputMode |= forwardBit;
            if (a.mVisibleFromClient) {
                // 标记已经将DecorView添加到Window
                a.mWindowAdded = true;
                // 5、将DecorView添加到Window
                // 由此可将,DecorView添加到 Window的过程跟我们将View添加到Window过程差不多
                wm.addView(decor, l);
            }
        } else if (!willBeVisible) {
            r.hideForNow = true;
        }
        ....
    } else {
        ....
    }
}

第一步:会获取 Activity 的 PhoneWindow 对象(这个对象已经在 Activity 的 onCreate() 方法调用 setContentView() 方法过程中实例化了)
第二步:从 PhoneWindow 中获取 DecorView,为接下来将 DecorView 添加到 Window 做准备
第三步:获取 WindowManager 的实现类 WindowManagerImpl ,这个类是连接远程 WindowManagerService 服务的一个纽带, WindowManagerService 服务负责管理系统的 Window级别的 View 的,通过这个类可以将 DecorView 添加到 WindowManagerService
第四步:将 PhoneWindow 中的 DecorView 保存到 Activity 中的 mDecor 属性中,从这里开始 DecorView 就和我们的 Activity 真正联系在一起了
第五步:添加到远程 WindowManagerService 服务中

小结:
我们的 DecorView 已经添加到了 Window 中了,由源码我们知道将 DecorView 添加 Window 的过程跟我们手动添加一个 View到 Window 的流程差不多,所以下面我只简要分析这个流程,具体细节可以看文章 《Window的View操作之一:添加View的过程》
 

Step 2:

现在我们知道通过 wm.addView(decor, l) 这一段代码就将 DecorView 添加到了远程的 WindowManagerImpl,但是我们还是不知道 View 在哪里被绘制,我们接着看到 WindowManagerImpl 中 addView() 方法
★ WindowManagerImpl # addView()

@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
    // 验证 LayoutParams是否为 WindowManager的LayoutParmas,同时检测一下令牌
    applyDefaultToken(params);
    // 调用 WindowManagerGlobal的 addView()方法将View添加到 Window
    mGlobal.addView(view, params, mDisplay, mParentWindow);
}
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();

WindowManagerImpl 将 addView() 的逻辑交给了 WindowManagerGlobal 类进行处理,那么我们接着看
★ WindowManagerGlobal # addView()

public void addView(View view, ViewGroup.LayoutParams params,
        Display display, Window parentWindow) {
    // 检测View是否为空
    if (view == null) {
        throw new IllegalArgumentException("view must not be null");
    }
    // 检测 display是否为空
    if (display == null) {
        throw new IllegalArgumentException("display must not be null");
    }
    // 再次检测 LayoutParams是否为 WindowManager的 LayoutParams
    if (!(params instanceof WindowManager.LayoutParams)) {
        throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
    }
    // 1、将 LayoutParams强转成 WindowManager的 LayoutParams
    final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
    ....
    synchronized (mLock) {
        ....
        // 2、创建ViewRootImpl,每添加一个View到Window,都会在 WindowManagerGlobal中创建
        // 一个 ViewRootImp;也就是说一个 ViewRootImpl对应 Window中的一个 View
        root = new ViewRootImpl(view.getContext(), display);
        // 3、为添加到 Window的View设置 LayoutParams
        view.setLayoutParams(wparams);
        // 4、将 View、对应的ViewRootImpl、对应的LayoutParams添加到相应的集合中进行管理
        mViews.add(view);
        mRoots.add(root);
        mParams.add(wparams);
    }
    try {
        // 5、最后将 View添加到 ViewRootImpl
        root.setView(view, wparams, panelParentView);
    } catch (RuntimeException e) {
        ....
    }
}

第一步:做一些检测工作,检测 View 、LayoutParams 等的正确性,然后将 LayoutParams 强转成 Window 的 LayoutParams,因为待会是要将 View 添加到 Window 中的,也就是要为 View 设置相应的LayoutParams

第二步:创建一个 ViewRootImpl ,添加到 Window 中的 View 都会对应这个一个 ViewRootImpl ,就是这个 ViewRootImpl 负责 View 的绘制,所以说一个Activity 的 DecorView 也对应者一个ViewRootImpl

第三步:为添加到 Window 中的 View 设置 LayoutParams

第四步:将 View、View对应的 ViewRootImpl、View 对应的 LayoutParams 分别添加到 mViews 、mRoots 、mParams 中进行管理,由此可知 WindowManagerGlobal 管理着添加到 Window 中的 View

第五步:最后将 View 添加到 ViewRootImpl,这样 View 和 ViewRootImpl 就连接起来了
 

Step 3:

到这里, View 已经初始化一系列的工作,但是还没有添加到 Window 、也还没有进行绘制;我们继续看到 ViewRootImpl 中的 setView() 方法中
★ ViewRootImpl # setView()

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    synchronized (this) {
        if (mView == null) {
            // 1、将传递进来的 View保存在 ViewRootImpl中的 mView属性中
            mView = view;
            ....
            // 2、在将View添加到 Window中前先调用 requestLayout()方法异步刷新布局
            // 保证在接收系统的其他事件前能进行重新绘制
            requestLayout();
            ....
            try {
                ....
                // 3、将该View添加到远程的 Session服务中进行显示
                res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                        getHostVisibility(), mDisplay.getDisplayId(),
                        mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                        mAttachInfo.mOutsets, mInputChannel);
            } catch (RemoteException e) {
                ....
            }
            ....
        }
    }
}

现在我们找到答案了,在 ViewRootImpl 中的 setView() 方法中会先进行 View 的绘制,然后将绘制好的 View 添加到 Session 中进行显示
首先,ViewRootImpl 会将 View 保存在 mView 属性中
然后,会调用 requestLayout() 方法,这个方法会进行调度 View 的绘制
最后,会调用 addToDisplay() 方法将 View 添加到远程的 Session 服务,让该服务对 View 进行显示

小结:
我们知道 View 的绘制发生在 requestLayout() 方法中,这个方法会进行调度 View 的绘制;由源码可知,View的绘制是发生在 Activity 的 onResume() 方法之后,也就是说,如果是第一次启动 Activity ,那么在 onCreate() 和 onResume() 方法中是无法直接通过 view.getWidth() 、view.getHeight() 方法获取 View 的宽高的,这一点请注意

好了,现在 View 的绘制、View 添加到 Window 都有了结果;接下来,我们开始分析 View 的绘制入口方法
requestLayout()
 

Step 4:

继续看到 ViewRootImpl 中的 requestLayout() 方法
★ ViewRootImpl # requestLayout()

@Override
public void requestLayout() {
    if (!mHandlingLayoutInLayoutRequest) {
        // 检测当前线程是不是主线程
        checkThread();
        // 标记已经请求刷新布局了
        mLayoutRequested = true;
        // 调用方法去异步刷新布局,绘制View
        // 这个方法会开始调度View的绘制
        scheduleTraversals();
    }
}

首先会检测一下线程,然后就调用 scheduleTraversals() 方法开始调度任务去异步刷新布局,看到这个方法
★ ViewRootImpl # scheduleTraversals()

void scheduleTraversals() {
    if (!mTraversalScheduled) {
        // 标记已经调度了 View的绘制任务
        mTraversalScheduled = true;
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
        // 调度任务开始 View的绘制,View的绘制逻辑保存在 mTraversalRunnable对象中
        mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        if (!mUnbufferedInputDispatch) {
            scheduleConsumeBatchedInput();
        }
        notifyRendererOfFramePending();
        pokeDrawLockIfNeeded();
    }
}
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();

★ ViewRootImpl.TraversalRunnable # 类结构

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

可见,最终会 post 一个 Runnable 给 mChoreographer ,让其执行任务,也就是说当 mChoreographer 执行完该任务后就完成了 View 的绘制
 

Step 5:

看到 ViewRootImpl 的 doTraversal() 方法
★ ViewRootImpl # doTraversal()

void doTraversal() {
    // 由于已经在 scheduleTraversals()方法中设置了View绘制任务调度,所以mTraversalScheduled为true
    if (mTraversalScheduled) {
        mTraversalScheduled = false;
        mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
        if (mProfile) {
            Debug.startMethodTracing("ViewAncestor");
        }
        // 开始绘制View
        performTraversals();
        ....
    }
}

重点来了:在这个方法中会调用 performTraversals() 方法开始绘制 View ,也就是说 View 的绘制入口就是这个 performTraversals() 方法
至此,我们已经知道了 View 的绘制入口的大致流程了,关于 View 绘制的具体流程可以看文章《View的绘制流程之三:View的绘制流程源码分析》


三、总结

  • DecorView 会在 Activity 启动流程中的一个 handleResumeActivity() 方法中被添加到 远程的 WindowManagerService 中

  • 当 DecorView 成功添加到 WindowManagerService 后就会开始绘制 DecorView ,同时也会开始绘制 Activity 的布局;也就是说 Activity 的布局会在这个时候 Activity 的 onResume() 方法之后进行绘制

  • 由于 View 的绘制发生在 Activity 的 onResume() 方法之后,所以如果是第一次启动 Activity ,那么在 onCreate() 和 onResume() 方法中是无法直接通过 view.getWidth() 、view.getHeight() 方法获取 View 的宽高的

  • 在 ViewRootImpl 的 setView() 方法中会调用 requestLayout() 方法进行 View 绘制任务的调度,这个任务就是 ViewRootImpl的内部类 TraversalRunnable,在这个类的 run()方法中会调用 doTraversal() 方法,然后这个方法会调用 performTraversals() 方法进行 DecorView 以及 Activity 的 Layout 布局的绘制;所以 ViewRootImpl中的 performTraversals()方法就是 View的绘制的入口方法

  • 在 ViewRootImpl 的 setView() 方法中会调用 addToDisplay() 方法将 View 添加到远程的 Session 服务,让该服务对 View 进行显示

★ Activity布局绘制的源头流向:

ActivityThread.handleResumeActivity()
ViewManager.addView()
WindowManagerImpl.addView()
WindowManagerGlobal.addView()
ViewRootImpl.setView()
ViewRootImpl.requestLayout()
ViewRootImpl.scheduleTraversals()
ViewRootImpl.TraversalRunnable.run()
ViewRootImpl.doTraversal()
ViewRootImpl.performTraversals()



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值