十六、View.post和Handler.post的区别

View.post和Handler.post

1.View.post的实现

首先我们还是先使用View.post的常用场景:

class MainActivity : AppCompatActivity() {
    private val TAG = "MainActivity"
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val postBtn = findViewById<Button>(R.id.post_btn)
        Log.d(TAG,"postBtn width = "+postBtn.width+"  height = "+postBtn.height)

        postBtn.post {
            Log.d(TAG, "post postBtn width = " + postBtn.width + "  height = " + postBtn.height)
        }
    }

我们看一下打印的结果:

2021-08-31 12:27:03.523 31182-31182/com.jhzl.viewpost D/MainActivity: postBtn width = 0  height = 0
2021-08-31 12:27:03.673 31182-31182/com.jhzl.viewpost D/MainActivity: post postBtn width = 333  height = 96

为什么会有这个差异呢?

我们可以分析一下view.post里面都做了什么,从源码找到答案:

    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;
    }

从这里可以看到,主要的两个信息,一是如果内部有attachInfo,会直接用handler post,如果没有,会被添加到一个RunQueue里面。那么就有下图:

在这里插入图片描述

在这里,就要分两步走了:

  1. attachinfo是哪里赋值的?
  2. getrunnerqueue是做什么的?

1.1 attachinfo怎么赋值的

在view里面搜索mAttachInfo = 可以得知,是在dispatchAttachedToWindow里面赋值的

View.java  
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
        mAttachInfo = info;
        ....
    }

那么dispatchAttachedToWindow又是在哪调用的呢?

ViewRootImpl
private void performTraversals() {
  ...
    View host = mView;
   	host.dispatchAttachedToWindow(mAttachInfo, 0);
  ...
    performMeasure();
    performLayout();
    preformDraw()
}

翻看源码,可以发现如下方法。需要说明的是performTraversals这个方法里面,其实还performMeasure层序测量view、层序layout viewgroup的view,层序的draw子view。

那么这个mView又是哪里赋值的呢?搜索ViewRootImpl,我们发现了如下代码:

ViewRootImpl.java
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView){
     mView = view;
 }

那么这个setView的调用代码呢?

WindowManagerGlobal.java
public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow){
   		...
   		 ViewRootImpl root;
   	   root = new ViewRootImpl(view.getContext(), display);
   
        root = new ViewRootImpl(view.getContext(), display);

        view.setLayoutParams(wparams);

        mViews.add(view);
        mRoots.add(root);
        mParams.add(wparams);
        root.setView(view, wparams, panelParentView);

 }

不过,WindowManagerGlobal我们是无法直接使用到的,通常需要通过WindowManager的实现类代理一下:

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

通过上面的代码分析,我们可以知道,ViewRootImpl的mView是在WindowManager.addView时,赋值给ViewRootImpl。addView之后,会调用performTranvels来把attachInfo这个对象,分发到每一个View。view.post时,就调用了attachInfo.mHandler.post(msg)。画个图:

在这里插入图片描述

回到最开始的问题(View的attachInfo是怎么设置的),这里就解释明白了。我们知道View的attachInfo是怎么设置了,那我们最开始的流程就可以增加一个节点了:

在这里插入图片描述

1.1.1 ViewGroup的dispatchAttachedToWindow

我们知道ViewRootImpl的view,要么是view,要么是viewgroup。前面view的dispatchAttachedToWindow我们已经介绍过了,这里我们来看看viewgroup的。

    @Override
    @UnsupportedAppUsage
    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()));
        }
    }

​ 这里有一个概念需要解释一下,mTransientIndices里面装的是暂态view的列表,暂态View即通过ViewGroup# addTransientView方法添加的View,该View无法获得焦点,不能响应事件等,它纯粹只用于显示效果,类似补间动画中的view,无法响应事件。

​ 解释完这个,我们看到,其实view分两部分,一部分是children,会把attachInfo分发给每个子view或者viewgroup。另一部分是mTransientViews也会收到attachInfo。

2. GetRunnerQueue().add(new Runnable())

那这部分是怎么处理的呢?

我们继续看View的源代码:

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

上述的代码很简单,就是获取一个队列。懒汉式,一个View只会有一个。

package android.view;

import android.os.Handler;

import com.android.internal.util.GrowingArrayUtils;

/**
 * Class used to enqueue pending work from Views when no Handler is attached.
 *
 * @hide Exposed for test framework only.
 */
public class HandlerActionQueue {
    private HandlerAction[] mActions;
    private int mCount;

  	//这里就是实际的getRunnerQueue().post(Runnable)
    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 removeCallbacks(Runnable action) {
        synchronized (this) {
            final int count = mCount;
            int j = 0;

            final HandlerAction[] actions = mActions;
            for (int i = 0; i < count; i++) {
                if (actions[i].matches(action)) {
                    // Remove this action by overwriting it within
                    // this loop or nulling it out later.
                    continue;
                }

                if (j != i) {
                    // At least one previous entry was removed, so
                    // this one needs to move to the "new" list.
                    actions[j] = actions[i];
                }

                j++;
            }

            // The "new" list only has j entries.
            mCount = j;

            // Null out any remaining entries.
            for (; j < count; j++) {
                actions[j] = null;
            }
        }
    }

  //使用Handler来执行Action
    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;
        }
    }

    public int size() {
        return mCount;
    }

	//获取第几个runnable.这个不是很常用  
    public Runnable getRunnable(int index) {
        if (index >= mCount) {
            throw new IndexOutOfBoundsException();
        }
        return mActions[index].action;
    }

  //获取指定索引的action的delay
    public long getDelay(int index) {
        if (index >= mCount) {
            throw new IndexOutOfBoundsException();
        }
        return mActions[index].delay;
    }

  //内部类,封装了Runnable和delay 
    private static class HandlerAction {
        final Runnable action;
        final long delay;

        public HandlerAction(Runnable action, long delay) {
            this.action = action;
            this.delay = delay;
        }

        public boolean matches(Runnable otherAction) {
            return otherAction == null && action == null
                    || action != null && action.equals(otherAction);
        }
    }
}

看完这个源代码后,其实就有一个大概的认识了,这里面的post其实就是往队列里面塞了一个runnable对象。那么是在哪执行的executeActions呢?

我看Find Usage一下发现:

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

其实最后还是在dispatchAttachedToWindow的时候,来调用了executeActions,这也说明,post只有当view被WindowManager执行的时候,才会执行。那我们就可以得到如下的流程图:

在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值