线程通信_Handler
Handler
作用
在通常的情况下,我们会在子线程处理耗时操作,等待子线程耗时操作结束之后,再通知主线程更新UI。
Handler
是运行在主线程中的,它的作用就是帮助我们在子线程和主线程之间进行线程通信。我们可以在子线程中发送消息通知主线程,然后主线程中监听接收消息,进而对该消息进行处理。
初识 Handler
的话,我们需要记住的是,他是一个不断在 looper
之中不断轮询的一个消息队列。我们可以不断的往这个轮询的池子中丢入信息,然后在获取的时候就是不断从池子里面的接受信息。
其基本用法也就是 发送
接收
android.os.Handler handler = new Handler(Looper.getMainLooper()){ @Override public void handleMessage(final Message msg) { //这里接受并处理消息 } }; //发送消息 handler.sendMessage(message); handler.post(runnable); 复制代码
//简单示意 //子线程发消息 Thread { val msg = Message() msg.what = 1 handler?.handleMessage(msg) }.start() //主线程接收消息 handler = object : Handler(Looper.getMainLooper()){ override fun handleMessage(message: Message){ super.handleMessage(message) if (message.what == 1){ val TAG = "hhh" Log.d(TAG, "handleMessage: ${message.what}") } } } 复制代码
Handler
浅析
Handler
与线程如何关联?Handler
在创建的时候是要先创建Looper
的,就如上边的示意代码,创建Handler
的时候,得传入一个Looper
类型到构造器。
而Looper 使用
Looper.prepare()
方法来创建 Looper ,并借助 ThreadLocal 来实现与当前线程的绑定功能。
Handler
发出去的消息是谁管理的?
我们查看sendMessage(message)
或者post(runnable)
可以得知,最后他们都会调用MessageQueue.enqueueMessage(Message,long)
方法,即消息是由MessageQueue
管理的- 消息又是怎么回到
handleMessage()
方法的?线程的切换是怎么回事?
Looper.loop() 是个死循环,会不断调用 MessageQueue.next() 获取 Message ,并调用
msg.target.dispatchMessage(msg)
回到了 Handler 来分发消息,以此来完成消息的回调
Thread.foo(){ Looper.loop() //不断循环获取Message -> MessageQueue.next() //获取到之后,Message.target 就是发送该消息的 Handler,回到该Handler分发消息 -> Message.target.dispatchMessage() -> Handler.handleMessage() } 复制代码
Handler.handleMessage() 所在的线程最终由调用 Looper.loop() 的线程所决定
平时我们用的时候从异步线程发送消息到 Handler,这个 Handler 的
handleMessage()
方法是在主线程调用的,所以消息就从异步线程切换到了主线程。这就是线程的切换了
小结
- Looper :负责关联线程以及消息的分发,在调用
Looper
线程下从 MessageQueue 获取 Message,分发给 Handler ;- MessageQueue :一个消息队列,负责消息的存储与管理,负责管理由 Handler 发送过来的 Message ;
- Handler : 负责发送并处理消息,面向开发者,提供 API,并隐藏背后实现的细节。
Handler 发送的消息由 MessageQueue 存储管理,并由 Loopler 负责回调消息到 handleMessage()。
线程的转换由 Looper 完成,handleMessage() 所在线程由 Looper.loop() 调用者所在线程决定。
Handler
泄露问题
为什么 Handler
会引发泄露呢?
其主要原因是,我们错误的使用了 Handler
;这导致我们在关闭 Activity
的时,Handler
或者其他还在运行的类仍引用了 Activity
。这就导致 Activity
无法被回收,就会发生泄露。
有可到达 Activity
的引用链,可能会导致泄露的情况:
Thread
或者Handler
为匿名内部类,持有了外部的Activity
。Thread
没了,但是其运行的Message
还在发送。这会导致Looper
也间接持有Activity
GC root原理:通过对枚举GCroot对象做引用可达性分析,即从GC root对象开始,向下搜索,形成的路径称之为 引用链。如果一个对象到GC roots对象没有任何引用,没有形成引用链,那么该对象等待GC回收。
GC root对象是什么?
Java中可以作为GC Roots的对象
1、虚拟机栈(javaStack)(栈帧中的局部变量区,也叫做局部变量表)中引用的对象。
2、方法区中的类静态属性引用的对象。
3、方法区中常量引用的对象。
4、本地方法栈中JNI(Native方法)引用的对象。
解决 Handler
泄露
- 强应用 Activity 改为弱引用
- 及时切断两大 GC Root 的引用链关系: Main Looper 到 Message;以及结束子线程。
步骤1:Handler设为静态内部类,且对 Activity
弱引用
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); } } } 复制代码
步骤2:onDestroy()
的时候切断引用链关系
//1.若子线程任务尚未结束,及时中断 @Override public void onDestroy() { ... thread.interrupt(); } //子线程中创建了 Looper 并成为了 Looper 线程的话,须手动 quit @Override public void onDestroy() { ... handlerThread.quitSafely(); } //主线程的 Looper 不可quit,退出App就挂掉了,所以要清除所有未处理的 message @Override public void onDestroy() { ... mainHandler.removeCallbacksAndMessages(null); } 复制代码
小结
Handler
处理中持有Activity
的,其生命周期应当与Activity
一致,才是正确的用法最正确用法应该是:
- 使用
Handler
机制,采用弱引用 + 静态内部类。保证错误延长了周期也能正确 GCActivity
结束时候,清空 Message、终止 Thread 或退出 Looper。及时切除引用链
正确在子线程使用 Toast
由于 Toast
就是依赖于 Handler 完成的,所以在子线程中需要学习完整的 Handler 使用方式
//完整的需要有下面 1 2 两步 class LooperThread extends Thread { public Handler mHandler; public void run() { Looper.prepare();//1 mHandler = new Handler() { public void handleMessage(Message msg) { // process incoming messages here } }; Looper.loop();//2 } } 复制代码
//得出下列代码 new Thread(new Runnable() { @Override public void run() { Looper.prepare(); Toast.makeText(HandlerActivity.this, "不会崩溃啦!", Toast.LENGTH_SHORT).show(); Looper.loop(); } }).start(); 复制代码
Looper一直死循环,为啥主线程不会卡死
死循环是为了保证主线程能一直执行下去,但是事实上他并不是简单的无限循环下去,而是会休眠的。当没有消息的时候,Handler会开始休眠,直到有消息传递的时候就会唤醒它。大部分情况下,Handler都是在休眠中。详细分析可以看Android中为什么主线程不会因为Looper.loop()里的死循环卡死?