zl程序教程

您现在的位置是:首页 >  移动开发

当前栏目

View.post(Runnble)的一点小问题详解手机开发

手机开发 问题 详解 View post 一点
2023-06-13 09:20:15 时间

今天刚好看到一个问题,为什么onCreate() 中使用 View.post(Runnable)可以拿取到View的宽高,第一想法就是内部利用handler将Runnbale加入主线程MessageQueue,执行完测量任务之后再执行Runnable。

点开源码之后发现好像没那么简单,不过也没那么难

public boolean post(Runnable action) {

 final AttachInfo attachInfo = mAttachInfo; 

 if (attachInfo != null) {

 return attachInfo.mHandler.post(action); 

 getRunQueue().post(action); 

 return true; 

在post()方法中,如果有attachInfo,直接attachInfo.mHandler.post(),若没有就getRunQueue().post();

看下第一种情况,attachInfo是什么时候不为null,还有mHandler是哪里的Handler?
// View 

void dispatchAttachedToWindow(AttachInfo info, int visibility) {

 mAttachInfo = info; 

 .... 

 if (mRunQueue != null) {

 mRunQueue.executeActions(info.mHandler); 

 mRunQueue = null; 

mAttachInfo 是在diapatchAttachedToWindow()赋值的,ViewRootImpl的performTraversals()会调用ViewGroup 的 diapatchAttachedToWindow(),最后会调用子View的diapatchAttachedToWindow()

// ViewRootImpl 

private void performTraversals() {

 // 如果是首次绘制 

 if (mFirst) {

 host.dispatchAttachedToWindow(mAttachInfo, 0); 

// ViewRootImpl 

final ViewRootHandler mHandler = new ViewRootHandler(); 

// ViewRootImpl 

public ViewRootImpl(Context context, Display display) {

 mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this, context); 

从上面代码可以看出,mAttachInfo在ViewRootImpl构造,并且performTraversals(),若i是首次绘制,会把mAttachInfo赋给View。

View里面的Handler其实就是ViewRootImpl的ViewRootHandler 。

ViewRootHandler直接在主线程构造,所以是消息处理都是在主线程中。

小结:

diapatchAttachedToWindow()后,mAttachInfo不为null,就可以直接通过ViewRootHandler处理Runnable,最后能够获取View宽高。

但是不是 diapatchAttachedToWindow()之前,就已经测量好宽高了呢?

再看下第二种情况,getRunQueue().post(action);
// View.java 

private HandlerActionQueue mRunQueue; 

private HandlerActionQueue getRunQueue() {

 if (mRunQueue == null) {

 mRunQueue = new HandlerActionQueue(); 

 return mRunQueue; 

// HandlerActionQueue 

public class HandlerActionQueue {

 private HandlerAction[] mActions; 

 private int 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; 

这里就直接说结果了,在attachInfo为null的时候,View.post()会将Runnable保存在HandlerActionQueue中,等待dispatchAttachedToWindow() 的时候,mRunQueue.executeActions(info.mHandler) 会执行保存在HandlerActionQueue的Runnbale。

可见,我没有考虑到测量绘制还没开始的情况,如果测量还没开始就需要一个数组将Runnable存起来,在测量结束后执行。

但是都同样存在一个问题:是不是 diapatchAttachedToWindow()之前,就已经测量好宽高了呢?

// ViewRootImpl 

private void performTraversals() { 

 host.dispatchAttachedToWindow(mAttachInfo, 0); 

 performMeasure(); 

 performLayout(); 

 performDraw(); 

上面的伪代码指出,performTraversals()中是先执行dispatchAttachedToWindow(),后面才执行measure()、layout()等测量,这个怎么保证post.view()肯定能拿到宽高?

后面终于搞清楚了!

原来无论是perfromTraversals,还是后面的Runnable,都是保存在MessageQueue,Looper会执行完当前的任务,再从MessageQueue取出下个任务接着执行;

第一种情况,attachInfo.mHandler.post(action),和第二种情况,在dispatchAttachedToWindow executeActions(),都是增加任务到在主线程的MessageQueue中,而Looper只会处理完当前的测量任务才会去执行View.post的任务。

可以看下面的示意图,当前还在执行performTraversals(),后续的任务还在MessageQueue等待执行。

在这里插入图片描述

6294.html

app程序应用开发手机开发无线开发移动端开发