view.post()和handler.post()到底做了什么

469 阅读6分钟

本文已参与好文召集令活动,点击查看:后端、大前端双赛道投稿,2万元奖池等你挑战!

前言

在开发的过程中,经常会遇到或者使用如下的代码,代码很稀松平常,感觉不到有什么可说的,但是隐藏在这些普通的方法之下,却又包含了很多的知识点需要我们注意,希望通过这篇文章来重新认识一下这几个方法,同时可以带来一些新的启发。

 handler.post(new Runnable() {
     @Override
     public void run() {
         System.out.println("Handler.post===");
     }
 });
 // 代码2
 textView.post(new Runnable() {
     @Override
     public void run() {
         System.out.println("View.post===");
     }
 });

Handler.post()

Handler机制中有很多知识点是很重要的,比如说IdleHandler同步屏障等等,当我们在通过handler发送消息时,除了sendMessage(),还可以通过post()的方式来发送消息。我们看一下post方法的源码是怎么实现的。

    public final boolean post(@NonNull Runnable r) {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }

通过构造一个Message消息m,然后给消息m的callback属性赋值。

    private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        return m;
    }

在处理消息的时候,先判断callback属性是否为空,如果不为空,则执行callback的run方法,否则去执行handleMessage方法进行消息处理。

    public void dispatchMessage(@NonNull Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }
    //执行callback的run方法
    private static void handleCallback(Message message) {
        message.callback.run();
    }

这样一看,handler的post方法原理其实也很简单,只是在构造message消息的时候,做了一些不同的处理而已。

View.post()

问题

首先想一个问题,当我们想在Activity的onCreate()方法中获取view的宽高时,应该怎么操作呢?我们知道要想获取一个view的宽高,首先view需要经过测量,也就onMeasure()之后,view才有了宽和高,这个方法是在哪里调用的呢?是到了onResume()方法以后才会调用。也就是说onCreate的时候,view实际上是没有宽高的。

view的测量时机在哪里

今天我们就从头开始扒一扒view从创建到测量的方法栈,我们知道ActivityThread的main()函数是整个App的入口,activity的onResume方法的源头大概似乎也应该在这里找一找,为什么找onResume,因为view是从onResume之后才开始进行绘制,测量方法的源头大概似乎也应该在这里找一找,为什么找onResume,因为view是从onResume之后才开始进行绘制的。

ActivityThread.java
    
    //resume方法的源头
    @Override
    public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
            String reason) {

        // TODO Push resumeArgs into the activity for consideration
        //调用activity的onResume方法
        final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
       ...
        if (r.window == null && !a.mFinished && willBeVisible) {
            ...
            ViewManager wm = a.getWindowManager();
            WindowManager.LayoutParams l = r.window.getAttributes();
           ...
            if (a.mVisibleFromClient) {
                if (!a.mWindowAdded) {
                    a.mWindowAdded = true;
                    //添加decor到WindowManager中,这里会调用其实现类WindowManagerImpl的add方法
                    wm.addView(decor, l);
                } 
            }
           ...
    }

找到了handleResumeActivity这个方法,并在其中调用了ViewManager的addView方法,ViewManger是一个接口,通过其实现类WindowManagerImpladdView方法来具体实现

WindowManagerImpl.java

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

然后桥接给WindowManagerGlobla,执行WindowManagerGlobla的addView方法。

WindowManagerGlobla.java
    
    public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
       ...
       //声明ViewRootImpl
        ViewRootImpl root;
        View panelParentView = null;

        synchronized (mLock) {
           
            root = new ViewRootImpl(view.getContext(), display);
              ...
            // do this last because it fires off messages to start doing things
            try {
                //调用viewRootImpl的setView方法
                root.setView(view, wparams, panelParentView);
            } 
            ...
        }
    }

用一张图来直观的看一下调用的线路:

image.png

从下边开始,就开始要进入界面的绘制渲染阶段了

