Android的View绘制流程浅析

Android绘制整体流程

首次View的绘制流程是在什么时候触发的?

答案在ActivityThread.handleResumeActivity里触发的。

public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
            String reason) {
// 1.执行Activity的onResume方法
 final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
        if (r == null) {
            // We didn't actually resume the activity, so skipping any follow-up actions.
            return;
        }
        // ...
        if (r.window == null && !a.mFinished && willBeVisible) {
            // 2.获取Window
            r.window = r.activity.getWindow();
            // 3.获取DecorView
            View decor = r.window.getDecorView();
            decor.setVisibility(View.INVISIBLE);
            // 4.获取Activity的WindowManager
            ViewManager wm = a.getWindowManager();
            WindowManager.LayoutParams l = r.window.getAttributes();
            a.mDecor = decor;
            // ...
            if (a.mVisibleFromClient) {
                if (!a.mWindowAdded) {
                    a.mWindowAdded = true;
                   // 5.调用windowManager的addView方法
                    wm.addView(decor, l);
                } else {
                  // ...
                }
            }
    }

WindowManager是一个接口,我们查看WindowManagerImpl的addView:

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

其内部调用的是WindowManagerGlobal的addView方法:

public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
   //...
ViewRootImpl root;
synchronized (mLock) {
  //...
 // 创建ViewRootImpl
 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 {
                // 6.调用ViewRootImpl的setView方法
                root.setView(view, wparams, panelParentView);
            } catch (RuntimeException e) {
                // BadTokenException or InvalidDisplayException, clean up.
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
                throw e;
            }
}
}

从源码中可以看到,onResume 之后,ActivityThread 通过调用 activity 中 windowmanager 的 addView 方法,将 decorView 传入到 ViewRootImpl 的 setView 方法中,通过 setView 来完成 View 的绘制。

ViewRootImpl 是如何绘制 View 的

我们再来看一下 setView 方法

// ViewRootImpl.setView   这里入参是DecorView
    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
      // ...
      requestLayout();
    }

@Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            // 1.线程检查
            checkThread();
            mLayoutRequested = true;
           // 执行绘制任务
            scheduleTraversals();
        }
    }

void scheduleTraversals() {  
        // 获取内存屏障
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
      // 执行绘制任务
        mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
    }

经过层层调用,绘制的任务落在了mChoreographer的身上,先看看mTraversalRunnable,从名字可以看出来跟Runnable有关系:

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

void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
// 移除内存屏障                          
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
            // 开始绘制--依次调用 performMeasure --performLayout --persformDraw
            performTraversals();
            // ...
        }
    }

简单来说 setView 做了三件事

① 检查绘制的线程是不是创建 View 的线程。
② 通过同步屏障保证绘制 View 的任务是最优先的
③ 调用 performTraversals 完成 measure,layout,draw 的绘制

后续 ViewGroup 和 View 的绘制其实是 performTraversals 对整个 ViewTree 的绘制。他们的关系可以用下面这张图表示


3381990-ae0232c07b6a52cc.png

既然View 的绘制是在 onResume 之后才开始的,那为什么我在 onCreate 中调用 View.post 方法可以得到 View 的宽高呢

查看view的post代码:

public boolean post(Runnable action) {
        // 如果View已经被attachToWindow就直接post到handler中
        final AttachInfo attachInfo = mAttachInfo;
        if (attachInfo != null) {
            return attachInfo.mHandler.post(action);
        }
        // 否则就放到队列中,等到attached之后再执行
        getRunQueue().post(action);
        return true;
    }

View.post 会判断当前 View 是否已经被添加到 window 上。如果添加了则立即执行 runnable,如果没有被添加则先放到一个队列中存储起来,等添加到 window 上时再执行。
而 View 被测量完成后才会 attachToWindow。所以当 post 的 runnable 执行时,View 已经绘制完成了。

MeasureSpec 的理解

View 的大小不仅仅取决于自身的宽高,还取决于父 View 的大小和测量模式。一个 200x200 的父 View 是不可能容纳一个 300x300 的子 View 的,父 View 的 wrap_content 和 match_content 也会影响子 View 的大小。
所以 View 的 measure 函数其实应该有 4 个参数:父 View 的宽,父 View 的高,宽的测量模式,高的测量模式。

那我自定义一个 View 的时候,如果不对 MeasureSpec 做处理。使用这个 View 时宽高传入 wrap_content,结果会怎么样?

查看view的默认的onMeasure():

#View.java
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }
#View.getDefaultSize
public static int getDefaultSize(int size, int measureSpec) {
        int result = size;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        switch (specMode) {
        case MeasureSpec.UNSPECIFIED:
            result = size;
            break;
        case MeasureSpec.AT_MOST:
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
        }
        return result;
    }

所以当我们自定义 View 时,如果没有对 MODE 做处理,设置 wrap_content 和 match_content 结果其实是一样的,View 的宽高都是取父 View 的宽高。

invalidate 和 requestlayout 方法的区别
//ViewRootImpl.invalidate
@UnsupportedAppUsage
    void invalidate() {
        mDirty.set(0, 0, mWidth, mHeight);
        if (!mWillDrawSoon) {
            scheduleTraversals();
        }
    }
#ViewRootImpl.requestLayout
    @Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }

ViewRootImpl 作为顶级 View 负责 View 的绘制。所以简单来说,requestlayout 和 invaliate 最终都会向上回溯调用到 ViewRootImpl 的 postTranversals 方法来绘制 View。
不同的是 requestlayout 会绘制 View 的 measure,layout 和 draw 过程。invaliate 因为只添加了绘制 draw 的标志位,只会绘制 draw 过程。

实现一下 findViewbyid 的过程
 protected  T findViewTraversal(@IdRes int id) {
        if (id == mID) {
            return (T) this;
        }

        if(mView instanceOf ViewGroup){
           int childrenCount = getChildrenCount();
        for (int i = 0; i < childrenCount; i++) {
            View v = getChildAt(i);

            if ((v.mPrivateFlags & PFLAG_IS_ROOT_NAMESPACE) == 0) {
                v = v.findViewById(id);

                if (v != null) {
                    return (T) v;
                }
            }
        }
        }
        
        return null;
    }

参考:
View绘制原理

你可能感兴趣的:(Android的View绘制流程浅析)