美文网首页Android开发Android开发经验谈Android技术知识
Android View 的绘制流程 01 - 前置流程

Android View 的绘制流程 01 - 前置流程

作者: __Y_Q | 来源:发表于2020-02-13 22:34 被阅读0次

    之前文集中学习了几个自定义的View, 那么一定还记得三个自定义View的重要流程.

    • measure, (测量, 测量每一个 View 及 ViewGroup 的尺寸 )
    • layout,     (摆放, 根据测量的结果及参数, 在布局上摆放每一个控件)
    • draw,       (绘制, 摆放好位置后, 就开始绘制, 然后显示在屏幕上)

    这个文集也主要是围绕着三个流程来学习. 正式开始.
     

    1. 前置流程

    View 的绘制流程最初就是在 ActivityThread.handleLaunchActivity() 中开始.


     
     

    2.1 ActivityThread.handleLaunchActivity()

    在 Activity 启动的过程中, 会调用 ActivityThread.handleLaunchActivity() 方法.
    handleLaunchActivity( ) 内部调用以下方法.

    1. ActivityThread.performLaunchActivity() - 这个方法会调用 Activity 的 onCreate 方法
    2. ActivityThread.handleResumeActivity() - 这个方法会调用 Activity 的 onResume 方法.(起始)

    这个方法虽是入口, 但是我们重点学习不在这里, 所以就不再贴代码上来, 只是简单说一下流程.


     
     

    1.2 ActivityThread.handleResumeActivity()

    ActivityThread 3599 行

    final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
         ...
        r = performResumeActivity(token, clearHide, reason);
            ...
            if (...) {
                //-------------1----------------
                r.window = r.activity.getWindow();
                View decor = r.window.getDecorView();
                ...
                //------------2----------------
                ViewManager wm = a.getWindowManager();
                WindowManager.LayoutParams l = r.window.getAttributes();
                ...
                ...
                if (...) {
                    if (...) {
                        ...
                        //-------------3--------------
                        wm.addView(decor, l);
                    }
                }
                  ...      
    }
    

    handleResumeActivity 方法中调用了 performResumeActivity, 我猜测里面可能会调用了 Activity 的 onResume 方法, 在这里不是重点, 不再表述.
    重点是下面几行.

    第一部分
    看到 View decor = r.window.getDecorView(); , 又见到了熟悉的 DecorView, 结合 Android 之 setContentView 流程 这个文集, 立刻能联想到 DecorView 的一系列关系.

    • DecorView 在 PhoneWindow 中
    • PhoneWindow 是 Window 的唯一派生类
    • DecorView 是一个 FrameLayout.
    • DecorView 内部有一个布局, 布局内部有一个ID 为 android.id.content 的 ViewGroup.(mContentParent)
    • 执行完 setContentView 后, DecorView 中 mContentParent 被改名为 NO_ID,
    • mContentParent 中包含了一个 SubDecorView ,
    • SubDecorView 中 有一个 ContentFrameLayout, 改名为 android.id.content.
    • 我们调用 setContentView ,传入的资源文件, 就在 SubDecorView 的 ContentFrameLayout 控件中.
    • ...

    怎么样, 有没有想起上面的那些?
    所以说在 View 的 绘制流程中, 和这个 DecorView 有着非常密切的关系.
    那么现在我们可以知道, PhoneWindow 赋值给了 r.window 属性, DecorView 赋值给了 decor 变量.
     
     
    第二部分
    接着看 ViewManager wm = a.getWindowManager();
    a 就是 Activity , Activity 中 getWindowManager() 返回的是 WindowManager 对象 mWindowManager,

    public WindowManager getWindowManager() {
            return mWindowManager;
    }
    public interface WindowManager extends ViewManager {
    }
    

    可是 WindowManager 只是一个接口, ViewManager 也是一个接口, 那么肯定有实现类, 那么继续在 Activity.java 中搜索, 看是实例化的哪个类.
    在 Activity.java 6955 行, 看到 mWindowManager 是在这里被赋值的.
    mWindowManager = mWindow.getWindowManager();.
    接着跟进去, 看 mWindowManager 在 Window.java 769 行中被赋值.
    mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
    那么现在就知道了, WindowManagerImpl 就是 ViewManager 子类的子类 . (WindowManagerImpl 继承自 WindowManager, WindowManager 是一个接口, 又继承自 ViewManager),
    至此, 我们得知, vm 其实可以看做是 WindowManagerImpl 对象

    接着看 WindowManager.LayoutParams l = r.window.getAttributes();
    这个比较简单, 代码跟进去发现就是 获取 PhoneWindow 的窗口属性
     
     
    第三部分
    wm.addView(decor, l);
    调用 WindowManagerImpl.addView 方法, 传入 decorView 和 PhoneWindow 的窗口属性.


     
     

    1.3 WindowManagerImpl.addView(View, ViewGroup.LayoutParams)

    WindowManagerImpl.java 90 行

    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
    @Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
    }
    

    又调用了 WindowManagerGlobal 的addView 方法, 并且传入DecorView 与 params


     
     

    1.4 WindowManagerGlobal.addView(View, ViewGroup.LayoutParams, Display, Window )

    WindowManagerGlobal.java 278行

    //管理所有Activity 的 ViewRootImpl
    private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
    //管理所有 Activity 的 DecorView
    private final ArrayList<View> mViews = new ArrayList<View>();
    
    public void addView(View view, ViewGroup.LayoutParams params,Display display, Window parentWindow) {
        ...
       final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
        ...
        synchronized (mLock) {
            ...
            ViewRootImpl root;
            ...
            //初始化 ViewRootImpl
            root = new ViewRootImpl(view.getContext(), display);
            //添加 DecorView 到 DecorView 集合
            mViews.add(view);
            //添加 ViewRootImpl 到集合
            mRoots.add(root);
            try {
                root.setView(view, wparams, panelParentView);
            } catch (RuntimeException e) {
                ...
                throw e;
            }
        }
    }
    

    WindowManager 维护着所有 Activity 的 DecorView 和 ViewRootImpl .这里初始化了一个 ViewRootImpl 又使用 ViewRootImpl 调用了 setView, 也传入了 DecorView 和经过转换的 WindowManager.LayoutParams.


     
     

    1.5 ViewRootImpl.setView(View , WindowManager.LayoutParams , View)

    ViewRootImpl.java 632 行

    View mView;
    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        //异步刷新View
        synchronized (this) {
            if (mView == null) {
                mView = view;
                ....
                requestLayout();
                ...
                view.assignParent(this);
                ...
            }
        }
    }
    

    首先把 DecorView 赋值给 ViewRootImpl 类成员变量 mView. 这里需要记住 mView 就是 DecorView.
    然后调用了 ViewRootImpl 类 方法 requestLayout(), 请求对页面进行布局, 对View 完成异步刷新, 在其内部执行 View 的绘制方法., 再去看 requestLayout()方法之前, 先看一下 view.assignParent(this); 这个方法将 ViewRootImpl 对象 this 作为参数调用了 DecorView 的 assignParent().

    View.java 16834 行

    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");
        }
    }
    
    • 参数是 ViewParent, 而ViewRootImpl 实现了 ViewParent 接口, 所以在这里就将 DecorView 和 ViewRootImpl 绑定起来了.
    • 每个Activity 的根布局都是 DecorView, 而 DecorView 的 Parent 又是 ViewRootImpl, 所以在子 View 里执行 invalidate() 之类的操作,需要循环找 parent 的时候, 最后都会走到 ViewRootImpl 里.

    现在接着看 ViewRootImpl.requestLayout() 方法.


     
     

    1.6 ViewRootImpl.requestLayout()

    ViewRootImpl.java 1153 行

    @Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }
    

    请求对页面进行布局, 对View 完成异步刷新, 在其内部执行 View 的绘制方法.
    checkThread(); 校验当前所在的线程
    scheduleTraversals() , (当我们自定义 View 调用 invalidate 的时候, 其实最后也是调用了这个方法), 这个方法是屏幕刷新的关键. 一起去一探究竟.


     
     

    1.7 ViewRootImpl.scheduleTraversals()

    Traversals /trəˈvərs(ə)l/ 遍历的意思
    ViewRootImpl.java 1354 行

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

    这里看到调用了 mChoreographer.postCallback 里面传入了三个参数, 第二个参数是一个 Runnable 对象, 继续跟踪到这个 Runnable 中.
    记住这个 boolean类型的变量mTraversalScheduled
    记住这个方法 postSyncBarrier()


     
     

    1.8 ViewRootImpl.mTraversalRunnable

    ViewRootImpl 6730 行

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

    这个 Runnable 在 run 中调用了 doTraversal() 方法, 继续跟踪


     
     

    1.9 ViewRootImpl.doTraversal()

    ViewRootImpl.java 1377 行

    void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
            ...
            //最最关键的方法
            performTraversals();
            ...
        }
    }
    

    boolean 类型变量 mTraversalScheduled 在1.7 中设置为 true, 并且调用了 postSyncBarrier(),
    这里设置为了 false, 调用了 removeSyncBarrier(). 也先记住.
    现在进入最最最关键的方法 performTraversals()


     
     

    1.10 ViewRootImpl.performTraversals() 从这里开始执行三个流程.

    ViewRootImpl.java 1576 行

    private void performTraversals() {
        ...
        if (...) {
            ...
            if (...) {  
                if (...) {
                    int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
                    int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);        
                     // Ask host how big it wants to be
                    //测量
                    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
                                 ...
                    layoutRequested = true;
                }
            }
        } else {
           ...
        }
        final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
        if (didLayout) {
            //摆放
            performLayout(lp, mWidth, mHeight);
                    ...
        }
            ...
        boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible;
    
        if (!cancelDraw && !newSurface) {
            ...
            //绘制
            performDraw();
        } else {
            ...
        }
            ...
    }
    

    performMeasure() 执行测量
    performLayout() 执行摆放
    performDraw() 执行绘制
    跟踪到这里, 终于看到了这三个方法. 这个方法的逻辑很复杂, 每次都会根据一些状态来判断走哪个流程, 有时候可能只执行某一个, 有时候可能三个都执行或者两个. 但是不管哪个流程, 都会遍历一遍View 树, 因此 View 的绘制是需要遍历很多次 View 树, 如果界面非常复杂, 耗时就会久一点. 当然这三个流程在遍历时, 也不一定都会遍历View 树, ViewGroup 在传递的时候, 还会根据响应的状态判断是否继续向下传递.

    int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
    int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
    getRootMeasureSpec : 根据 window 的布局参数计算出 root view 的 measure.
    (可以理解为: 基于 phonewindow 的 layout parms, 算出 DecorView 的 measureSpec )

    mWidth 和 mHeight 是 window (也就是 PhoneWindow) 的高度和宽度.
    lp 是 window 的 LayoutParams 值. lp.width 和 lp.height 默认都是 MATCH_PARENT,
    getRootMeasureSpec() 中又调用了 makeMeasureSpec() 将 高度和宽度值与 MATCH_PARENT 传入. 组合后返回一个采用32位存储的整型值 measureSpec.

    根据在 Android View 的绘制流程 - 开篇 MeasureSpec 中所学习的.
    现在已经得到了 DecorView 的 MeasureSpec了, 包含的模式与值分别如下.

    MeasureSpec 模式 大小
    childWidthMeasureSpec MeasureSpec.EXACTLY window 的宽度值
    childHeightMeasureSpec MeasureSpec.EXACTLY window 的高度值

    下一章将会开始学习 performMeasure() 流程.


     
     

    扩展知识

    • 知识点1

    为什么我们在 Activity.onCreate 与 onResume 中获取不到 View 宽高 ?
    看过上面的流程, 是不是知道了, 打开一个Activity, 当它的 onCreate 和 onResume 执行完后, 才会将它的 DecorView 与新建的一个 ViewRootImpl 绑定起来, 同时开始执行测量, 摆放, 绘制等流程. 执行完测量后, 我们自己 View 中控件才能宽高等属性. 所以, 都还没有进行测量, 甚至连 ViewRootImpl 都没创建,(在1.4中创建的) 怎么会有宽高呢.
    还能得到一个信息是, Activity 界面的绘制, 是在 onResume 之后.

    • 知识点2

    当我们调用 View 的 invalidate() 方法, 执行重绘的时候, 内部也是要层层走到 ViewRootImpl 的 scheduleTraversals 方法里去. 然后这个方法会将遍历绘制 View 树的操作 preformTraversals() 封装到 Runnable 中. 传给 Chorerographer, 以当前的时间戳放进一个 mCallbackQueue 队列中, 然后调用了 native 层方法向底层注册监听下一个屏幕刷新信号事件.

    相关文章

      网友评论

        本文标题:Android View 的绘制流程 01 - 前置流程

        本文链接:https://www.haomeiwen.com/subject/fvrxfhtx.html