第十章 Android的消息机制
Handler的说明如下:有时候需要子线程去进行耗时I/O操作,譬如读取文件、访问网络。耗时操作完成后需要在UI上做一些改变,但我们并不能在子线程中更新UI,通过Handler可以更新UI操作并切换至主线程进行。
Android中的消息机制主要是指Hadnler的运行机制,其运行需要MessageQueue(消息队列,单链表数据结构形式存储的消息列表)和Looper(以无限循环的方式去查找有没有新消息,如有处理)的支撑。Looper中有一个TheadLocal,其作用是在每个线程中存储数据,ThreadLocal可以在不同线程中互不干扰的存储并提供数据,轻易获取每个线程的Looper。UI线程ActivityThread被创建时就会初始化Looper。
本章主要介绍:Android的消息机制概述、Android的消息机制分析(ThreadLocal、消息队列、Looper、Handler的工作原理)、主线程的消息循环。
(一)Android的消息机制概述
Handler的主要作用是将一个任务切换至某个指定的线程中去执行。这是因为UI只能在主线程访问。ViewRootImp的checkThread方法对UI操作进行了验证。Handler可以解决子线程中无法访问UI的矛盾。
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
系统为什么不允许在子线程中更新UI?Android中的UI不是线程安全的,多进程中并发访问会导致UI控件处于不可预期的状态。加上锁机制,缺点有两个:1.加上锁机制会让UI访问的逻辑变得复杂;2.锁机制会降低UI访问的效率,会阻塞某些线程的执行。Handler切换UI访问的执行线程即可,单线程处理。
Handler创建完毕后,内部Looper和MessageQueue可以和Handler一起协同工作了。步骤一:Handler的post方法将一个Runnable投递到Handler的内部Looper中处理,当然也可以通过封装Message对象并通过send方法发送。步骤二:send方法后,其会调用MessageQueue的enqueueMessage方法将这个消息放在消息队列中,Looper一旦发现有新消息到来,则会处理。一般会使用Handler的handlemessage进行处理。注意:Looper是运行创建Handler所在得线程的。这样一来Handler的业务逻辑被切换至创建Handler所在的线程去执行了。
(二)Android的消息机制分析
Android的消息机制实际上是Handler的运行机制。本节主要围绕Handler的工作过程来分析Android的消息机制,主要包括Handler、MessageQueue和Looper,为了更好地理解Looper,我们介绍了ThreadLocal。
2.1.ThreadLocal的工作原理
2.1.1.ThreadLocal机制
ThreadLocal是一个线程内部的数据存储类,通过它可以在执行的线程中存储数据,数据存储以后,只有在指定线程中可以获取到存储的数据,对于其他线程来说则无法获取到数据。eg:Lopper,ActivityThread和AMS。一般来说,当某些数据是以线程为作用域并且不同的线程具有不同的数据副本时可以考虑使用ThreadLocal, 譬如Looper。Looper是以线程为作用域并且不同线程具有不同的Lopper,可以通过ThreadLocal轻松实现Looper在线程中的存取。
ThreadLocal另一场景是复杂逻辑下的对象传递,譬如监听器的传递。采用ThreadLocal可以让监听器作为线程内的全局对象而存在,线程内部只要通过get方法可以获得监听器。如果不采用ThreadLocal,有如下两个办法:将监听器作为参数的形式在函数调用栈中进行传递(当函数调用栈深的时候,通过函数参数来传递监听器对象这几乎是不可接受的);2.将监听器作为静态变量供线程访问,上述的两种办法都是有局限性的(不具有可扩展性,比如同时两个线程在执行,那么需要提供两个静态的监听器对象,如果10个就需要10个监听器)。而采取ThreadLocal,每个监听器对象都有自己的线程内部存储。
2.1.2.ThreadLocal代码示例
定义一个Boolean类型的ThreadLocal对象。分别在主线程、子线程1、子线程2中设置和访问它的值。
虽然在不同线程中访问的是同一个ThreadLocal对象,但是它们通过ThreadLocal获取到的值却是不一样的(emmm...奇妙)
private ThreadLocal<Boolean> mbooleanThreadLocal = new ThreadLocal<>();
mbooleanThreadLocal.set(true);
Log.d(TAG, "[Thread:mbooleanThreadLocal=]"+mbooleanThreadLocal.get());
new Thread(new Runnable() {
@Override
public void run() {
mbooleanThreadLocal.set(false);
Log.d(TAG, "[Thread1:mbooleanThreadLocal=]"+mbooleanThreadLocal.get());
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
Log.d(TAG, "[Thread2:mbooleanThreadLocal=]"+mbooleanThreadLocal.get());
}
}).start();
2.1.3.源码分析
首先是ThreadLocal的set方法。values方法来获取当前线程的ThreadLocal数据;如果values为空,则对其进行初始化,初始化后将ThreadLocal的值进行存储到table数组中去。ThreadLocal的值在table数组中的存储位置总是为ThreadLocal的reference字段所标识的对象的下一个位置,比如ThreadLocal的reference对象在table数组中的索引为index,那么ThreadLocal的值在table数组中的索引就是index + 1 ,
public void set(T value) {
Thread currentThread = Thread.currentThread();
Values values = values(currentThread);
if (values == null) {
values = initializeValues(currentThread);
}
values.put(this, value);
}
再者是ThreadLocal的get方法。取出当前线程的localvalues对象,若localvalues不为空,取出它的table数组并找出ThreadLocal的rederence对象在table数组中的位置,然后table数组的下一个位置所存储的数据就是ThreadLocal的值。
public T get() {
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操作的对象都是当前线程的localValues对象的table数组,因此在不同线程中可访问同一个ThreadLocal的set和get方法,他们对ThreadLocal所做的读写操作仅限于各自内部,这就是ThreadLocal可以在多个线程互不干扰的存储和修改数据的原因。
2.2.消息队列的工作原理
消息队列是指MessageQueue,其主要包含两个操作:插入和读取(本身伴随删除)。插入和读取对应的方法分别为enqueueMessage和next。前者作用是往消息队列中插入一条消息;后者的作用是从消息队列中取出一条消息并将其从消息队列中移除。内部实现不是队列,而是使用单链表的数据结构维护消息列表。
enqueueMessage主要完成单链表的插入操作。next方法是一个无限循环的方法,如果消息队列中没有消息,那么next方法会一直阻塞在这里,当有新消息到来时,next方法会返回这条消息并将其从单链表中移除。
2.3.Looper的工作原理
Looper在Android的消息机制中扮演着消息循环的角色,具体来说就是它会不停的从MessageQueue中查看是否有新消息,如果有新消息就会立即处理,否则就会一直阻塞在那里。
步骤一:构造方法Looper里它会创建一个MessageQueue,然后将当前线程的对象保存起来:
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
步骤二:为一个线程创建Looper:通过Looper.prepare()就可以为当前线程创建Looper了,然后通过Looper.loop来开启循环。
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();//为当前线程创建一个Looper
Handler handler01 = new Handler();
Log.d(TAG, "run: "+getMainLooper());//可以在任何地方获取到主线程的Looper
Looper.loop();//开启消息循环
}
}).start();
注意:Looper的loop方法是一个死循环,唯一跳出的方法是MessageQueue的next方法返回null。quit和quitSafely会将其next方法返回置为null,如果MessageQueue的next返回了新消息,Looper就会处理这条消息:msg.target.dispatchMessage(msg),这里的msg.target是发送这条消息的Handler对象,这样Handler发送的消息最后又交给dispatchMessage来处理,dispatchMessage方法是在创建Handler时所使用的Lopper中执行的,可成功将代码逻辑切换到指定的线程中去执行。
步骤三:Looper提供了quit和quitSafely来退出一个Looper,两者的区别:quit会直接退出,但是quitSafely是退出一个Looper,然后把消息队列中已有消息处理完毕后才安全退出,建议不需要的时候终止Looper。
2.3.Handler的工作原理
Handler的工作主要包含消息的发送和接收,消息的发送是通过post(post本质也是send)和send方法来实现的。
步骤一:Handler发消息仅仅是向消息队列里插入一条消息,MessageQueue的next方法就会返回这条消息给Looper,Looper交由Handler处理,即Handler的dispatchMessage方法就会被调用。
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处理过程如下:1.检查Message的callback是否为null,不为null就通过handlerCallback来处理消息,callback是一个Runnable对象,实际上就是Handler的post方法所传递的Runnable参数;2.检查mCallback是否为null,不为null就调用mCallback.handleMessage方法来处理消息;Handler handler = new Handler(callback);mCallback是创建一个Handler的实例但并不需要派生Handler的子类;一般是派生一个子类并重写其handleMessage方法。3.最后通过调用Handler的handleMessage方法来处理消息。
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
private static void handleCallback(Message message) {
message.callback.run();
}
(三)主线程的消息循环
Android的主线程就是ActivityThread,主线程的入口为main。
步骤一:在main方法中系统通过Looper.prepareMainLooper来创建主线程的Looper和MessageQueue,并且通过Looper.loop()来开启主线程的消息循环。
public static void main(String[] args) {
....
Process.setArgV0("<pre-initialized>");
//创建主线程的Looper以及MessageQueue;
Looper.prepareMainLooper();
//主线程
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
AsyncTask.init();
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
//使用loop开启循环
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
}
步骤二:主线程开始循环之后,ActivityThread还需要一个Handler来和消息队列交互,也就是H,其内部定义了一组消息类型,主要包含四大组件的启动和停止等过程。
步骤三:ActivityThread通过ApplicationThread和AMS进行进程间通信,AMS以进程间通信的方法完成ActivityThread的请求后回调ApplicationThread的Binder方法,然后通过H发送消息,H收到消息后将ApplicationThread中的逻辑切换到ActivityThread去执行,即切换到主线程去执行,这个过程就是主线程的消息循环模型。