Android 自定义View的post(Runnable)方法非100%执行的原因和处理方法解析
2015-12-11 10:56
507 查看
最近在写一个需求,需要在view.post(Runnable)方法当中进行一些操作。但是实际使用中(特定场景)发现并不靠谱。
现象
如果调用了view的post(Runnable)方法,该Runnable在View处于detached状态期间并不会执行;只有当此View或另一个View的view.post()方法被调用,且这个view处于attached状态时(也就是这个Runnable能顺利执行时),前一个post的Runnable才会顺带一块被执行。
原理
一个功能既然一部分能够成功运行,一部分不能够成功运行,那一定是有原因的,那我们来看看View的post方法里面都干了什么:
可以看到,post()当中实际使用的是attachInfo的Handler,正好与我们出现问题的场景吻合(其实100%复现问题后找原因就很简单了)。第三行,attachInfo 是否为空进行判断,我们的问题场景明显不符合,因此走到第7行。可以看到仍然是使用的ViewRootImpl这个根View,获取它的RunQueue来post这个这个Runnable。 看到第6行这个注释,我们就知道不太妙了:『假设它待会会成功执行』,然后系统默默地返回了true。。。 可以看到这里与我们的问题已经完全对应了。
解决方案
从解决问题的角度,分析到这里已经能够形成解决方案了。那么根据View的attach状态,我们只需要在attached过后的detached期间,换用另一种更靠谱的方法弥补这个方法的不足即可。对于异步但不要求delay的Runnable,直接执行即可:
是不是很简单。当一个问题能100%复现后,其解决方案总是很简单。
[b]浅析深入原理[/b]
只是解决问题,到上面的部分就可以结束了。但作为一个原理,我们还是希望继续深入了解一下Android对post()这些机制的处理,那么我们继续深挖刚才看到的RunQueue的代码,至少能够把我们的好奇心说服为止。来看看ViewRootImpl.GetRunQueue()的方法:
『这个运行队列用于在没有Handler attached时,把来自View的即将运行的工作加入此队列。这个工作会此此线程下次调用遍历时执行。』
这个代码较多,再精简来看一下我们在post时会调用的runQueue.post(Runnable)方法:
可以看到new了一个HandlerAction包装我们传入的action,并添加到mActions中。什么?整个方法竟然就只是add到了一个List中。没错,这和我们之前观察的现象是完全一致的。调用了post(),但并没有执行。这些Runnable对象,会在excuteActions当中会执行:
总结
至此,我们已经了解清楚,View在执行post(Runnable)方法时,会使用attachInfo中的mHandler;而在没有attachedInfo时,会使用一个RunQueue暂时装载着Runnable对象而不会立即执行;在进行遍历时,会用新的attachInfo的Handler执行这个Runnable对象。这与我们观察到的现象,以及使用的解决方法是一致的。
如果有需要一定执行同时又使用view.post(Runnable)实现时,留心一下post(Runnable)调用时View的生命周期,避免实际执行时机与顺序与预期的不一致。
现象
如果调用了view的post(Runnable)方法,该Runnable在View处于detached状态期间并不会执行;只有当此View或另一个View的view.post()方法被调用,且这个view处于attached状态时(也就是这个Runnable能顺利执行时),前一个post的Runnable才会顺带一块被执行。
原理
一个功能既然一部分能够成功运行,一部分不能够成功运行,那一定是有原因的,那我们来看看View的post方法里面都干了什么:
public boolean post(Runnable action) { final AttachInfo attachInfo = mAttachInfo; if (attachInfo != null) { return attachInfo.mHandler.post(action); } // Assume that post will succeed later ViewRootImpl.getRunQueue().post(action); return true; }
可以看到,post()当中实际使用的是attachInfo的Handler,正好与我们出现问题的场景吻合(其实100%复现问题后找原因就很简单了)。第三行,attachInfo 是否为空进行判断,我们的问题场景明显不符合,因此走到第7行。可以看到仍然是使用的ViewRootImpl这个根View,获取它的RunQueue来post这个这个Runnable。 看到第6行这个注释,我们就知道不太妙了:『假设它待会会成功执行』,然后系统默默地返回了true。。。 可以看到这里与我们的问题已经完全对应了。
解决方案
从解决问题的角度,分析到这里已经能够形成解决方案了。那么根据View的attach状态,我们只需要在attached过后的detached期间,换用另一种更靠谱的方法弥补这个方法的不足即可。对于异步但不要求delay的Runnable,直接执行即可:
if(mAttached) { post(r); } else { r.run(); }其中r是我自己new出来的Runnable变量。如果仍然期待使用post()达到的效果拒绝立即同步调用,也可以换用Handler,代码也都相当简单:
if(mAttached) { post(r); } else { Handler handler = new Handler(); handler.post(r); }mAttached是自行维护的一个变量,等价于View的:
isAttachedToWindow(),只是该api较高,通常为了兼容于是自行维护该状态。维护的代码如下:
@Override protected void onAttachedToWindow() { super.onAttachedToWindow(); mAttached = true; } @Override protected void onDetachedFromWindow() { mAttached = false; super.onDetachedFromWindow(); }
是不是很简单。当一个问题能100%复现后,其解决方案总是很简单。
[b]浅析深入原理[/b]
只是解决问题,到上面的部分就可以结束了。但作为一个原理,我们还是希望继续深入了解一下Android对post()这些机制的处理,那么我们继续深挖刚才看到的RunQueue的代码,至少能够把我们的好奇心说服为止。来看看ViewRootImpl.GetRunQueue()的方法:
static RunQueue getRunQueue() { RunQueue rq = sRunQueues.get(); if (rq != null) { return rq; } rq = new RunQueue(); sRunQueues.set(rq); return rq; }可以发现,只是一个相对简单的get方法,存在了sRunQueues里。那么这个RunQueue是个啥Queue,我们来看一下这个RunQueue类型的说明。
/** * The run queue is used to enqueue pending work from Views when no Handler is * attached. The work is executed during the next call to performTraversals on * the thread. * @hide */ static final class RunQueue { private final ArrayList<HandlerAction> mActions = new ArrayList<HandlerAction>(); void post(Runnable action) { postDelayed(action, 0); } void postDelayed(Runnable action, long delayMillis) { HandlerAction handlerAction = new HandlerAction(); handlerAction.action = action; handlerAction.delay = delayMillis; synchronized (mActions) { mActions.add(handlerAction); } } void executeActions(Handler handler) { synchronized (mActions) { final ArrayList<HandlerAction> actions = mActions; final int count = actions.size(); for (int i = 0; i < count; i++) { final HandlerAction handlerAction = actions.get(i); handler.postDelayed(handlerAction.action, handlerAction.delay); } actions.clear(); } } private static class HandlerAction { Runnable action; long delay; ... } }这里只包括了主要的变量和方法。RunQueue是ViewRootImpl.class的一个内部静态类,可以看到这个队列是用一个叫做mActions的ArrayList实现的,元素就是一个Runnable(系统叫做action)和一个delay时间组成的对象。对于我们从使用角度理解原理,注释已经把把该类的功能进行了一个概括:
『这个运行队列用于在没有Handler attached时,把来自View的即将运行的工作加入此队列。这个工作会此此线程下次调用遍历时执行。』
这个代码较多,再精简来看一下我们在post时会调用的runQueue.post(Runnable)方法:
void post(Runnable action) { postDelayed(action, 0); }首先调用到postDelayed(Runnable, long),再看看实现:
void postDelayed(Runnable action, long delayMillis) { HandlerAction handlerAction = new HandlerAction(); handlerAction.action = action; handlerAction.delay = delayMillis; synchronized (mActions) { mActions.add(handlerAction); } }
可以看到new了一个HandlerAction包装我们传入的action,并添加到mActions中。什么?整个方法竟然就只是add到了一个List中。没错,这和我们之前观察的现象是完全一致的。调用了post(),但并没有执行。这些Runnable对象,会在excuteActions当中会执行:
void executeActions(Handler handler) { synchronized (mActions) { final ArrayList<HandlerAction> actions = mActions; final int count = actions.size(); for (int i = 0; i < count; i++) { final HandlerAction handlerActi 93f0 on = actions.get(i); handler.postDelayed(handlerAction.action, handlerAction.delay); } actions.clear(); } }而在什么时机调用的呢?在performTraversals当中,会调用该方法,执行加入队列的操作如果有detached的view往队列里加入过action。action就是我们传入的Runnable对象。使用的Handler仍然是attachInfo的Handler,可以知道原理和view.post(Runnable)当中的前面那段的逻辑一致,也就是说相当于做了一个暂停,并在合适的时机再执行。执行时的调用实现都是一致的。
private void performTraversals() { ... // Execute enqueued actions on every traversal in case a detached view enqueued an action getRunQueue().executeActions(mAttachInfo.mHandler); ... }
总结
至此,我们已经了解清楚,View在执行post(Runnable)方法时,会使用attachInfo中的mHandler;而在没有attachedInfo时,会使用一个RunQueue暂时装载着Runnable对象而不会立即执行;在进行遍历时,会用新的attachInfo的Handler执行这个Runnable对象。这与我们观察到的现象,以及使用的解决方法是一致的。
如果有需要一定执行同时又使用view.post(Runnable)实现时,留心一下post(Runnable)调用时View的生命周期,避免实际执行时机与顺序与预期的不一致。
相关文章推荐
- 使用C++实现JNI接口需要注意的事项
- Android IPC进程间通讯机制
- Android Manifest 用法
- [转载]Activity中ConfigChanges属性的用法
- Android之获取手机上的图片和视频缩略图thumbnails
- Android之使用Http协议实现文件上传功能
- Android学习笔记(二九):嵌入浏览器
- android string.xml文件中的整型和string型代替
- i-jetty环境搭配与编译
- android之定时器AlarmManager
- android wifi 无线调试
- Android Native 绘图方法
- Android java 与 javascript互访(相互调用)的方法例子
- android 代码实现控件之间的间距
- android FragmentPagerAdapter的“标准”配置
- Android"解决"onTouch和onClick的冲突问题
- android:installLocation简析
- android searchView的关闭事件
- SourceProvider.getJniDirectories