Handler相信每个从事Android开发的小伙伴都非常熟悉了, 最常用的场景就是在子线程中进行数据操作然后通过handler消息机制通知到UI线程来更新UI,地球人都知道在子线程中更新UI,一般情况下都会报错。每每出去面试被问到“handler原理”,“消息是怎么从子线程发送到主线程的”等等handler底层的实现,就懵逼了。
虽然网上关于分析handler的博客问丈夫非常多,已经有很多大佬分析的非常清晰了。这里分析主要是为了让自己加深理解,另一方面就是想分享所学知识。
Android消息循环流程图如下所示:
主要涉及的角色如下所示:
- Message:消息。
- MessageQueue: 消息队列,负责消息的存储于管理,负责管理由handler发过来的Message,读取会自动删除消息,单链表维护,插入和删除上有优势。 在其next()方法中会无限循环,不短判断是否有消息,有就返回这条消息并移除。
- Looper: 消息循环器,负责关联线程以及消息的分发,在该线程下从MessageQueue获取Message,分发给handler,Looper创建的时候会创建一个MessageQueue,调用loop()方法的时候消息循环开始,其中会不断调用MessageQueue的next()方法,有消息就处理,否则就阻塞在MessageQueue的next()方法中,当Looper的quit()被调用的时候会调用MessageQueue的quit(),此时的next()会返回null,然后loop()方法也就跟着退出。
- Handler:消息处理器,负责发送并处理消息,面向开发则,提供API,并隐藏背后实现的细节。
整个消息循环流程还是比较清晰的,具体来说:
- 1.Handler通过sendMessage()发送消息Message到队列MessageQueue.
- 2.Looper通过loop()不断提取触发条件的Message,并将Message交给对应的target handler来处理。
- 3.target handler调用自身的handleMessage()方法来处理Message。
事实上,在整个消息循环的流程中,并不止只有Java层参与,很多重要的工作都是在C++层来完成,我们来看看下图的这些类的调用它关系。
注:虚线表示关联关系,实现表示调用关系。
在这些类中MessageQueue 是Java层与C++层维系的桥梁,MessageQueue与Looper相关功能都通过MessageQueue的Native方法来完成,而其他虚线连接的类只有关联关系,并没有直接调用的关系,它们发生关系的桥梁是MessageQueue.
总结:
- Handler发送的消息由MessageQueue存储管理,并由Looper负责回调消息到handlerMessage()
- 线程的切换由Looper完成,handlerMessage()所在线程由Looper.loop()调用者所在线程决定。
下面来列举我们几个工作中或许都会遇到的问题。
Handler引起的内存泄漏原因以及最佳解决方案
Handler允许我们发送延迟消息,如果在延时期间内用户关闭了activity,那么该activity会泄漏。这个泄漏是因为Message会出油Handler,而又因为Java的特性,内部类会持有外部类,使得activity会被Handler持有, 这样最终就会导致activity泄漏了。
解决的办法就是:将Handler定义为静态的内部类,在内部持有activity的弱引用,并在activity的ondestroy()中调用handler.removeCallbacksAndMessage(null)及时移除所有消息。
private static class SafeHandler extends Handler {
private WeakReference<HandlerActivity> ref;
public SafeHandler(HandlerActivity activity) {
this.ref = new WeakReference(activity);
}
@Override
public void handleMessage(final Message msg) {
HandlerActivity activity = ref.get();
if (activity != null) {
activity.handleMessage(msg);
}
}
}
并且再在 Activity.onDestroy() 前移除消息,加一层保障:
@Override
protected void onDestroy() {
safeHandler.removeCallbacksAndMessages(null);
super.onDestroy();
}
为什么我们能在主线程直接使用Handler,而不需要创建Looper?
通常我们认为ActivityThread就是主线程,事实上它并不是一个线程,而是主线程操作的管理者。在ActivityThread.main()方法中调用了Looper.prepareMainLooper()方法创建了主线程的looper,并且调用了loop()方法,所有我们就可以直接使用Handler了。
因此我们可以利用Callback这个拦截机制来拦截Handler的消息,如大部分插件化框架中的Hook ActivityThread.mH的处理。
主线程的Looper不允许退出
主线程不允许退出,退出就意味APP要挂了。
Handler里藏着Callback能干什么?
Handler.Callback 有优先处理消息的权利 ,当一条消息被 Callback 处理并拦截(返回 true),那么 Handler 的 handleMessage(msg) 方法就不会被调用了;如果 Callback 处理了消息,但是并没有拦截,那么就意味着一个消息可以同时被 Callback 以及 Handler 处理。
创建Message实例的最佳方式
为了节省开销,Android给Message设计了回收机制,所以我们在使用的时候尽量复用Message,减少内存的消耗:
- 通过Message的静态方法Message.obtain()
- 通过Handler的共有方法handler.obtainMessage()
子线程里弹Toast的正确姿势
本质上是因为Toast的实现依赖于Handler,按子线程使用Handler的要求修改即可,同理的还有就是dialog。
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();
Toast.makeText(MainActivity.this, "不会崩溃啦!", Toast.LENGTH_SHORT).show();
Looper.loop();
}
}).start();
妙用Looper机制
- 将Runnable post 到主线程执行
- 利用Looper判断当前线程是否是主线程
public final class MainThread {
private MainThread() {
}
private static final Handler HANDLER = new Handler(Looper.getMainLooper());
public static void run(@NonNull Runnable runnable) {
if (isMainThread()) {
runnable.run();
}else{
HANDLER.post(runnable);
}
}
public static boolean isMainThread() {
return Looper.myLooper() == Looper.getMainLooper();
}
}
主线程的死循环一直运行是不是特别消耗CPU资源呢?
并不是,这里就涉及到Linux pipe/epoll机制,简单说就是在主线程的MessageQueue没有消息时,便阻塞在loop的queue.next()中的nativePollOnce()方法里,此时主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生,通过往pipe管道写端写入数据来唤醒主线程工作。这里采用的epoll机制,是一种IO多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质是同步I/O,即读写是阻塞的。所以说,主线程大多数时候都是处于休眠状态,并不会消耗大量CPU资源。
最后
如果你看到了这里,觉得文章写得不错就给个赞呗!欢迎大家评论讨论!如果你觉得那里值得改进的,请给我留言。一定会认真查询,修正不足,定期免费分享技术干货。谢谢!