第二节课_Handler|青训营笔记
这是我参与「第四届青训营 」笔记创作活动的的第3天
线程通信_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()里的死循环卡死? - 知乎 (zhihu.com)
进程通信_Binder
参考
Handler 都没搞懂,拿什么去跳槽啊? - 掘金 (juejin.cn)
handler - 黄朝旺 - 博客园 (cnblogs.com)
一次性讲清楚 Handler 可能导致的内存泄漏和解决办法 | 开发者说·DTalk (qq.com)