美文网首页Android自定义View
Android View 绘制流程 ( 一 ) Measure

Android View 绘制流程 ( 一 ) Measure

作者: 是刘航啊 | 来源:发表于2020-12-29 15:56 被阅读0次
    首先思考一个问题
    public class V8Activity extends Activity {
        private String TAG = "V8Activity";
        ActivityV8Binding v8Binding;
        
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            initBinding();
            //Log-1
            Log.d(TAG, "Button Height onCreate:" + v8Binding.btn.getMeasuredHeight());
            v8Binding.btn.post(new Runnable() {
                @Override
                public void run() {
                    //Log-2
                    Log.d(TAG, "Button Height post:" + v8Binding.btn.getMeasuredHeight());
                }
            });
        }
    
        private void initBinding() {
            v8Binding = DataBindingUtil.setContentView(this, R.layout.activity_v8);
        }
    
        protected void onResume() {
            super.onResume();
            //Log-3
            Log.d(TAG, "Button Height onResume:" + v8Binding.btn.getMeasuredHeight());
        }
    }
    

    Log - 1 打印值为多少 ?
    Log - 2 打印值为多少 ?
    Log - 3 打印值为多少 ?

    运行结果 -> 「Log1 = 0」「Log2 = 132」「Log3 = 0」
    为什么会产生这样的结果呢?下面一个一个来分析

    Log1 打印 0 原因

    之前分析了 setContentView 源码,setContentView 是将布局加载到 DecorView 中,这个时候 View 还没有测量,所以 Log1 打印为 0。

    setContentView 源码解析

    Log3 为什么打印 0

    追一下源码
    ActivityThread -> handleResumeActivity
    public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
                String reason) {
        ...
        final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
        ...
        wm.addView(decor, l);
        ...
    }
    

    performResumeActivity() -> ActivityThread. performResumeActivity()

    ActivityThread -> performResumeActivity
    public ActivityClientRecord performResumeActivity(IBinder token, boolean finalStateRequest,
                String reason) {
        ...
        r.activity.performResume(r.startsNotResumed, reason);
        ...
    }
    

    r.activity.performResume() -> Activity.performResume()

    Activity -> performResume
    final void performResume(boolean followedByPause, String reason) {
        ...
        mInstrumentation.callActivityOnResume(this);
        ...
    }
    

    mInstrumentation.callActivityOnResume() -> Instrumentation.callActivityOnResume()

    Instrumentation -> callActivityOnResume
    public void callActivityOnResume(Activity activity) {
        activity.mResumed = true;
        activity.onResume();
            
        if (mActivityMonitors != null) {
            synchronized (mSync) {
                final int N = mActivityMonitors.size();
                for (int i=0; i<N; i++) {
                    final ActivityMonitor am = mActivityMonitors.get(i);
                    am.match(activity, activity, activity.getIntent());
                }
            }
        }
    }
    

    最后调用到 Activity 的 onResume 方法

    handleResumeActivity 执行过程
    1. performResumeActivity
    2. wm.addView
    1. Activity 执行 onResume
    2. 将 DecorView 添加到 WindowManager
    View 的绘制流程在 wm.addView 才开始,View 的绘制流程是在 Activity onResume 之后

    Log2 打印数值

    view.post() 入口
    /**
     * <p>Causes the Runnable to be added to the message queue.
     * The runnable will be run on the user interface thread.</p>
     *
     * @param action The Runnable that will be executed.
     *
     * @return Returns true if the Runnable was successfully placed in to the
     *         message queue.  Returns false on failure, usually because the
     *         looper processing the message queue is exiting.
     *
     * @see #postDelayed
     * @see #removeCallbacks
     */
    public boolean post(Runnable action) {
        final AttachInfo attachInfo = mAttachInfo;
        if (attachInfo != null) {
            return attachInfo.mHandler.post(action);
        }
    
        // Postpone the runnable until we know on which thread it needs to run.
        // Assume that the runnable will be successfully placed after attach.
        getRunQueue().post(action);
        return true;
    }
    

    The runnable will be run on the user interface thread -> runnable 会在主线程运行

    很明显,这里的 attachInfo 并没有赋值,所以会走 getRunQueue().post() 方法

    getRunQueue()
    private HandlerActionQueue getRunQueue() {
        if (mRunQueue == null) {
            mRunQueue = new HandlerActionQueue();
        }
        return mRunQueue;
    }
    

    获取了 HandlerActionQueue 对象

    HandlerActionQueue -> post
    public void post(Runnable action) {
        postDelayed(action, 0);
    }
    
    public void postDelayed(Runnable action, long delayMillis) {
        final HandlerAction handlerAction = new HandlerAction(action, delayMillis);
    
        synchronized (this) {
            if (mActions == null) {
                mActions = new HandlerAction[4];
            }
            mActions = GrowingArrayUtils.append(mActions, mCount, handlerAction);
            mCount++;
        }
    }
    

    将 runnable 转换成 HandlerAction 对象,然后缓存起来,大致可以理解为
    HandlerAction 是 runnable 的一个封装类。

    这里只是将消息缓存,并没有执行。
    所以带着 2 点疑问,什么时候会执行 run 方法 ?在哪个位置会触发执行的方法 ?

    什么时候会执行 run 方法

    getRunQueue() 方法是在 View 中执行的,在 View 中搜索就可找到 dispatchAttachedToWindow 方法

    dispatchAttachedToWindow
    void dispatchAttachedToWindow(AttachInfo info, int visibility) {
        mAttachInfo = info;
        ...
        if (mRunQueue != null) {
            mRunQueue.executeActions(info.mHandler);
            mRunQueue = null;
        }
        ...
    }
    

    mAttachInfo 在 dispatchAttachedToWindow 中才初始化

    mRunQueue -> executeActions
    public void executeActions(Handler handler) {
        synchronized (this) {
            final HandlerAction[] actions = mActions;
            for (int i = 0, count = mCount; i < count; i++) {
                final HandlerAction handlerAction = actions[I];
                handler.postDelayed(handlerAction.action, handlerAction.delay);
            }
    
            mActions = null;
            mCount = 0;
        }
    }
    

    解答上面的 2 点疑问,executeActions 会执行 run 方法( 通过 handler ),dispatchAttachedToWindow 会执行 executeActions 方法。

    接着 View 的绘制流程 wm.addView() 往下看

    wm -> Activity.getWindowManager() -> Window.getWindowManager() -> WindowManagerImpl
    wm.addView() -> WindowManagerImpl.addView() -> WindowManagerGlobal.addView()

    WindowManagerGlobal -> addView
     public void addView(View view, ViewGroup.LayoutParams params,
                Display display, Window parentWindow, int userId) {
        ...
        root = new ViewRootImpl(view.getContext(), display);
        root.setView(view, wparams, panelParentView, userId);
        ...
    }
    
    ViewRootImpl
    public ViewRootImpl(Context context, Display display) {
        this(context, display, WindowManagerGlobal.getWindowSession(),
                false /* useSfChoreographer */);
    }
    
    public ViewRootImpl(Context context, Display display, IWindowSession session) {
        this(context, display, session, false /* useSfChoreographer */);
    }
    
    public ViewRootImpl(Context context, Display display, IWindowSession session,
                boolean useSfChoreographer) {
        ...
        mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this,
                    context);
        ...
    }
    

    在 ViewRootImpl 的构造方法中初始化了 AttachInfo

    root.setView() -> ViewRootImpl.setView()

    ViewRootImpl -> setView
    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
                int userId) {
        ...
        requestLayout();
        ...
    }
    
    ViewRootImpl -> requestLayout
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }
    
    ViewRootImpl -> scheduleTraversals
    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }
    
    mTraversalRunnable -> run
    final class TraversalRunnable implements Runnable {
            @Override
            public void run() {
                doTraversal();
            }
        }
    final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
    
    ViewRootImpl -> doTraversal
    void doTraversal() {
            if (mTraversalScheduled) {
                mTraversalScheduled = false;
                mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
    
            if (mProfile) {
                Debug.startMethodTracing("ViewAncestor");
            }
    
            performTraversals();
    
            if (mProfile) {
                Debug.stopMethodTracing();
                mProfile = false;
            }
        }
    }
    
    ViewRootImpl -> performTraversals
    private void performTraversals() {
        final View host = mView;
        ...
        host.dispatchAttachedToWindow(mAttachInfo, 0);
        getRunQueue().executeActions(mAttachInfo.mHandler);
        ...
        performMeasure();
        performLayout();
        performDraw();
        ...
    }
    

    wm.addView(DecorView) -> windowManagerGlobal.addView(DecorView) ->
    root.setView(DecorView)
    所以这里的 host 其实是 DecorView

    host.dispatchAttachedToWindow() -> DecorView.dispatchAttachedToWindow(),但是 DecorView 并没有实现这个方法,接着往上走,最终在 ViewGroup 中找到 dispatchAttachedToWindow 方法。

    ViewGroup -> dispatchAttachedToWindow
    void dispatchAttachedToWindow(AttachInfo info, int visibility) {
        mGroupFlags |= FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW;
        super.dispatchAttachedToWindow(info, visibility);
        mGroupFlags &= ~FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW;
    
        final int count = mChildrenCount;
        final View[] children = mChildren;
        for (int i = 0; i < count; i++) {
            final View child = children[I];
            child.dispatchAttachedToWindow(info,
                    combineVisibility(visibility, child.getVisibility()));
        }
        final int transientCount = mTransientIndices == null ? 0 : mTransientIndices.size();
        for (int i = 0; i < transientCount; ++i) {
            View view = mTransientViews.get(i);
            view.dispatchAttachedToWindow(info,
                    combineVisibility(visibility, view.getVisibility()));
        }
    }
    

    ViewGroup 的 dispatchAttachedToWindow 方法会便利子 View 的 dispatchAttachedToWindow,而持有的 AttachInfo 对象是同一个,而 AttachInfo 持有的是 ViewRootImpl 中的 mHandler 对象,所以可以得出一个结论 executeActions 中的消息都是由 ViewRootImpl 的 mHandler 对象统一处理的。

    ViewRootImpl -> performTraversals
    private void performTraversals() {
        final View host = mView;
        ...
        host.dispatchAttachedToWindow(mAttachInfo, 0);
        getRunQueue().executeActions(mAttachInfo.mHandler);
        ...
        //View 的绘制流程
        performMeasure();
        performLayout();
        performDraw();
        ...
    }
    

    接着看 performTraversals 方法

    View.post() 能拿到数值,而 dispatchAttachedToWindow 方法不是在 View 的绘制流程之前调用的吗 ?这个时候我们可以用结果推断一下,View.post() 应该是在测量流程结束之后才执行的。
    这个时候需要了解 Android 的消息机制。
    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }
    
    public void executeActions(Handler handler) {
        synchronized (this) {
            final HandlerAction[] actions = mActions;
            for (int i = 0, count = mCount; i < count; i++) {
                final HandlerAction handlerAction = actions[I];
                handler.postDelayed(handlerAction.action, handlerAction.delay);
            }
    
            mActions = null;
            mCount = 0;
        }
    }
    

    handler 先发送的 mTraversalRunnable ,之后才发送的 handlerAction.action (也就是我们 Activity 里的 runnable ),所以会在 View 的绘制流程结束之后执行我们 Activity 中的 runnable。

    模仿源码写了一个小例子,更容易理解绘制流程这一块
    public class V8Test1 {
        private Context context;
        private V8Test2.AttachInfoTest attachInfoTest;
        private V8Test2 mTest;
        
        Handler handler = new Handler();
        public V8Test1(Context context) {
            this.context = context;
            attachInfoTest = new V8Test2.AttachInfoTest(context,handler);
        }
    
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                Log.d("response","Test1 执行=====");
                performTraversals();
            }
        };
    
        void scheduleTraversals() {
            handler.post(runnable);
        }
        
        private void performTraversals() {
            final V8Test2 host = mTest;
            host.dispatchAttachedToWindow(attachInfoTest);
            Log.d("response","Test1 执行 Measure");
            Log.d("response","Test1 执行 Layout");
            Log.d("response","Test1 执行 draw");
        }
    
        public void setTest(V8Test2 mTest) {
            this.mTest = mTest;
        }
    }
    

    V8Test1 类对应就是 ViewRootImpl

    public class V8Test2 {
        AttachInfoTest attachInfoTest;
        final static class AttachInfoTest {
    
            final Handler handler;
            final Context context;
    
            AttachInfoTest(Context mContext, Handler mHandler) {
                context = mContext;
                handler = mHandler;
            }
    
        }
        
        void dispatchAttachedToWindow(AttachInfoTest info) {
            this.attachInfoTest = info;
            executeActions(attachInfoTest.handler);
        }
    
        public void executeActions(Handler handler) {
            synchronized (this) {
                handler.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        Log.d("response","Test2执行=====");
                    }
                }, 0);
            }
        }
    }
    

    V8Test2 类对应就是 View,其中的 executeActions 对应 HandlerActionQueue 的 executeActions 方法

    测试代码
    V8Test1 v8Test1 = new V8Test1(this);
    v8Test1.setTest(new V8Test2());
    v8Test1.scheduleTraversals();
    

    new V8Test1 -> 对应 windowManagerGlobal.addView() 方法中的 new ViewRootImpl

    v8Test1.setTest -> 对应 windowManagerGlobal.addView() 方法中的 root.setView()

    v8Test1.scheduleTraversals 对应 ViewRootImpl 中的 scheduleTraversals

    测试结果
    response: Test1 执行=====
    response: Test1 执行 Measure
    response: Test1 执行 Layout
    response: Test1 执行 draw
    response: Test2执行=====
    

    测试结果和我们预测的结果一致。

    Github 源码链接

    接下来就是 View 绘制流程的第一步 measure
    private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
            if (mView == null) {
                return;
            }
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
            try {
                mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
            } finally {
                Trace.traceEnd(Trace.TRACE_TAG_VIEW);
            }
        }
    

    mView.measure() -> View.measure()

    View -> measure
    public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
        ...
        onMeasure(widthMeasureSpec, heightMeasureSpec);
        ...
    }
    
    View -> onMeasure (这里我们以 LinearLayout 为例子)
    public class LinearLayout extends ViewGroup {
        ...
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            if (mOrientation == VERTICAL) {
                measureVertical(widthMeasureSpec, heightMeasureSpec);
            } else {
                measureHorizontal(widthMeasureSpec, heightMeasureSpec);
            }
        }
        ...
    }
    

    根据方向处理不同的方法,我们看 measureVertical 方法

    LinearLayout -> measureVertical
    void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
        ...
        for (int i = 0; i < count; ++i) {
            ...
            measureChildBeforeLayout(child, i, widthMeasureSpec, 0,
                            heightMeasureSpec, usedHeight);
            ...
        }
        ...
    }
    
    LinearLayout -> measureChildBeforeLayout
    void measureChildBeforeLayout(View child, int childIndex,
                int widthMeasureSpec, int totalWidth, int heightMeasureSpec,
                int totalHeight) {
        measureChildWithMargins(child, widthMeasureSpec, totalWidth,
                heightMeasureSpec, totalHeight);
    }
    
    LinearLayout -> measureChildWithMargins
    protected void measureChildWithMargins(View child,
                int parentWidthMeasureSpec, int widthUsed,
                int parentHeightMeasureSpec, int heightUsed) {
        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
        
        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                    mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
                            + widthUsed, lp.width);
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                    mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
                            + heightUsed, lp.height);
    
        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }
    

    调用 child.measure 方法,如果 child 为 LinearLayout,则继续进入 LinearLayout 的 onMeasure 方法。

    childWidthMeasureSpec, childHeightMeasureSpec 这 2 个参数分别对象宽高,下面记录一下测量模式的计算 -> getChildMeasureSpec()

    measure 流程
    ViewGroup -> getChildMeasureSpec
    public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
        int specMode = MeasureSpec.getMode(spec);
        int specSize = MeasureSpec.getSize(spec);
        
        int size = Math.max(0, specSize - padding);
        int resultSize = 0;
        int resultMode = 0;
    
        switch (specMode) {
            case MeasureSpec.EXACTLY:
                if (childDimension >= 0) {
                    resultSize = childDimension;
                    resultMode = MeasureSpec.EXACTLY;
                } else if (childDimension == LayoutParams.MATCH_PARENT) {
                    // Child wants to be our size. So be it.
                    resultSize = size;
                    resultMode = MeasureSpec.EXACTLY;
                } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                    // Child wants to determine its own size. It can't be
                    // bigger than us.
                    resultSize = size;
                    resultMode = MeasureSpec.AT_MOST;
                }
                break;
            
            case MeasureSpec.AT_MOST:
                if (childDimension >= 0) {
                    // Child wants a specific size... so be it
                    resultSize = childDimension;
                    resultMode = MeasureSpec.EXACTLY;
                } else if (childDimension == LayoutParams.MATCH_PARENT) {
                    // Child wants to be our size, but our size is not fixed.
                    // Constrain child to not be bigger than us.
                    resultSize = size;
                    resultMode = MeasureSpec.AT_MOST;
                } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                    // Child wants to determine its own size. It can't be
                    // bigger than us.
                    resultSize = size;
                    resultMode = MeasureSpec.AT_MOST;
                }
                break;
    
            case MeasureSpec.UNSPECIFIED:
                if (childDimension >= 0) {
                    // Child wants a specific size... let him have it
                    resultSize = childDimension;
                    resultMode = MeasureSpec.EXACTLY;
                } else if (childDimension == LayoutParams.MATCH_PARENT) {
                    // Child wants to be our size... find out how big it should
                    // be
                    resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                    resultMode = MeasureSpec.UNSPECIFIED;
                } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                    // Child wants to determine its own size.... find out how
                    // big it should be
                    resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                    resultMode = MeasureSpec.UNSPECIFIED;
                }
                break;
            }
            //noinspection ResourceType
            return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
        }
    }
    

    这里拿的是父布局的 Mode 和 size,然后根据父布局的 mode 去决定子 View 的 mode 和 size。所以子 View 的 mode 和 size 是由子 View 和父布局共同决定的。一个一个分析

    当父布局 Mode 为 MeasureSpec.EXACTLY ( match_parent,100dp ) 时

    子 View childSize > 0,子 View mode = EXACTLY,size = childSize

    子 View childSize = MATCH_PARENT,子 View mode = EXACTLY,size = parentSize

    子 View childSize = WRAP_CONTENT,子 View mode = AT_MOST,size = parentSize

    当父布局 Mode 为 MeasureSpec. AT_MOST ( wrap_content ) 时

    子 View childSize > 0,子 View mode = EXACTLY,size = childSize

    子 View childSize = MATCH_PARENT,子 View mode = AT_MOST,size = parentSize

    子 View childSize = WRAP_CONTENT,子 View mode = AT_MOST,size = parentSize

    当父布局 Mode 为 MeasureSpec. UNSPECIFIED ( 基本不会用到 ) 时

    子 View childSize > 0,子 View mode = EXACTLY,size = childSize

    子 View childSize = MATCH_PARENT,子 View mode = UNSPECIFIED,size = parentSize

    子 View childSize = WRAP_CONTENT,子 View mode = UNSPECIFIED,size = parentSize

    测量模式

    确定子 View 的 mode 和 size。

    确定子 View 的 mode 和 size 之后,LinearLayout 循环会叠加每个子 View 的高度,最终通过 setMeasuredDimension 方法确定宽高。
    LinearLayout -> measureVertical
    void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
        ...
        setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
                    heightSizeAndState);
        ...
    }
    
    如果是自定义View 就需要重写 onMeasure 方法
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
        getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }
    
    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;
    }
    

    如果 onMeasure 方法没有设置宽高,那么会调用 getDefaultSize 设置一个默认的宽高

    总结

    Measure 流程是确定 View 的大小,如果有子 View 会先确定子 View 的大小,最后通过子 View 的大小确定父容器的大小。

    到这里 View 绘制流程 Measure 流程 到这里就介绍完了,如果有什么写得不对的,可以在下方评论留言,我会第一时间改正。

    相关文章

      网友评论

        本文标题:Android View 绘制流程 ( 一 ) Measure

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