Android的Handler消息机制
Handler消息机制
Android消息循环流程图
每个 Handler 都会跟一个线程绑定,并与该线程的 MessageQueue 关联在一起,从而实现消息的管理以及线程间通信。
主要涉及的角色如下所示:
- 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,并隐藏背后实现的细节。
==整个消息的循环流程==还是比较清晰的,具体说来:
- Handler通过sendMessage()发送消息Message到消息队列MessageQueue。
- Looper通过loop()不断提取触发条件的Message,并将Message交给对应的target handler来处理。
- target handler调用自身的handleMessage()方法来处理Message。
总结
- 在实例化 Handler 的时候 Handler 会去检查当前线程的 Looper 是否存在,如果不存在则会报异常,也就是说==在创建 Handler 之前一定需要先创建 Looper==。
- Handler 发送的消息由 MessageQueue 存储管理,并由 Looper 负责回调消息到 handleMessage()。
- 线程的转换由 Looper 完成,==handleMessage() 所在线程由 Looper.loop() 调用者所在线程决定==,不是创建handler的线程。
- Looper 有退出的功能,但是主线程的 Looper 不允许退出;
- 异步线程的 Looper 需要自己调用 Looper.myLooper().quit(); 退出;
问题
- Handler 引起的内存泄露原因以及最佳解决方案?
泄漏原因:handler 允许我们发送延时消息,如果在延时期间用户关闭了 Activity,那么该 Activity 会泄露。 这个泄露是因为 Message 会持有 Handler,而又因为 Java 的特性,内部类会持有外部类,使得 Activity 会被 Handler 持有,这样最终就导致 Activity 泄露。
解决:将 Handler 定义成静态的内部类,在内部持有 Activity 的弱引用,并在Acitivity的onDestroy()中调用handler.removeCallbacksAndMessages(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);
}
}
}
@Override
protected void onDestroy() {
safeHandler.removeCallbacksAndMessages(null);
super.onDestroy();
}
- 为什么我们能在主线程直接使用 Handler,而不需要创建 Looper ?
通常我们认为 ActivityThread 就是主线程。事实上它并不是一个线程,而是主线程操作的管理者。在 ActivityThread.main() 方法中调用了 Looper.prepareMainLooper() 方法创建了 主线程的 Looper ,并且调用了 loop() 方法,所以我们就可以直接使用 Handler 了。
在子线程中使用:
class LooperThread extends Thread {
public Handler mHandler;
public void run() {
Looper.prepare();
mHandler = new Handler() {
public void handleMessage(Message msg) {
// process incoming messages here
}
};
Looper.loop();
}
}
Looper 提供了 Looper.prepare() 方法来创建 Looper ,并且会借助 ThreadLocal来实现与当前线程的绑定功能。Looper.loop() 则会开始不断尝试从 MessageQueue 中获取 Message , 并分发给对应的 Handler。也就是说 Handler 跟线程的关联是靠 Looper 来实现的。一个线程只能有一个Looper
- Handler 里的 Callback 是干什么的?
Handler.Callback 有优先处理消息的权利 ,当一条消息被 Callback 处理并拦截(返回 true),那么 Handler 的 handleMessage(msg) 方法就不会被调用了;如果 Callback 处理了消息,但是并没有拦截,那么就意味着一个消息可以同时被 Callback 以及 Handler 处理。
- 创建 Message 实例的最佳方式
为了节省开销,Android 给 Message 设计了回收机制,所以我们在使用的时候尽量复用 Message ,减少内存消耗:
- 通过 Message 的静态方法 Message.obtain();
- 通过 Handler 的公有方法 handler.obtainMessage()。
- 子线程里弹 Toast 的正确姿势?
在子线程弹Toast会报错,本质上是因为 Toast 的实现依赖于 Handler,按子线程使用 Handler 的要求修改即可
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();
Toast.makeText(HandlerActivity.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();
}
}
-
==Android为什么要设置只能通过Handler机制更新UI==?
最根本的问题解决多线程并发的问题;假设如果在一个Activity中,有多个线程去更新UI,并且都没有加锁机制,马么会产生生么样的问题?——更新界面混乱;如果对更新UI 的操作都加锁处理的话会产生什么样子的问题?——性能下降。对于上述问题的考虑,Android提供了一套更新UI的机制,我们只需要遵循这样的机制就好了。 不用关心多线程的问题,更新UI的操作,都是在主线程的消息队列当中轮询处理的。 -
HandlerThread的作用是什么?
HandlerThread thread=new HandlerThread("handler thread");自动含等待机制,等Looper创建好了,才创建Handler,避免出现空指针异常。 -
==非UI线程真的不能更新UI吗==?
不一定,之所以子线程不能更新界面,是因为Android在线程的方法里面采用checkThread进行判断是否是主线程,而这个方法是在ViewRootImpl中的,这个类是在onResume里面才生成的,因此,如果这个时候子线程在onCreate方法里面生成更新UI,而且没有做阻塞,就是耗时多的操作,还是可以更新UI的。
- 一个线程能否创建多个Handler,Handler跟Looper之间的对应关系 ?
- ==一个Thread只能有一个Looper,一个MessageQueen,可以有多个Handler==
- 以一个线程为基准,他们的数量级关系是: Thread(1) : Looper(1) : MessageQueue(1) : Handler(N)
- Looper死循环为什么不会导致应用卡死?
- 主线程的主要方法就是消息循环,一旦退出消息循环,那么你的应用也就退出了,Looer.loop()方法可能会引起主线程的阻塞,但只要它的消息循环没有被阻塞,能一直处理事件就不会产生ANR异常。
- 造成ANR的不是主线程阻塞,而是主线程的Looper消息处理过程发生了任务阻塞,无法响应手势操作,不能及时刷新UI。
- 阻塞与程序无响应没有必然关系,虽然主线程在没有消息可处理的时候是阻塞的,但是只要保证有消息的时候能够立刻处理,程序是不会无响应的。
- 使用Handler的postDealy后消息队列会有什么变化?
- 如果队列中==只有这个消息==,那么消息不会被发送,而是计算到时唤醒的时间,==先将Looper阻塞,到时间就唤醒它==。但如果此时==要加入新消息==,该消息队列的队头==跟delay时间相比更长,则插入到头部,按照触发时间进行排序,队头的时间最小、队尾的时间最大。==