本文共 6547 字,大约阅读时间需要 21 分钟。
如果你对Handler的工作原理比较了解的话,那么对于post() 就比较好了解了,因为post() 的内部用的就是Handler,但是它里面是如何实现的,这就是本节的重点。如果你对这个方法有个清晰的了解的话,那么在开发的时候,有时候就不必自己去维护一个handler了,当然这里使用不当也是会有内存泄露的,就和自己维护的Handler一样,这也是我们在使用的时候需要注意的。对于Handler处理消息回调的接口总共有三个并且是有优先级的,post()使用的就是Message中callback,如果你对Handler还不了解,可以看一看。
先来看一下View.post()方法:
public boolean post(Runnable action) { final AttachInfo attachInfo = mAttachInfo; if (attachInfo != null) { //使用attachInfo中的Handler的post()方法,handler用过的人应该看下就明白了, //不明白的应该就是attachInfo这个到底是什么,后面会重点分析 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. //这里做的就是当attchInfo为null时,将action暂时保存在一个数组中,当attachInfo不为null时, // 再交由Handler处理 getRunQueue().post(action); return true;}
看到这里,你可能会有两个点不太明白:
一个是attachInfo是如何赋值的;
二是getRunqueue.post()是如何暂时保存任务,注意,这里用的是暂时。
那么我们先来看看getRunqueue()给我们返回的什么:
private HandlerActionQueue getRunQueue() { if (mRunQueue == null) { mRunQueue = new HandlerActionQueue(); } return mRunQueue;}
那我们再去看看HandlerActionQueue这个类是如何实现:
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]; } //GrowingArrayUtils是一个工具类,这里的作用只是对数组进行判断, // 如果数组到达容量后,会对数值进行扩容后再将任务放进去 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进行处理,现在我们可以大胆的猜测一下, // attachInfo不为null的时候,应该就是将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; } } public int size() { return mCount; } public Runnable getRunnable(int index) { if (index >= mCount) { throw new IndexOutOfBoundsException(); } return mActions[index].action; } public long getDelay(int index) { if (index >= mCount) { throw new IndexOutOfBoundsException(); } return mActions[index].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); } }}
这个类还是比较简单的,很容易就看懂,它主要是将传进来的任务进行封装后在保存在数组中。当需要将这些任务交由handler处理的时候,我们看到,这里提供了一个方法executeActions(Handler h),就是将任务循环遍历交由Handler处理,HandlerActionQueue看完了,接下来就该去找找attachInfo了。
在View中我们搜素一下给mAttachInfo赋值的地方,很快就发现了是在dispatchAttachToWindow()中对mAttachInfo进行赋值的:
void dispatchAttachedToWindow(AttachInfo info, int visibility) { mAttachInfo = info; // Transfer all pending runnables. if (mRunQueue != null) { mRunQueue.executeActions(info.mHandler); mRunQueue = null; }}
这是dispatchAttachToWindow()方法中的一部分,可以看到暂时保存起来的任务通过executeActions()将任务交给了Handler处理,到这,我们唯一还要知道就是dispatchAttachToWindow()这个是在什么时候调用。但当我们再次进行搜索的时候发现找不到了,这下该怎么继续呢?我们知道,view的分发肯定是从ViewGroup中来的,那我们就该去ViewGroup中找一找,还真有:
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有子view,循环调用子view的dispatchAttachToWindow()方法,还是挺简单的。
到ViewGroup后,我们就应该想到这个应该是从View树的最顶层传下来的,而对于View树最终会在resume的时候传递到ViewRootImpl中去,所有,现在就该去ViewRootImpl中找找了,我们可以在performTranversals()中看到:
host.dispatchAttachedToWindow(mAttachInfo, 0);
终于找到了,原来就是从这里对view的mAttachInfo进行赋值的,并且一个view树共用的是同一个AttachInfo,在ViewRootImpl的构造方法中:
mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this,context);
到此,整个流程就全清楚了。
1、首先得明确performTranversals()这个方法是在Activity的resume这个时期调用的,也就是mAttachInfo也是在这个时候进行赋值的;
2、有时候我们可能会在Activity的onCreate()利用view.post()去获取view的宽高,这个是怎么做到的呢?从上面的整个流程下来,我们知道,一开始mAttachInfo是null,那么,post()就会将传进来的的任务暂时保存起来,等到resume的时候通过dispatchAttachToWindow()对mAttachInfo进行赋值,并将缓存起来的任务全部交由mAttachInfo中的Handler处理,这里有一点需要清楚,Android是基于消息驱动来处的,Activity的生命周期也是作为Handler的消息进行分发的,所以这些交给Handler处理的任务要等到目前的消息回调事件处理完才会触发,也就是说,这些任务执行的时间实在view的的测量、布局和绘制的后面,所以在post()中可以拿到view的宽高,并且也一定是在UI线程中执行的。
3、这里在提一次,使用的时候注意可能存在的内存泄露。
有哪里看不懂的欢迎留言!!!
转载地址:http://vzhai.baihongyu.com/