文章目录
异步消息处理机制
异步消息处理机制概述
1.作用:跨线程间消息传递。一般用于开启子线程执行耗时操作后,需要在执行结束更新UI线程。主线程的UI控件非线程安全,因此android系统不允许直接在子线程更新UI。
2.四元素
异步消息处理机制中主要包括Handler、Looper、MessageQueue、Message。
Handler:消息的处理者与发送者。通过sendmessage
可以发送消息;通过handlemessage
进行处理信息。
MessageQueue:信息队列,内部其实是一个单链表,只负责存储消息,不负责发送消息。
Looper:消息泵,通过loop
方法持续的从消息队列取出消息,并发送至消息对应的Handler。
Message:消息。用来存储一定的消息,并在线程中传递。具有msg.what字段,以此区分不同Message。
3.关系:
1.一个线程只可以有一个Looper,因为Looper类中有一个静态变量ThreadLocal,线程每创建一个新looper时,
都会将这个looper set到ThreaLocal中,ThreadLocal可以通过线程来找到对应looper。
如果一个线程有多个looper就无法找到对应的拿一个Looper了。
2.一个线程可以有多个Handler
3.一个Looper中只有一个MessageQueue
4.Handler可以根据线程找到对应的Looper。
5.MessageQueue中存放多个Message,这些Message可以来自多个Handler。
注意:Handler可以在任意线程发送消息,这些消息会发送到Handler所关联的MessageQueue。Handler在处理时是在它关联的Looper线程中处理消息的。
举个例子:我们首先创建一个Handler对象handler1,其关联的Looper就是主线程的looper1,我们在子线程使用handler1发送信息,信息会发送到looper1的messageQueue中。looper1将信息发送给handler1,handler1在主线程执行UI操作。
异步消息处理机制的工作流程
1.当Handler对象使用sendMessage
发送message后,会调用handler所绑定的looper实例中的MessageQueue的enqueueMessage
方法,将message添加进MessageQueue。
2.Looper的loop
方法会持续调用MessageQueue的next
方法去取出消息。取出消息后,会调用msg对应的Handler的dispatchMessage
方法。
3.在dispatchMessage
方法中,会调用handlemessage
方法,进行UI操作。
异步消息处理机制的具体原理
1.ThreadLocal工作原理
当某些数据以线程为作用域,且不同线程具有不同的数据副本时,我们使用ThreadLocal。
ThreadLocal和信息机制的关系在于,Looper类中有一个静态变量,这个静态变量的类型就是ThreadLocal类。当我们为线程创建Looper时,其实调用了threadLocal的set
方法。Handler可以通过threadlocal对象找到当前线程与之对应的Looper。
ThreadLocal的原理是为每个线程都创建了一个数组来存储值。当ThreadLocal使用get函数获取当前线程的值的时候,首先会根据当前线程找到对应的存储数组,并通过ThreadLocal的下标在数组中找到存储的值。
ThreadLocal主要函数有set与get,首先来看set函数。
set函数
可以看到在set函数中,首先通过currentThread()
方法得到当前线程。在Thread
内部有一个Values
类专门用于存储线程中的ThreadLocal值。接下来通过values(currentThread)
获取当前线程对应的Values对象。之后判断values对象是否为null,如果为null就初始化,否则就调用put函数去设置当前ThreadLocal的值。
public void set(T value) {
Thread currentThread = Thread.currentThread();
Values values = values(currentThread);
if (values == null) {
values = initializeValues(currentThread);
}
values.put(this, value);
}
可以看到在put方法中,会将ThreadLocal的值放置于table的refrence+1
下标处。
void put(ThreadLocal<?> key, Object value) {
cleanUp();
// Keep track of first tombstone. That's where we want to go back
// and add an entry if necessary.
int firstTombstone = -1;
for (int index = key.hash & mask;; index = next(index)) {
Object k = table[index];
if (k == key.reference) {
// Replace existing entry.
table[index + 1] = value;
return;
}
...
}
}
get函数
get函数的逻辑比较清晰,也是首先获取当前线程,并找到当前线程对应的Values对象,进而从table数组中取出reference+1
位置的结果。
public T get() {
// Optimized for the fast path.
Thread currentThread = Thread.currentThread();
Values values = values(currentThread);
if (values != null) {
Object[] table = values.table;
int index = hash & values.mask;
if (this.reference == table[index]) {
return (T) table[index + 1];
}
} else {
values = initializeValues(currentThread);
}
return (T) values.getAfterMiss(this);
}
从ThreadLocal的set和get方法可以看出,他们所操作的对象都是当前线程的localValues对象和table数组,因此在不同线程中访问同一个ThreadLocal的set和get方法,他们对ThreadLocal所做的读写操作仅限于各自内部,这就是为什么ThreadLocal可以在多个线程中互不干扰的存储和修改数据。
2.Lopper工作原理
主线程中可以不创建Looper,但是在子线程中需要手动创建Looper,否则直接在子线程创建的Handler找不到匹配的Looper会报错。
主线程不需要创建Looper是因为已经自动创建了。
//主线程中不需要自己创建Looper
public static void main(String[] args) {
......
Looper.prepareMainLooper();//为主线程创建Looper,该方法内部又调用 Looper.prepare()
......
Looper.loop();//开启消息轮询
......
}
而在子线程需要调用Looper类的静态方法prepare
方法与loop
方法。
//子线程中需要自己创建一个Looper
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();//为子线程创建Looper
Looper.loop(); //开启消息轮询
}
}).start();
在prepare
方法中我们创建新的Looper,并将Looper存储至ThreadLocal中,以便Handler可以通过ThreadLocal找到当前线程对应的Looper。可以从下述代码看到Looper类有一个全局变量ThreadLocal,并在prepare
方法中执行sThreadLocal.set(new Looper())
操作。同时在Looper的构造函数中可以看到会创建一个消息队列MessageQueue。
public class Looper {
// 每个线程中的Looper对象其实是一个ThreadLocal,即线程本地存储(TLS)对象
private static final ThreadLocal sThreadLocal = new ThreadLocal();
// Looper内的消息队列
final MessageQueue mQueue;
// 当前线程
Thread mThread;
// 。。。其他属性
// 每个Looper对象中有它的消息队列,和它所属的线程
private Looper() {
mQueue = new MessageQueue();
mRun = true;
mThread = Thread.currentThread();
}
// 我们调用该方法会在调用线程的TLS中创建Looper对象
public static final void prepare() {
if (sThreadLocal.get() != null) {
// 试图在有Looper的线程中再次创建Looper将抛出异常
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper());
}
// 其他方法
}
loop
方法是一个阻塞的方法,会持续的访问MessageQueue的next方法,直到next方法返回message,之后会将调用message对应Handler(msg.target
)的dispatchmessage
方法,将message发送给Handler去处理。这里当消息队列被标记为退出状态的时候,他的next方法就会返回null,也就是说,Looper必须退出,否则loop方法就会无限循环下去。
public static void loop() {
......
for (;;) {//死循环
Message msg = queue.next();
if (msg == null) {
return;
}
......
try {
msg.target.dispatchMessage(msg);
end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
......
}
2.消息队列MessageQueue的工作原理
MessageQueue主要有两个核心函数enqueueMessage
方法与next
方法,分别对应着读取与插入操作。MessageQueue的内部是一个单链表实现的。
enqueueMessage
会向链表尾部中插入一个元素,而next函数会从链表头部移出一个元素。next
方法是一个阻塞方法,当消息队列中没有消息就会一直阻塞在这里,当有消息后,会将消息移出链表。
3.Handler的工作原理
Handler负责对消息的发送与接收,Handler有两种创建方式。不过不管是post还是send方式,最终调用的都是sendMessage方法发送广播。
//第一种:send方式的Handler创建
Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
//如UI操作
}
};
//第二种:post方式的Handler创建
Handler handler = new Handler();
sendMessage
方法会调用Handler对应的MessageQueue的enqueueMessage
来将消息添加至消息队列。此时Looper会通过next方法获取到这个消息,并调用消息对应Handler的dispatchMessage
方法。
Handler发送Message的代码流程:Handler.sendMessage()->Handler.sendMessageDelayed()->Handler.sendMessageAtTime()->MessageQueue.enqueueMessage()
public final boolean sendMessage(Message msg)
{
return sendMessageDelayed(msg, 0);
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
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, uptimeMillis);
}
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
在dispatchMessage
中,首先,他会检查Message的callback
是否为null,不为null就通过handlerCallback
来处理消息,Message的callback是一个Runnable对象,实际上就是Handler的post方法所传递Runnable参数,handlerCallback的逻辑也很简单。最终都会调用handleMessage方法去处理message。
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
异步消息处理机制的使用
开启子线程
开启子线程首先要new一个Thread
,并在其内部实现一个Runnable
接口,重写其run
函数。一般在android中我们开启子线程是用子线程来完成耗时操作,比如网络请求。
new Thread(new Runnable() {
@Override
public void run() {
//耗时操作的逻辑
}
}).start();
异步消息处理机制
在android中只要在主线程才可以进行UI操作,但在子线程中,我们可能在获取网络数据结束后要更新一些控件的信息,因此Android提供了异步消息处理机制,主要包括Handler(信息的发送者和接收者)与Message(信息的载体,可以携带少量信息在不同线程交换),MessageQueue(信息队列),Looper
代码流程
首先在子线程中创建一个Message
对象,并为它的what
字段赋值,赋值后通过sendMessage(message)
发送消息。之后在主线程中,创建Handler
对象,并实现其内部的handleMessage(Message msg)
方法,并根据msg.what
去匹配接收到的是哪个Message
,并最终更新UI。
图片来源
一个实例。
public static final int UPDATE_TEXT =1;
/*创建Handler实例并重写handleMessage方法*/
private Handler handler=new Handler(){
public void handleMessage(Message msg){
switch (msg.what){
case UPDATE_TEXT:
text.setText("Nice to meet you");
break;
default:
break;
}
}
};
/*开启的子线程*/
new Thread(new Runnable() {
@Override
public void run() {
Message message=new Message();
message.what=UPDATE_TEXT;
handler.sendMessage(message);
}
}).start();
为什么Handler这套机制不会使主线程卡死呢?
答:简单说就是在主线程的MessageQueue没有消息时,便阻塞在loop的queue.next()中的nativePollOnce()方法里,此时主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生,通过往pipe管道写端写入数据来唤醒主线程工作。 所以说,主线程大多数时候都是处于休眠状态,并不会消耗大量CPU资源。
再简单补一下阻塞与ANR的区别:
- Looper上的阻塞,前提是没有消息待处理,MsgQ为空,Looper空闲状态,线程进入阻塞,释放CPU执行权,等待唤醒。
- UI耗时导致卡死,前提是要有消息待处理,MsgQ不为空,Looper正常轮询,线程并没有阻塞,但是该事件执行时间过长( a.主线程对输入事件5秒内没有处理完毕
b.主线程在执行BroadcastReceiver的onReceive()函数时10秒内没有处理完毕
c.主线程在Service的各个生命周期函数时20秒内没有处理完毕。),而且与此期间其他的事件(按键按下,屏幕点击…)都没办法处理(卡死),然后就ANR异常了。
总结(复习必看)
1.消息机制是android用来实现线程间通信的,最常用的场景就是让子线程执行一个耗时操作,执行后,需要更新UI控件,这时就需要用消息机制。而UI控件是线程不安全的,所以不能让UI控件直接在子线程更新。
2.消息机制是指Handler的工作模式。其中包含四个部分,Handler:消息的发出者、执行者;MessageQueue:消息队列,用来存储信息;Looper:消息崩,用来取出MessageQueue的信息并发送给消息对应的Handler处理;Message:消息的载体,具有Message.what字段来匹配Message。
3.四个部分的关系:
一个线程只能有一个Looper,但可以有多个Handler;MessageQueue中可以存储多个Message,这些Message可以来自不同的Handler;Looper中只有一个MessageQueue;Handler可以在任意线程发送消息至Handler所绑定的MessageQueue,但Handler只会在所绑定的Looper所在线程对信息进行处理。
4.消息机制工作流程:
1)Handler会通过sendMessage方法发出消息并调用所绑定的MessageQueue的enqueueMessage方法将消息放入消息队列MessageQueue;
2)Looper的loop方法会持续的调用MessagQueue的next方法,此时next方法会返回message,之后调用message所对应的Handler的dispatchMessage方法交由Handler处理,
3)Handler会调用handleMessage方法进行具体操作。
5.ThreadLocal在信息机制的作用及原理:
Looper类中存在静态的ThreadLocal对象,并在每次为线程创建Looper对象时,都会将Looper对象存入ThreadLocal对象中。ThreadLocal用于帮助Handler找到当前线程对应的Looper,进而找到其MessageQueue,并调用其enqueueMessage方法。
ThreadLocal类为每个线程创建了一个数组,当使用set函数时,会根据当前线程找到对应的数组,并通过ThreadLocal对象将其存入数组中的特定下标reference+1
处。而get函数时同理,也会根据当前线程找到存储的对应数组,并根据ThreadLocal对象找到数组的对应下标reference+1
,并将数值取出。ThreadLocal在不同线程下变更的是不同数组,所以ThreadLocal可以在多个线程中互不干扰的存储和修改数据。
6.Looper的工作原理与使用
想在子线程创建Handler(此Handler与子线程绑定)并使用,需要首先创建Looper,否则会报错。Looper创建包括两个静态方法:Looper.prepare
方法与Looper.loop
方法。
prepare方法中,会为当前线程创建一个新的Looper实例,在Looper构造方法中还会创建MessageQueue。
loop方法中,会持续地调用MessageQueue的next方法,直至next方法返回了消息,此方法是个阻塞方法。但当消息队列被标记位退出状态时,可以由MessageQueue的next方法返回一个null,来停止这个持续的死循环。
7.MessageQueue的工作原理
MessageQueue内部其实是个单链表,主要包含两个方法:enqueMessage
与next
方法。enqueueMessage
方法会在Handler发送消息后调用并将消息插入链表;而**next
方法是一个阻塞方法,会持续的去寻找链表中是否有元素,并删除元素。**
8.Handler的工作原理
Handler有两种创建方式,一种是post一种是send,send方式比较常用,两种方式最终都会调用sendMessage方法将消息传送。之后会调用当前handler所匹配的MessageQueue的enqueueMessage
方法将消息添加至链表中。
当Looper调用消息队列的next
函数返回msg后,会调用msg匹配Handler的dispatchMessage
方法交由Handler处理,最终Handler会调用handlemessage
进行实际操作。
9.消息机制的具体使用实例
首先在主线程创建一个Handler实例,并重写其handleMessage方法。开启一个子线程,并在子线程耗时操作结束后使用Handler实例调用sendMessage发送消息。
10.Message的创建方式:
后两种比较好,后两种都是从Message池中返回一个Message实例,可以避免Message的重复创建。
1.Message = new Message();
2.Message = Message.obtain();
3.Message = handler1.obtainMessage();
11.使用Hanlder的postDelay()后消息队列会发生什么变化?
postDelay发送的消息并不是延迟一会在发送,而是 发送到MessageQueue后,直接阻塞线程。与MessageQueue的队首元素根据触发时间比较,始终让触发时间短的在队首,让触发时间长的在队尾。此时如果队首就是delay的消息,就会将线程阻塞delay的时间,之后在执行该Message。
参考链接
本文中的图出自:
android的消息处理机制(图+源码分析)——Looper,Handler,Message
要点提炼|开发艺术之消息机制