关键词:
本文来自于腾讯Bugly公众号(weixinBugly),未经作者同意,请勿转载,原文地址:http://mp.weixin.qq.com/s/KpeBqIEYeOzt_frANoGuSg
yangu
导语 干货!干货!或许可以是一种处理问题的新思路哟~~
前言
我们知道android是基于Looper消息循环的系统,我们通过Handler向Looper包含的MessageQueue投递Message, 不过我们常见的用法是这样吧?
new Handler(Looper.getMainLooper()).post(new Runnable()
@Override
public void run() /do something
);
一般我们比较少接触MessageQueue, 其实它内部的IdleHandler接口有很多有趣的用法,首先看看它的定义
/**
* Callback interface for discovering when a thread is going to block
* waiting for more messages.
*/
public static interface IdleHandler
/**
* Called when the message queue has run out of messages and will now
* wait for more. Return true to keep your idle handler active, false
* to have it removed. This may be called if there are still messages
* pending in the queue, but they are all scheduled to be dispatched
* after the current time.
*/
boolean queueIdle();
简而言之,就是在looper里面的message暂时处理完了,这个时候会回调这个接口,返回false,那么就会移除它,返回true就会在下次message处理完了的时候继续回调,让我们看看它有哪些有趣的用法吧~~
一.提供一个android没有的声明周期回调时机
如果有这种需求,想要在某个activity绘制完成去做一些事情,那这个时机是什么时候呢?有同学可能觉得onResume()是一个合适的机会,不是可是这个onResume() 真的是各种绘制都已经完成才回调的吗?No, too naive ~~
你看谷老师说了,onStart是用户可见,onResume是用户可交互,谷老师可没说onResume是绘制完成吧~那么android那些耗时的measure, layout, draw是在什么时候执行的呢?它们跟onResume()又有何关系呢?让我们先来看看源码吧~
1. ActivityThread.java
我们知道app的进程其实是ActivityThread, 那么activity的生命周期自然是它来执行了,
final void handleResumeActivity(IBinder token,
boolean clearHide, boolean isForward, boolean reallyResume)
//省略部分代码..
//call activity的onResume
ActivityClientRecord r = performResumeActivity(token, clearHide);
//省略部分代码..
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
l.softInputMode |= forwardBit;
if (a.mVisibleFromClient)
a.mWindowAdded = true;
//这里就是关键代码了
wm.addView(decor, l);
performResumeActivity就是回调onResume了, 我们继续看wm.addView方法, 这个ViewManager是一个接口,其实现者是WindowManagerImpl
2.WindowManagerImpl.java
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params)
applyDefaultToken(params);
mGlobal.addView(view, params, mDisplay, mParentWindow);
这个mGlobal是WindowManagerGlobal对象,我们继续
3.WindowManagerGlobal.java
public void addView(View view, android.view.ViewGroup.LayoutParams params,
Display display, Window parentWindow)
//我们跳过不相关代码..
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
this.mViews.add(view);
this.mRoots.add(root);
this.mParams.add(wparams);
try
root.setView(view, wparams, panelParentView);
catch (RuntimeException var15) //省略...
这里我们new 出了ViewRootImpl对象, 我们知道这个对象就是android view的根对象了,负责view绘制的measure, layout, draw的巨长的方法 performTraversals就是这个类的,我们继续看setView方法
4.ViewRootImpl.java
public void setView(View view, LayoutParams attrs, View panelParentView)
//省略部分...
this.requestLayout();
//省略部分..
switch(res)
case -9:
throw new InvalidDisplayException("Unable to add window " + this.mWindow + " -- the specified display can not be found");
case -8:
throw new BadTokenException("Unable to add window " + this.mWindow + " -- permission denied for this window type");
case -7:
throw new BadTokenException("Unable to add window " + this.mWindow + " -- another window of this type already exists");
case -6:
return;
case -5:
throw new BadTokenException("Unable to add window -- window " + this.mWindow + " has already been added");
case -4:
throw new BadTokenException("Unable to add window -- app for token " + attrs.token + " is exiting");
case -3:
throw new BadTokenException("Unable to add window -- token " + attrs.token + " is not for an application");
case -2:
case -1:
throw new BadTokenException("Unable to add window -- token " + attrs.token + " is not valid; is your activity running?");
default:
throw new RuntimeException("Unable to add window -- unknown error code " + res);
这个函数调用了关键方法requestLayout(), 我们继续跟踪,顺便说下,后面一连串的BadTokenException就是我们常常遇到的dialog相关抛出的,也有些特殊场景也会出这个异常,可以到这里查看线索
public void requestLayout()
if(!this.mHandlingLayoutInLayoutRequest)
this.checkThread();
this.mLayoutRequested = true;
this.scheduleTraversals();
调用了scheduleTraversals, 从名字就能看出来了吧
void scheduleTraversals()
if(!this.mTraversalScheduled)
this.mTraversalScheduled = true;
this.mTraversalBarrier = this.mHandler.getLooper().postSyncBarrier();
this.mChoreographer.postCallback(2, this.mTraversalRunnable, (Object)null);
this.scheduleConsumeBatchedInput();
它往Choreographer里面post了一个runnable, 这个Choreographer是android负责帧率刷新相关的东西,我们暂时可以不关注它,可以理解为往主线程post一个消息是一样的,顺便说下这个Choreographer可以做帧率检测相关的东西,,可以用于卡顿检测什么的。。。
final class TraversalRunnable implements Runnable
TraversalRunnable()
public void run()
ViewRootImpl.this.doTraversal();
void doTraversal()
if(this.mTraversalScheduled)
this.mTraversalScheduled = false;
this.mHandler.getLooper().removeSyncBarrier(this.mTraversalBarrier);
if(this.mProfile)
Debug.startMethodTracing("ViewAncestor");
Trace.traceBegin(8L, "performTraversals");
try
this.performTraversals();
finally
Trace.traceEnd(8L);
if(this.mProfile)
Debug.stopMethodTracing();
this.mProfile = false;
我们看这个runnable果然是去执行了那个巨长无比的函数performTraversals函数, 现在我们可以总结下流程了
结论:所以如果我们想在界面绘制出来后做点什么,那么在onResume里面显然是不合适的,它先于measure等流程了, 有人可能会说在onResume里面post一个runnable可以吗?还是不行,因为那样就会变成这个样子
所以你的行为一样会在绘制之前执行,这个时候我们的主角IdleHandler就发挥作用了,我们前面说了,它是在looper里面message暂时执行完毕了就会回调,顾名思义嘛,Idle就是队列为空的意思,那么我们的onResume和measure, layout, draw都是一个个message的话,这个IdleHandler就提供了一个它们都执行完毕的回调了,大概就是这样
说了这么多,那么现在获取到这个时机有什么用呢? look!!
这个是我们地图的公交详情页面, 进入之后产品要求左边的页卡需要展示,可以看到左边的页卡是一个非常复杂的布局,那么进入之后的效果可以明显看到头部的展示信息是先显示空白再100毫秒左右之后才展示出来的,原因就是这个页卡的内容比较复杂,用数据向它填充的时候花了较长时间,代码如下
long time = System.currentTimeMillis();
detailView.populate(route);
//省略部分不相关代码
drawerLayout.openDrawer(GravityCompat.START);
Log.i("yangu", "cost time " + (System.currentTimeMillis() - time));
可以看到这个detailView就是这个侧滑的页卡了,填充里面的数据花了90ms,如果这个时间是用在了界面view绘制之前的话,就会出现以上的效果了,view先是白的,再出现,这样就体验不好了,如果我们把它放到IdleHandler里面呢?代码如下
Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler()
@Override
public boolean queueIdle()
long time = System.currentTimeMillis();
detailView.populate(route);
//省略部分不相关代码
drawerLayout.openDrawer(GravityCompat.START);
Log.i("yangu", "cost time " + (System.currentTimeMillis() - time));
return false;
);
效果是这样的
看出不同了吗?顶部的页卡先展示出来了,这样体验是不是会更好一些呢。虽然只有短短90ms,不过我们做app也应该关注这种细节优化的,是吧~ 这个做法也提供了一种思路,android本身提供的activity框架和fragment框架并没有提供绘制完成的回调,如果我们自己实现一个框架,就可以使用这个IdleHandler来实现一个onRenderFinished这种回调了。
二.可以结合HandlerThread, 用于单线程消息通知器
我们先思考一个问题,如果有一个model数据管理模块,怎么设计?比如地图的收藏模块的model部分。就是下面这个图的小星星
它原来的model设计大概是这个样子的
public class FavoriteModel
private static FavoriteModel ourInstance = new FavoriteModel();
public static FavoriteModel getInstance()
return ourInstance;
private FavoriteModel()
//收藏点数据
private List<Favorite> mData = new ArrayList<>();
//同步锁
public Object lock;
//添加收藏点
public void add(Favorite f)
synchronized (lock)
doSomeThing();
//删除一个收藏点
public void delete(Favorite f)
synchronized (lock)
doSomeThing();
//获取当前收藏列表
public List<Favorite> get()
return mData;
//同步服务器收藏数据
public void sync()
synchronized (lock)
doSomeThing();
由于这个model是单例的,而且是多线程可以访问的,所以它的增删改查都加上了锁,而且由于外部访问需要遍历有哪些收藏点,所以外部遍历列表也需要加锁,大概是这样的
public void visit(List<Favorite> favorites)
synchronized (FavoriteModel.getInstance().lock)
for (Favorite favorite : favorites)
doSomeThing();
因为是多线程可访问的,如果遍历不加锁的话,其他线程删除了一个收藏,就会crash的,原来的这样设计有几个不好的地方
1.外部使用者需要关系锁的使用,增加了负担,不用还不安全
2.如果在主线程加锁的话,可能另一个线程执行操作会阻塞主线程造成anr
总之,多线程代码就是容易出错,而且真的出错的时候查起来太费劲了,目前收藏夹模块就有N多bug,所以我想用单线程来解决这个问题,由于model层的访问需要数据库和网络等,所以需要异步线程,那么单线程队列+异步线程,首先想到的就是HandlerThread, 大概架构如下
现在,我们把原来多线程的逻辑改到了单线程里面,各种收藏的model共用一个HandlerThread,这样我们增删改查都不用加锁了,出错几率大大减小,而且这种model的设计有点类似插件的意思,可以很方便的增加其他收藏
ok, 那么跟我们的主题IdleHandler有什么关系呢?思考这样一个问题,地图上的小星星需要实时更新,也就是model的任何变化都需要显示到地图上,那么收藏的小星星就应该作为model的观察者,以前的做法是向收藏model注册监听,在每一个增删改查操作后都对观察者回调,大概是这样
//添加收藏点
public void add(Favorite f)
synchronized (lock)
doSomeThing();
notifyObserver();
//删除一个收藏点
public void delete(Favorite f)
synchronized (lock)
doSomeThing();
notifyObserver();
这样有一个小小的问题,就是如果有一个操作生成10个快速连续的增删改查操作,那么我们的UI就会收到10次回调,而这种场景下我们其实只需要最后一次回调就够了,中间操作其实不用刷新UI的
那么现在改成单线程模型,我们又该如何处理这个问题呢?当然我们也能在每个post到异步线程的runnable里面去回调观察者,但这样未免不够优雅,所以这个时候IdleHandler不就又可以发挥作用了吗?它是在消息暂时处理完的时候回调的呀,不是很符合我们的时机么,对吧
public AbsFavoriteModel()
if (sThread == null)
sThread = new HandlerThread("favorite-model");
sThread.start();
mHandler = new Handler(sThread.getLooper());
try
Field field = Looper.class.getDeclaredField("mQueue");
field.setAccessible(true);
MessageQueue queue = (MessageQueue) field.get(sThread.getLooper());
queue.addIdleHandler(new MessageQueue.IdleHandler()
@Override
public boolean queueIdle()
if (mListeners != null)
for (DataChangeListener<T> mListener : mListeners)
mListener.onDataChange(new ArrayList<>(mData));
return true;
);
catch (Exception e)
e.printStackTrace();
就是这个样子了,这里为什么不用第一个场景下的Looper.myQueue().addIdleHandler()呢?注意这个地方Looper.myQueue()如果在主线程调用就会使用主线程looper了,所以我选择反射这个HandlerThread的looper来设置它,这个IdleHandler我们返回了true, 表示我们要长期监听消息队列,因为返回false,下次就没有回调了哦。
好了,结论是这个地方IdleHandler用作了一个消息的触发器,是不是挺有意思的呢?
结语
如果你没有用过它,从今天开始试试吧,这篇文章只是我个人的一点小思路,说不定这个IdleHandler有很多其他的用法呢~~如果喜欢的话请点个赞哟,有任何不正确的地方也请随时指出
更多精彩内容欢迎关注腾讯 Bugly的微信公众账号:
腾讯 Bugly是一款专为移动开发者打造的质量监控工具,帮助开发者快速,便捷的定位线上应用崩溃的情况以及解决方案。智能合并功能帮助开发同学把每天上报的数千条 Crash 根据根因合并分类,每日日报会列出影响用户数最多的崩溃,精准定位功能帮助开发同学定位到出问题的代码行,实时上报可以在发布后快速的了解应用的质量情况,适配最新的 iOS, Android 官方操作系统,鹅厂的工程师都在使用,快来加入我们吧!
《你知道android的messagequeue.idlehandler吗?》(代码片段)
...于Looper消息循环的系统,我们通过Handler向Looper包含的MessageQueue投递Message,不过我们常见的用法是这样吧?newHandler(Looper.getMainLooper()).post(newRunnable()@Overridepublicvoidrun()/dosomething);一般我们比较少接触MessageQueue,其实它内部... 查看详情
[android源代码分析]android消息机制,handler,message,looper,messagequeue
最近准备把Android源码大致过一遍,不敢私藏,写出来分享给大家,顺便记录一下自己的学习感悟。里面一定有一些错误的地方,希望广大看客理解理解。网上也有不少分析文章,这里我尽量分析的更加细致详尽。不留死角。一.... 查看详情
android多线程分析之四:messagequeue的实现(代码片段)
Android多线程分析之四:MessageQueue的实现罗朝辉(http://blog.csdn.net/kesalin)CC许可,转载请注明出处在前面两篇文章《Android多线程分析之二:Thread的实现》,《Android多线程分析之三:Handler,Looper的实现》中分别... 查看详情
android中的handler,looper,messagequeue和thread对应关系
...其中包含了消息ID,消息处理对象以及处理的数据等,由MessageQueue统一列队,终由Handler处理。Handler:处理者,负责Message的发送及处理。使用Handler时,需要实现handleMessage(Messagemsg)方法来对特定的Message进行处理,例如更新UI等。Me... 查看详情
心细两界的messagequeue
...处理循环。在andrid2.3以前,只有Java世界的居民有资格MessageQueue中添加消息以驱动Java世界的正常运转,但从android2.3开始,MessageQueue的核心部分下移只Native层,让Netaive世界的居民能利用消息循环来处理他们所在世界... 查看详情
这些关于handler的知识点你都知道吗?(代码片段)
...f1f;一、题目层次Handler的基本原理子线程中怎么使用HandlerMessageQueue获取消息是怎么等待为什么不用wait而用epoll呢?线程和HandlerLooperMessageQueue的关系多个线程给MessageQueue发消息 查看详情
androidmessagequeue源码分析(代码片段)
...发中离不开Handler,而Handler实际上最终是将Message交给MessageQueue。MessageQueue是Android消息机制的核心,熟悉MessageQueue能够帮助我们更清楚详细地理解Android的消息机制。这篇文章会介绍MessageQueue消息的插入(enqueueMessage)和读取(n... 查看详情
好玩的handler
...ndler:你可以构造Handler对象来与Looper沟通,以便push新消息到MessageQueue里,或者接口Looper从MessageQueue取出的消息;Looper类用来管理特定线程内对象之间交换Message;一个线程可以产生一个Looper对象 查看详情
android消息机制和原理
...用来管理特定线程内对象之间的消息交换(MessageExchange)。MessageQueueMessageQueue是持有Message(在Looper中派发)的一个链表,Message并不是直接添加到MessageQueue中的,而是通过与Looper相关联的Handler来进行的。用来存放线程放入的消息,读... 查看详情
androidmessagequeue源码分析(代码片段)
...发中离不开Handler,而Handler实际上最终是将Message交给MessageQueue。MessageQueue是Android消息机制的核心,熟悉MessageQueue能够帮助我们更清楚详细地理解Android的消息机制。这篇文章会介绍MessageQueue消息的插入(enqueueMessage)和读取(n... 查看详情
android-view的绘制你知道多少?
https://github.com/android-cn/android-open-project-analysis/tree/master/tech/viewdrawflowAndroid应用层View绘制流程与源码分析Android-View的绘制源码学习总结 查看详情
心细两界的messagequeue
...处理循环。在andrid2.3以前,只有Java世界的居民有资格MessageQueue中添加消息以驱动Java世界的正常运转,但从android2.3开始,MessageQueue的核心部分下移只Native层,让Netaive世界的居民能利用消息循环来处理他们所在世界 查看详情
android多线程编程之handler篇(消息机制)
...机制主要是指Handler的运行机制,Handler的运行需要底层的MessageQueue和Looper的支撑。MessageQueue消息队列,以队列的形式(实为单链表结构)对外提供插入和删除的工作,Looper以无限循环的形式不断获取MessageQueue中的消息,有则处理... 查看详情
android那些你所不知道的bitmap对象具体解释
.../41084843),请尊重他人的辛勤劳动成果,谢谢。我们知道Android系统分配给每一个应用程序的内存是有限的,Bitmap作为消耗内存大户。我们对Bitmap的管理稍有不当就可能引发OutOfMemoryError,而Bitmap对象在不同的Android版 查看详情
handler与异步消息处理(代码片段)
...消息处理的流程。Android中的异步消息处理框架由Handler、MessageQueue、Looper和ThreadLocal等组成。Handler是我们使用最多的一个类,主要负责发送和处理消息,MessageQueue是一个队列,用来存储Handler发送来的消息,而Looper... 查看详情
handler实现机制图解
...线程可以产生一个Looper对象,由它来管理此线程里的MessageQueue(消息队列)。2)Handler:你可以构造Handler对象来与Looper沟通,以便push新消息到MessageQueue里;或者接收Looper从MessageQueue取出)所送来的消息。3)MessageQueue(消息队列):用... 查看详情
handler实现机制图解
...线程可以产生一个Looper对象,由它来管理此线程里的MessageQueue(消息队列)。2)Handler:你可以构造Handler对象来与Looper沟通,以便push新消息到MessageQueue里;或者接收Looper从MessageQueue取出)所送来的消息。3)MessageQueue(消息队列):用... 查看详情
handler+executorservice(线程池)+messagequeue模式+缓存模式
android线程池的理解,晚上在家无事预习了一下android异步加载的例子,也学习到了一个很重要的东东那就是线程池+缓存 下面看他们的理解。[size=1.8em]Handler+Runnable模式我们先看一个并不是异步线程加载的例子,使用Handler+... 查看详情