Android的消息机制
Android的消息机制主要是指Handler的运行机制,Handler的运行需要底层的MessageQueue和Looper的支撑。MessageQueue的内部采用单链表的数据结构来存储消息列表。Looper会以无限循环的形式去查找是否有新消息,如果有的话就处理消息,否则就一直等待。Looper中有一个特殊的概念,就是ThreadLocal,ThreadLocal并不是线程,它的作用是可以在每个线程中存储数据。Handler创建的时候会使用当前线程的Looper来构造消息循环系统,Handler在内部就是通过使用ThreadLocal来获取每个线程的Looper。
线程默认是没有Looper的,如果需要使用Handler就必须为线程创建Looper。在主线程中可以直接使用Handler是因为主线程就是ActivityThread,ActivityThread被创建时就会初始化Looper。
Handler消息机制概述
Handler的主要作用是将一个任务切换到某个指定的线程中去执行。
问:为什么Android不允许在子线程中访问UI呢?
答:Android的UI控件不是线程安全的,如果在多线程中并发访问可能会导致UI控件处于不可预期的状态。
问:为什么Android系统不给UI控件的访问加上锁机制呢?
答:1.加上锁机制会让UI访问的逻辑变得复杂。2.锁机制会降低UI访问的效率,因为锁机制会阻塞某些线程的执行。鉴于这两个缺点,最简单高效的方法就是采用单线程模型来处理UI操作,对于开发者来说只需要通过Handler切换一下UI访问的执行线程即可。
ThreadLocal的工作原理
ThreadLocal是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储以后,只有在指定线程中可以获取到存储的数据,对于其他线程来说则无法获取到数据。ThreadLocal另一个使用场景是复杂逻辑下的对象传递。下面通过列子代码来说明。
package com.tom.threadlocaldemo;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
public class ThreadLocalDemo extends AppCompatActivity {
private ThreadLocal<Boolean> mBooleanThreadLocal = new ThreadLocal<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_thread_local_demo);
mBooleanThreadLocal.set(true);
Log.d("ThreadLocal","主线程 :" + mBooleanThreadLocal.get());
new Thread("子线程 1"){
@Override
public void run() {
mBooleanThreadLocal.set(false);
Log.d("ThreadLocal","子线程 1 :" + mBooleanThreadLocal.get());
}
}.start();
new Thread("子线程 2"){
@Override
public void run() {
Log.d("ThreadLocal","子线程 2 :" + mBooleanThreadLocal.get());
}
}.start();
}
}
运行并查看输入日志,结果如下图:
ThreadLocal运行结果在主线程中设置了mBooleanThreadLocal的值为true,在子线程1中设置为false,在子线程2中没有设置,通过get方法,分别获取到的值为true,false,null,符合预期结果。
ThreadLocal的值在table数组中的存储位置总是为ThreadLocal的reference字段所标识的对象的下一个位置,例如ThreadLocal的reference对象在table数组中的索引为index,那么ThreadLocal的值在table数组中的索引就是index + 1。最终ThreadLocal的值将会被存储在table数组中:table[index +1] = value。
关于ThreadLocal的源码解析,可以查看任老师的书。
消息队列的工作原理
消息队列在Android中指的是MessageQueue,MessageQueue主要包含两个操作:插入和读取。读取操作本身会伴随着删除操作,插入的方法为enqueueMessage,往消息队列中插入一条信息,读取的方法为next,从消息队列中取出一条消息并将其从消息队列中删除。MessageQueue是通过一个单链表的数据结构来维护的。
Looper的工作原理
Handler的工作需要Looper,没有Looper的线程就会报错,通过Looper.prepare()可以为当前线程创建一个Looper,接着通过Looper.loop()来开启消息循环。
new Thread("子线程"){
@Override
public void run() {
Looper.prepare();
Handler handler = new Handler();
Looper.loop();
}
}.start();
Looper除了prepare方法外,还提供了prepareMainLooper方法,这个方法主要是给主线程也就是ActivityThread创建Looper使用的,其本质也是通过prepare方法来实现的。由于主线程的Looper比较特殊,所以Looper提供了一个getMainLooper方法,通过它可以在任何地方获取到主线程的Looper。Looper提供了quit和quitSafely来退出一个Looper,quit会直接退出Looper,而quitSafely只是设定一个退出标记,然后把消息队列中的已有消息处理完毕后才安全的退出。在执行quit或者quitSafely方法后,Handler的sendMessage方法会返回false。在子线程中,如果手动为其创建了Looper,在所有事情完成以后应该调用quit方法来终止消息循环,否则这个子线程就会一直处于等待的状态,而如果退出Looper以后,这个线程就会立刻终止,因此建议不需要的时候终止Looper。
源码中quit和quitSafely方法的区别,二者都是调用了MessageQueue的quit方法,只是参数传递不同,传true的时候调用removeAllFutureMessagesLocked()方法,false的时候调用removeAllMessagesLocked(),具体的可以在源码中查看。
public void quit() {
mQueue.quit(false);
}
public void quitSafely() {
mQueue.quit(true);
}
loop方法是一个死循环,唯一跳出循环的方式是MessageQueue的next方法返回了null。当Looper的quit方法被调用时,Looper就会调用MessageQueue的quit或者quitSafely方法来通知消息队列退出,当消息队列被标记为退出状态时,它的next方法就会返回null。
Handler的工作原理
Handler的工作主要包括消息的发送和接收过程。消息的发送可以通过post的一系列方法以及send的一系列方法来实现,post的一系列方法最终是通过send的一系列方法来实现的。
Handler发送消息的过程仅仅是向消息队列中插入了一条消息,MessageQueue的next方法就会返回这条消息给Looper,Looper收到消息后就开始处理了,最终消息由Looper交由Handler处理,即Handler的dispatchMessage方法会被调用,这时Handler就进入了处理消息的阶段。
/**
* Handle system messages here.
*/
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}