Android的消息机制
一、什么是 Android 消息机制
首先我们需要了解两个前提:一是 Android 的主线程(即 UI 线程)无法执行耗时操作,否则会出现 ANR(应用程序无响应) 的情况;二是 Android 不允许在子线程中更新 UI。
因此,我们一般会将 I/O 读写或网络请求等耗时操作放到子线程中执行,执行完毕后,再切换回主线程执行 UI 更新的操作。这所谓的“切换线程执行”其实就是线程间的通信,而像这样将一个任务切换到指定的线程中去执行的操作,就需要借助 Handler 来完成。
因此,Android 的消息机制通常也称为 Handler 机制,它主要用于实现主线程(即 UI 线程)与其它线程之间的通信。这种机制可以避免应用程序因为执行耗时操作而导致 ANR 的情况发生。
二、Handler 机制的核心组成
- Message(消息):Message 作为消息机制中的基本单元,携带了需要传递的数据。我们可以通过 Message 对象的 what 字段来标识不同类型的消息,并且可以通过 obj 字段或者自定义的 Bundle 对象来携带额外的数据。
- MessageQueue(消息队列):MessageQueue 负责存储待处理的消息。当新的消息被添加到队列后,它们会被按照先进先出的顺序处理。
- Looper(循环器):Looper 维护了一个无限循环,不断地从 MessageQueue 中取出消息,并分发给对应的 Handler 进行处理。通常每个线程只会有一个 Looper 实例,它是通过调用Looper.prepare() 创建的,并通过调用 Looper.loop() 开始消息循环。
- Handler(处理器):Handler 是消息的发送者和接收者。它可以通过 sendMessage() 方法将 Message 对象放入 MessageQueue。当 Looper 取出消息后,会调用 Handler 的 handleMessage() 方法来处理消息。
注意:每个线程有且只有一个 MessageQueue 和 Looper,可以有很多个 Handler。
三、简述 Handler 机制的工作流程
- 创建 Looper;
- 通过 Looper 创建 Handler 实例;
- 启动 Looper;
- 在非 UI 线程中通过 Handler 发送消息到 MessageQueue;
- Looper 不断地从 MessageQueue 中取出消息,交给对应的Handler 进行处理;
- Handler 处理消息。
四、Handler 机制的具体流程解析
首先要想使用 Handler 就需要为线程创建一个 Looper,应用程序在启动过程中就通过 Looper.prepareMainLooper 为主线程创建了 Looper 对象,这也是在主线程中默认可以使用 Handler 的原因。
而在子线程中我们需要通过 Looper.prepare() 来创建 Looper 对象,这个 Looper 对象会被存放到 ThreadLocal 中,这样可以保证每个线程都有自己唯一对应的 Looper 对象。在 Looper 对象初始化的过程中,它会创建一个 MessageQueue 对象,让 Looper 持有它,因此一个 Looper 对应一个 MessageQueue。
第二步是创建 Handler,首先创建子类继承自 Handler,然后重写 handleMessage 方法。在 Handler 初始化的过程中会调用 Looper.myLooper() 获取当前线程的 Looper 对象,让 Handler 持有它,再让 Handler 持有 Looper 中的 MessageQueue 对象,这样 Handler 才能将消息发送到对应线程的 MessageQueue 中,最后再给它设置处理回调的 Callback 对象(如果在构造函数中有传入的话)。
我们可以通过 Handler 的 send 或 post 的相关方法发送消息,这两种方式最终都会走到 sendMessageAtTime 方法中,将 Handler 本身设置给 Message 的 target 属性,然后调用 MessageQueue 的 enqueueMessage 方法将消息入队。
第三步通过 Looper.loop() 方法来启动消息队列的循环运转。在该函数中,首先通过 Looper 得到当前线程的 MessageQueue。然后在一个死循环中不断调用 MessageQueue 的 next 方法取出其中的 Message 交给对应的 Handler 处理,Handler 的 dispatchMessage 方法会被调用。在这个方法中,如果前面我们是通过 post(Runnable) 方法发送的消息,则会回调 Runnable 的 run 方法,如果构造 Handler 时传入了 Callback 对象,则会回调 Callback 对象的 handleMessage 方法,如果前面两种情况都不是,最后就会执行 Handler 的 handleMessage 方法来处理消息。
当 MessageQueue 中没有消息时,next 方法会阻塞线程,释放CPU资源,直到下个消息到来。至此,实现了整个Android的消息机制。
五、拓展
Android系统为什么不允许在子线程中访问UI?
这是因为Android的UI控件不是线程安全的,如果在多线程中并发访问可能会导致UI控件处于不可预期的状态。
那么为什么Android系统不对UI控件的访问加上锁机制呢?
- 首先加上锁机制会让UI访问的逻辑变得复杂;
- 锁机制会降低UI访问的效率,因为锁机制会阻塞某些线程的执行
所以最简单且高效的方法就是采用单线程模型来处理UI操作,也就是只允许通过主线程访问UI。
创建 Handler 的方式有哪些?
- 匿名内部类的方式
public class MainActivity extends AppComposeActivity{
...;
// 匿名内部类的方式创建一个 Handler实例
private Handler myHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case XXX:
break;
// 其他case处理...
default:
super.handleMessage(msg);
}
}
};
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
}
}
- 创建子类继承自Handler类
public class MainActivity extends AppComposeActivity{
...;
private MyHandler handler = new MyHandler(myActivity, Looper.getMainLooper());
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
// 使用 Handler发送消息
....
}
@Override
protected void onDestroy() {
super.onDestroy();
myHandler.removeCallbacksAndMessages(null);
}
// 创建子类继承自Handler(使用静态内部类+弱引用避免内存泄露)
public static class MyHandler extends Handler {
private final WeakReference<MainActivity> weakRef;
public MyHandler(MainActivity activity, Looper looper) {
super(looper);
weakRef = new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
// 在这里实现具体的逻辑处理
MyActivity activity = weakRef.get();
switch (msg.what) {
case XXX:
if (activity != null) {
// 更新UI或其他操作
}
break;
// 其他 case处理...
default:
super.handleMessage(msg);
}
}
}
}
对比来看,通过匿名内部类的方式来创建 Handler 显然更为简便,但在追求代码质量和长期维护的角度看,创建子类继承 Handler 的方式则提供了更多的灵活性和更好的代码管理方式。
为什么说 Handler 机制会造成内存泄露?
Handler 机制本身并没有任何问题,造成内存泄露是因为使用的方法不当。
在 Handler 使用的过程中,我们应该将创建的 Handler 子类定义为静态内部类,然后使用弱引用来引用 Activity 对象。如果使用 Handler 发送了延时消息,那么在 onDestory 方法中,还需要调用 Handler 的 removeMessages() 或 removeCallbacks() 方法,把消息从 MessageQueue 中移除,否则就可能会造成内存泄露。
原因是非静态内部类(包括匿名内部类)会隐式地持有外部类对象的引用,而 Handler 发出的 Message 会持有 Handler 的引用。如果我们在 Activity 中发送了一个延迟消息,然后 Activity 被关闭了,那么由于存在 Message -> Handler -> Activity 这样一条引用链,就会导致 Activity 就无法被回收,进而造成内存泄露。
为什么非静态内部类(包括匿名内部类)会隐式地持有外部类对象的引用?
当使用内部类来创建 Handler 的时候,编译器会为它增加一个外部类的属性,同时它的构造函数增加一个参数,这个参数指向了当前的外部类对象,因此 handler 会默认持有当前 Activity 对象。
Looper 从 MessageQueue 中取出 Message 是如何交给指定的 Handler 的呢?
Looper 拿到 Message 以后,它会检查 Message 的 target 属性,然后调用 target 的 dispatchMessage() 方法处理该消息。这个 target 其实就是发送该消息的 Handler,因为 Handler 在发送消息的过程中会把自己设置给 Message 对象的 target 属性。因此,最后这个消息还是由发送它的 Handler 来进行处理,只不过此时已经位于主线程中了。
使用 sendXXX 和 postXXX 发送消息的区别?
使用 sendXXX 发送的是 Message 对象,允许你携带额外的信息,如 what 标识符、arg1 和 arg2 作为整数参数,以及 obj 作为任意对象数据。而使用 postXXX() 发送的是一个 Runnable 对象,这个 Runnable 包含了需要执行的代码块。
虽然最终都会在目标线程的 Looper 中执行,但 Message 通过 handleMessage() 方法处理,而 Runnable 直接执行其中的 run() 方法。
在取消方面,removeCallbacksAndMessages() 可以用来移除 postXXX 的 Runnable,而 removeMessages() 可以用来移除 sendXXX 的 Message。
因此使用 sendXXX 更适合于需要传递复杂数据结构或需要精确控制消息类型的情况,而使用 postXXX 适用于简单的异步任务,不需要传递复杂的参数,只需要执行一段代码的情况。