概述
什么是Android消息机制
Android中的消息机制主要是指Handler的运行机制以及Handler所附带的MessageQueue和Looper的工作过程,这三个实际上是一个整体。-
Handler的作用,为什么有Handler
Handler的主要作用:负责跨线程通信。因为在主线程不能做耗时操作,而子线程不能更新UI,所以当子线程中进行耗时操作后需要更新UI时,通过Handler将有关UI的操作切换到主线程中执行。
系统为什么不允许在子线程中访问UI?
因为Android的UI控件不是线程安全的,如果在多线程中并发访问可能会导致UI控件处于不可预期的状态。
为什么系统不对UI控件的访问加上锁机制呢?
加上锁机制会让UI访问的逻辑变得复杂
锁机制会降低UI访问的效率,因为锁机制会阻塞某些线程的执行。
鉴于这两个缺点,最简单且高效的方法就是采用单线程模型来处理UI操作。
- 几个相关概念
Message:消息,被传递和处理的数据。其中包含了消息ID,消息处理对象以及处理的数据等,由MessageQueue统一列队,终由Handler处理。
Handler:处理者,负责Message的发送及处理。
Handler的主要作用:(有两个主要作用)
1. Handler.sendMessage(),在工作线程中发送消息;
2. Handler.handleMessage(),在主线程中获取、并处理消息。MessageQueue:消息队列,本质是一个单链表,用来存放Handler发送过来的消息,并按照FIFO规则执行。当然,存放Message并非实际意义的保存,而是将Message串联起来,等待Looper的抽取。
Looper:消息泵或循环器,通过Looper.loop()不断从MessageQueue中抽取Message。因此,一个MessageQueue需要一个Looper。
Thread:线程,负责调度整个消息循环,即消息循环的执行场所。
一个Thread只能有一个Looper,一个MessageQueue,可以有多个Handler。
-
工作流程
- Handler通过sendMessage()发送Message时,Looper的成员变量MessageQueue会通过enqueueMessage()向MessageQueue中添加一条消息。
此时Message会将Handler对象赋值给Message对象的target参数。
Looper通过loop()方法开启循环后,不断轮询调用MessageQueue的next()方法来获取Message。
-
通过调用Messag目标Handler的dispatchMessage()方法去传递消息,目标Handler收到消息后调用handleMessage()方法处理消息
简单来说,Handler通过sendMessage()方法将Message发送到Looper的成员变量MessageQueue中,之后Looper通过loop()方法不断循环遍历MessageQueue从中读取Message,最终回调给Handler处理。
消息机制分析
- ThreadLocal
概念:
ThreadLocal是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储以后,只有在指定线程中可以获取到存储的数据,对于其他线程来说则无法获取到数据。-
使用场景:
- 一般来说,当某些数据是以线程为作用域并且不同线程具有 不同的数据副本的时候,就可以考虑采用ThreadLocal。
- 复杂逻辑下的对象传递。
-
ThreadLocal 的set和get方法原理
-
set原理
-
get原理
-
-
消息队列
消息队列(MessageQueue)主要包含了两个操作:插入和读取。尽管被称为队列,但是它的内部实现是通过一个单链表的数据结构来维护的消息列表,单链表在插入和删除上比较有优势。
- 插入
插入的方法是enqueueMessage,作用是往消息队列中插入一条消息。主要操作是单链表的插入操作。
插入的规则:
会根据message的延迟时间来进行插入,无延迟的会放到链表的头部,
有延迟的,延迟时间越长越靠后
- 读取
读取对应的方法是next,作用是从消息队列中取出一条消息并将其从消息队列中移除。next方法是一个无限循环的方法,如果消息队列没有消息,那么next方法会一直阻塞,当有新的消息到来时,next方法会返回这条信息并将其从单链表中移除。
-
Looper
Looper在Android的消息机制中扮演者消息循环的角色,具体来说,它会不停地从MessageQueue中查看是否有新的消息,如果有新消息就会立刻处理,否则就会一直阻塞在那里。
- Looper的使用
UI线程会自动创建Looper,子线程需自行创建。
通过Looper.prepare()即可为当前线程创建一个Looper。
//子线程中需要自己创建一个Looper
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();//为子线程创建Looper
Handler handler = new Handler();
Looper.loop(); //开启消息轮询
}
}).start();
prepare()
public static void prepare() {
prepare(true);
}
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
所创建的Looper会保存在ThreadLocal(线程本地存储区)中,它不是线程,作用是帮助Handler获得当前线程的Looper。
ThreadLocal.set()
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
获取当前线程,然后获取当前线程的ThreadLocalMap,通过map.set方法,将ThreadLocal和当前的Lopper对象存到map中。
ThreadLocal.get()
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
所以一个线程只会有一个ThreadLocal和一个Lopper,并且Looper是唯一的,不可被替换的。
在这里我们看到了Looper的构造方法
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
我们看到它会创建一个MessageQueue,然后将当前线程的对象保存起来。
- 除了prepare(),还提供prepareMainLooper(),本质也是通过prepare(),主要是给主线程也就是ActivityThread创建Looper使用的。
- 由于主线程Looper比较特殊,所以Looper提供了一个getMainLooper的方法,通过它可以在任何地方获取到主线程的Looper。
- 无论是主线程还是子线程,Looper只能被创建一次,即一个Thread只有一个Looper。
- Looper的退出
- quit:直接退出Looper
- quitSafely:只是设定一个退出标记,然后把消息队列中的已有消息处理完毕后才安全地退出。
在子线程中,如果手动为其创建了Looper,那么在所有的事情完成以后应该调用quit方法来终止消息循环,否则这个子线程就会一直处于等待的状态,而如果退出Looper以后,这个线程就会立刻终止,因此建议不需要的时候终止Looper。
-
loop方法原理
-
Handler
Handler的主要工作包含消息的发送和接收过程。
- 发送有两种方式 send 和 post
//send方式的Handler创建
Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
//如UI操作
mTextView.setText(new_str);
}
};
//send
mHandler.sendEmptyMessage(0);
//post方式
private Handler mHandler = new Handler();
mHandler.post(new Runnable() {
@Override
public void run() {
mTextView.setText(new_str);
}
});
post方式最终还是通过一系列send方法实现的
public final boolean sendMessageAtFrontOfQueue(Message msg) {
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
return enqueueMessage(queue, msg, 0);
}
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
从上边的代码可以发现,Handler发送消息的过程仅仅是向消息队列插入了一条信息,MessageQueue的next方法就会返回这条信息给Looper,Looper收到消息后就开始处理了,最终消息由Looper交给Handler处理,即Handler的dispatchMessage方法会被调用,这是Handler就进入了处理消息的阶段。
- 处理消息
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
最终会调用Handler的handleMessage方法来处理消息。
知识点延伸
-
Handler泄露的原因及正确写法
如果直接在Activity中初始化一个Handler对象,会报如下错误:
This Handler class should be static or leaks might occur
原因是:
在Java中,非静态内部类会持有一个外部类的隐式引用,可能会造成外部类无法被GC;
比如这里的Handler,就是非静态内部类,它会持有Activity的引用从而导致Activity无法正常释放。
当一个消息是一个延迟消息的时候,这个Message会一直存在MessageQueue中,因为Message的target对应的就是Handler,然后Handler持有Activity的引用,所以就会导致了Activity无法被正常释放。
解决方法:
将 Handler 定义成静态的内部类,在内部持有Activity的弱引用,并在Acitivity的onDestroy()中调用handler.removeCallbacksAndMessages(null)及时移除所有消息。
private static class MyHandler extends Handler {
//创建一个弱引用持有外部类的对象
private final WeakReference<MainActivity> content;
private MyHandler(MainActivity content) {
this.content = new WeakReference<MainActivity>(content);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
MainActivity activity= content.get();
if (activity != null) {
switch (msg.what) {
case 0: {
activity.notifyUI();
}
}
}
}
}
-
Message可以如何创建?哪种效果更好,为什么?
- 直接生成实例Message m = new Message
- Message msg=Message.obtain();
- Message msg=handler.obtainMessage();
而后两者是直接在消息池中取出一个Message实例,这样做就可以避免多生成Message实例。
-
Looper死循环为什么不会导致应用卡死?
- 主线程的主要方法就是消息循环,一旦退出消息循环,那么你的应用也就退出了,Looer.loop()方法可能会引起主线程的阻塞,但只要它的消息循环没有被阻塞,能一直处理事件就不会产生ANR异常。
- 造成ANR的不是主线程阻塞,而是主线程的Looper消息处理过程发生了任务阻塞(即Looper中有消息,但是消息没有得到及时的处理),无法响应手势操作,不能及时刷新UI。
- 阻塞与程序无响应没有必然关系,虽然主线程在没有消息可处理的时候是阻塞的,但是只要保证有消息的时候能够立刻处理,程序是不会无响应的。
为什么主线程可以直接new Handler()
因为在ActivityMainThread的main()方法里调用了Looper.prepareMainLooper()方法。
在其他线程如果要使用Handler,必须要调用Looper.prepare()方法。子线程中维护的Looper,消息队列无消息的时候的处理方案是什么?有什么用?
next()中的:nativePollOnce(ptr, nextPollTimeoutMillis); 阻塞线程
quit()中的:nativeWake(mPtr); 唤醒线程既然可以存在多个Handler往MessageQueue中添加数据(发消息时各个Handler可能处于不同线程),那它内部是如何确保线程安全的?
这里主要关注 MessageQueue 的消息存取即可,看源码内部的话,在往消息队列里面存储消息时,会拿当前的 MessageQueue 对象作为锁对象,这样通过加锁就可以确保操作的原子性和可见性了。
消息的读取也是同理,也会拿当前的 MessageQueue 对象作为锁对象,来保证多线程读写的一个安全性。