内容简介:最近因为个人原因,导致要离开杭州了,人生就是一次又一次的意外,你不知道突然会发生什么,你能做到的只有把握好每一次机会在Android中,想要获取View的宽高,常见的基本都是接调用View.getWidth或者View.getMeasuredWidth方法来获取宽高,但是往往在oncreate或者onresume中,得到的值都是0,也就可以理解为此时View的绘制流程并没有执行完,熟悉Activity启动流程的朋友都知道,Activity的生命周期和页面的绘制流程并不是一个串行的状态,没有特定的先后关系,所
最近因为个人原因,导致要离开杭州了,人生就是一次又一次的意外,你不知道突然会发生什么,你能做到的只有把握好每一次机会
原理猜测
在Android中,想要获取View的宽高,常见的基本都是接调用View.getWidth或者View.getMeasuredWidth方法来获取宽高,但是往往在oncreate或者onresume中,得到的值都是0,也就可以理解为此时View的绘制流程并没有执行完,熟悉Activity启动流程的朋友都知道,Activity的生命周期和页面的绘制流程并不是一个串行的状态,没有特定的先后关系,所以也不难理解获取的值是0了
再次回到主题,那为什么View.post(),就可以获取到准确的值呢,不妨猜测一下,首先整体上思考一下,想要实现知道准确的宽高,那就是post的Runnable那肯定是在View整个绘制流程结束之后才执行的,主线程又是基于Looper的消息机制的,如果把Runnable直接作为一个消息插入消息队列,那么很明显不能保证这种效果,熟悉View绘制流程的朋友知道,View的绘制是在ViewRootImp中的,但View的绘制其实也是一个Runnable消息,那么我们可不可以先把post的这个Runnable给缓存起来,等到绘制的Runnable执行完之后,再来通知去执行,这样就能够获取到准确的宽高了。
View##post
本文源码是API是android-28,不同版本可能有些差异,需要读者自行注意
public boolean post(Runnable action) { final AttachInfo attachInfo = mAttachInfo; if (attachInfo != null) { return attachInfo.mHandler.post(action); } getRunQueue().post(action); return true; } 复制代码
可以看到方法很简答,主要就是一个 attachInfo ,如果不为空就直接使用attachInfo.mHandler去执行这个action,如果为空,把Runnable放入一个类似队列的东西里面
我们再回头想想开头说的话,好像还真是这么实现的,这里的 mAttachInfo 其实可以看做为是否已经绘制好了的一个标志,如果不为空,说明绘制完成,直接handler执行action,如果为空,说明没有绘制完,这时候就把Runnable缓存起来,那么关键点也就来了,这个 mAttachInfo 是什么时候被赋值的,全局搜索下赋值
View##dispatchAttachedToWindow
void dispatchAttachedToWindow(AttachInfo info, int visibility) { mAttachInfo = info; ...... } 复制代码
从这个方法名字,我们也应该能看出来,绑定到window的时候,此时会进行赋值mAttachInfo,也就意味着绘制完毕,当然,我们还不知道dispatchAttachedToWindow这个方法是什么时候调用的,先这么理解着
getRunQueue().post(action)
private HandlerActionQueue getRunQueue() { if (mRunQueue == null) { mRunQueue = new HandlerActionQueue(); } return mRunQueue; } 复制代码
public class HandlerActionQueue { private HandlerAction[] mActions; private int mCount; 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++; } } 复制代码
可以看到,实际上是维护了一个HandlerActionQueue类,内部维护了一个数组,长度居然是固定为4(问号脸),然后将这些Runnabel给缓存起来。那疑问就来了,既然是缓存起来,那什么时候执行的,可以看到有个executeActions方法
executeActions(Handler handler)
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; } } 复制代码
void dispatchAttachedToWindow(AttachInfo info, int visibility) { mAttachInfo = info; ...... // Transfer all pending runnables. if (mRunQueue != null) { mRunQueue.executeActions(info.mHandler); mRunQueue = null; } .... } 复制代码
通过传递进来的handler,然后将内部缓存的Runnable去执行,ctrl一下,看看哪里调用了,咦。又是 dispatchAttachedToWindow 这个方法。
先总结下, View.post(Runnable) 的这些 Runnable 操作,在 View 被 attachedToWindow 之前会先缓存下来,然后在 dispatchAttachedToWindow() 被调用时,就将这些缓存下来的 Runnable 通过 mAttachInfo 的 mHandler 来执行。在这之后再调用 View.post(Runnable) 的话,这些 Runnable 操作就不用再被缓存了,而是直接交由 mAttachInfo 的 mHandler 来执行
所以问题最终也就到这个方法这里了,这个方法是什么时候被调用的,ctrl一下。。居然没有地方调用,那肯定是隐藏类调用了,此时祭出我们的Source Insight,从源码里面找找。
ViewRootImpl##performTraversals
private void performTraversals() { ..... final View host = mView; ..... host.dispatchAttachedToWindow(mAttachInfo, 0) .... } final ViewRootHandler mHandler = new ViewRootHandler(); public ViewRootImpl(Context context, Display display) { ... mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this); .... } 复制代码
这个方法是不是很眼熟,View的绘制流程就从这个方法开始的,可以看到的是,mAttachInfo这个对象是在ViewRootImpl初始化的时候就赋值了,并且Handler是直接在主线程中创建的,这个就可以说明了为什么View.post是可以更新UI的,因为最终的Runnable是在主线程的Handler中去执行的,自然是可以更新UI的。
但是大家有可能还有个疑问,那既然View的绘制也是这个方法执行的,dispatchAttachedToWindow也是在这个方法执行的,那怎么能保证一定是在View的绘制流程完成之后才去执行dispatchAttachedToWindow的呢。
答案也很简单,因为android主线程是基于Looper的消息机制的,不断的从绑定Looper的MessageQueue中去获取message去执行,View的绘制操作其实也是一个Runnable对象,所以在执行performTraversals()方法的时候,调用dispatchAttachedToWindow方法,这个时候,所以子View通过View.post(Runnable)缓存的Runnabel是会通过mAttachInfo.mHandler 的 post() 方法将这些 Runnable 封装到 Message 里发送到 MessageQueue 里。mHandler 我们上面也分析过了,绑定的是主线程的 Looper,所以这些 Runnable 其实都是发送到主线程的 MessageQueue 里排队的,所以也就可以保证这些 Runnable 操作也就肯定会在 performMeasure() 操作之后才执行,宽高也就可以获取到了,我们也可以在源码中找到些许痕迹
final class TraversalRunnable implements Runnable { @Override public void run() { doTraversal(); } } void doTraversal() { if (mTraversalScheduled) { ..... performTraversals(); .... } } 复制代码
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 拿到一个 Demo 该怎么下手?
- 拿到一个 Demo 该怎么下手?
- 如何备战蓝桥杯拿到省一
- 利用XSS漏洞轻松拿到登录用户的cookie
- Sqlmap初体验,渗透拿到网站用户名密码
- 春招:我居然三天就拿到了offer?
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。