ViewRootImpl.java

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
            ...
            //view的绘制流程
            requestLayout(); 
            //创建InputChannel
            if ((mWindowAttributes.inputFeatures
                        & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
                    mInputChannel = new InputChannel();
                }
                
            //通过windowSession进行IPC调用,将view添加到window上,
            //同时通过InputChannel接收触摸事件回调
            res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(), mWinFrame,
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel);
            ...
            //处理触摸事件回调
             mInputEventReceiver = new WindowInputEventReceiver(mInputChannel,
                            Looper.myLooper());
            ...
        }
}
    
    
     @Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            //检查是否是主线程
            checkThread();
            mLayoutRequested = true;
            //执行绘制的操作
            scheduleTraversals();
        }
    }
    
    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            
            //添加同步屏障
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            
            //向mChoreographer发送一个回调TraversalRunnable,等待接收vsync信号执行回调
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            ...
        }
    }
    
    //具体的回调
    final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            //等待执行run方法
            doTraversal();
        }
    }
    
    void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            //移除屏障
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
            ...
            //进行绘制的
            performTraversals();
            ...
        }
    }
    
     private void performTraversals() {
         ...
         //测量
         performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
         //布局
         performLayout(lp, mWidth, mHeight);
         //绘制
         performDraw();
         ...
     }

以上就是绘制的具体流程,只有当执行完performMeasure测量以后,view才有了宽高。我们接着上一个图来继续捋一捋。将一个调用的全过程简略的画一下。

image.png

view.post()源码分析

下面来看看view.post的具体实现,

public boolean post(Runnable action) {
        final AttachInfo attachInfo = mAttachInfo;
        //如果有attachInfo的话,就会调用handler的post方法,跟普通的消息没有什么区别
        if (attachInfo != null) {
            return attachInfo.mHandler.post(action);
        }
        //如果attachInfo没有值,则执行下面的代码,放入队列中
        getRunQueue().post(action);
        return true;
    }
    //得到一个HandlerActionQueue的对象
     private HandlerActionQueue getRunQueue() {
        if (mRunQueue == null) {
            mRunQueue = new HandlerActionQueue();
        }
        return mRunQueue;
    }

//执行任务,发送任务都在这个类里了
public class HandlerActionQueue {
    private HandlerAction[] mActions;
    private int mCount;
    //调用postDelayed方法,延迟时间为0
    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++;
        }
    }
    
    //执行任务
    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;
        }
    }

view的post方法很简单,没有多少代码,最终会通过postDelayed发送一个任务,通过executeActions执行一个任务,所以下一步就来看看executeActions这个方法在哪里执行就可以了。点进去发现在dispatchAttachedToWindow这个方法中会有调用,这个方法又在ViewRootImpl中有调用,我们再回头看看这个performTraversals方法。

ViewRootImpl.java

     private void performTraversals() {
         ...
         //执行任务的源头
          host.dispatchAttachedToWindow(mAttachInfo, 0);
         ...
         //测量
         performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
         //布局
         performLayout(lp, mWidth, mHeight);
         //绘制
         performDraw();
         ...
     }

好了,我们已经找到源头了,在ViewRootImpl的performTraversals这个方法中,但是是在performMeasure这个方法之前调用的啊,为什么可以拿到宽高呢? 我们仔细看一下executeActions这个方法的实现

for (int i = 0, count = mCount; i < count; i++) {
    final HandlerAction handlerAction = actions[i];
    //通过handler发送
    handler.postDelayed(handlerAction.action, handlerAction.delay);
}

通过hendler发送消息的方式,最终将消息插入到消息队列中来执行。所以执行任务的方法最终执行的时机是在measure之后的,消息在队列中总要有个先来后到不是么。

结语

本文,我们分析了view的绘制流程、handler的post、view的post方法,通过这些分析,对view的整体绘制流程应该有了一个宏观的印象了吧,当然view的绘制牵涉到很多其他的知识,我只是将其中的一部分拿出来说明而已,为什么可以在view的post方法中可以拿到宽高,相信你也有了答案。这些源码其实并不难,只要花费精力去研究它,就会有所收货和启发。

参考资料