前言:
对于熟悉Android消息机制的小伙伴,可以跳到最后,看主线程的消息循环。
Android的消息机制主要是指Handler的运行机制,Handler的运行需要底层MessageQueue和Looper的支撑。MessageQueue是采用单链表的数据结构来存储消息列表。 Looper会以无限循环的形式去查看是否有新消息,如果有就处理消息,否则就一直等待。ThreadLocal可以在不同线程中互不干扰的存储并提供数据,通过ThreadLocal可以轻松的获取每个线程的Looper。
10.1 Android消息机制概述
- Handler的主要作用是将某个任务切换到Handler所在的线程中去执行。为什么Android要提供这个功能呢?这是因为Android规定访问UI只能通过主线程,如果子线程访问UI,程序会抛出异常;ViewRootImpl在checkThread方法中做了判断。
- 由于Android不建议在主线程进行耗时操作,否则可能会导致ANR。那我们耗时操作在子线程执行完毕后,我们需要将一些更新UI的操作切换到主线程当中去。所以系统就提供了Handler。
- 系统为什么不允许在子线程中去访问UI呢?
因为Android的UI控件不是线程安全的,多线程并发访问可能会导致UI控件处于不可预期的状态,为什么不加锁?因为加锁机制会让UI访问逻辑变得复杂;其次锁机制会降低UI访问的效率,因为锁机制会阻塞某些线程的执行。所以Android采用了高效的单线程模型来处理UI操作。 - Handler创建时会采用当前线程的Looper来构建内部的消息循环系统,如果当前线程没有Looper就会报错。Handler可以通过post方法发送一个Runnable到消息队列中,也可以通过send方法发送一个消息到消息队列中,其实post方法最终也是通过send方法来完成。
- MessageQueue的enqueueMessage方法最终将这个消息放到消息队列中,当Looper发现有新消息到来时,处理这个消息,最终消息中的Runnable或者Handler的handleMessage方法就会被调用,注意Looper是运行Handler所在的线程中的,这样一来业务逻辑就切换到了Handler所在的线程中去执行了。
10.2 Android的消息机制分析
10.2.1 ThreadLocal的工作原理
- ThreadLocal是一个线程内部的数据存储类,通过它可以在指定线程存储数据,数据存储后,只能在指定的线程可以获取到存储的数据,对于其他线程则无法获取到数据。一般来说,当数据是以线程作为作用域并且不同线程有不同副本的时候,就可以考虑使用ThreadLocal。对于Handler来说,它需要获取当前线程的Looper,而Looper的作用于就是线程并且不同的线程具有不同的Looper,通过ThreadLocal可以轻松实现线程中的存取。
- ThreadLocal的另一个使用场景是复杂逻辑下的对象传递。
- ThreadLocal原理:不同线程访问同一个ThreadLoacl的get方法,ThreadLocal的get方法会从各自的线程中取出一个数组,然后再从数组中根据当前ThreadLocal的索引去查找对应的Value值。
(1) ThreadLocal set方法
//PS:JDK中与Android SDK的ThreadLocal有些许区别,我们以SDK中的为例
public void set(T value) {
Thread currentThread = Thread.currentThread();
Values values = values(currentThread);
if (values == null) {
values = initializeValues(currentThread);
}
values.put(this, value);
}
获取到当前线程,并从当前线程中取出对应的ThreadLocalMap(内部由Entry[] table来存储ThreadLoacl的值),再把相应数据存入其中。如果ThreadLocalMap为空,则创建该线程的ThreadLocalMap对象,再将ThreadLocal的值进行存储。
(2) ThreadLocal get方法
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);
}
获取当前线程,并从当前线程中获取对应的ThreadLocalMap,通过key获取对应的value;如果未获取到对应的ThreadLocalMap,则创建并将该对象返回。
(3)ThreadLocal的值在table数值中的位置总是ThreadLocal的索引+1。
10.2.2 消息队列的工作原理
- MessageQueue主要有两个操作,插入和读取,读取操作伴随着删除操作;MessageQueue是通过单链表的数据结构来维护消息列表的。
- enqueueMessage方法的作用是往消息队列插入一条消息。next方法是一个无线循环的方法,如果消息队列中没有消息,那么next方法会一直阻塞在这里。当有新消息到来时,next方法会返回这条消息并将其从单链表中移除。
10.2.3 Looper的工作原理
- prepareMainLooper方法主要给主线程也就是ActivityThread创建Looper使用的,本质也是通过prepare方法实现的。
- Looper提供quit和quitSafely来退出一个Looper,区别在于quit会直接退出Looper,而quitSafely会把消息队列中已有的消息处理完毕后才安全地退出。
Looper退出后,这时候通过Handler发送的消息会失败,Handler的send方法会返回false。
在子线程中,如果手动为其创建了Looper,在所有事情做完后,应该调用Looper的quit方法来终止消息循环,否则这个子线程就会一直处于等待状态;而如果退出了Looper以后,这个线程就会立刻终止,因此建议不需要的时候终止Looper。 - loop方法会调用MessageQueue的next方法来获取新消息,而next是是一个阻塞操作,但没有信息时,next方法会一直阻塞在那里,这也导致loop方法一直阻塞在那里。如果MessageQueue的next方法返回了新消息,Looper就会处理这条消息:mas.target.dispatchMessage(msg),这里的msg.target是发送这条消息的Handler对象,这样Handler发送的消息最终又交给Handler来处理了。
10.2.4 Handler的工作原理
- Handler的工作主要包含消息的发送和接受过程。发送过程通过post的一系列方法和send的一系列方法来实现。Handler发送过程仅仅是向消息队列中插入了一条消息。MessageQueue的next方法就会返回这条消息给Looper,Looper拿到这条消息就开始处理,最终消息会交给Handler来处理。
- Handler处理消息
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
//Message的callback是一个Runnable,
//也就是Handler的 post方法所传递的Runnable参数
handleCallback(msg);
} else {
//如果给Handler设置了Callback的实现,
//则调用Callback的handleMessage(msg)
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
//调用Handler的handleMessage方法来处理消息,
//该Handler子类需重写handlerMessage(msg)方法
handleMessage(msg);
}
}
private static void handleCallback(Message message) {
message.callback.run();
}
public interface Callback {
public boolean handleMessage(Message msg);
}
//默认空实现
public void handleMessage(Message msg) {
}
- Handler还有一个特殊的构造方法,可以指定一个特殊的Looper来构造Handler。
public Handler(Looper looper) {
this(looper, null, false);
}
- Handler创建需要Looper,否则会抛出异常,默认获取当前线程的Looper。主线程也就是ActivityThread会自动创建Looper,其他线程如果需要Looper均需要手动创建。
10.3 主线程消息循环
- Android的主线程就是ActivityThread,主线程的入口方法为main,在main方法中系统会通过Looper.prepareMainLooper()来创建主线程的Looper以及MessageQueue,并通过Looper.loop()来开启主线程的消息循环。
public static void main(String[] args) {
...
Process.setArgV0("");
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"));
}
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
- Looper.loop(),这里是一个死循环,如果主线程的Looper终止,则应用程序会抛出异常。那么问题来了,既然主线程卡在这里了,(1)那Activity为什么还能启动;(2)点击一个按钮仍然可以响应?
问题1:startActivity的时候,会向AMS(ActivityManagerService)发一个跨进程请求(AMS运行在系统进程中),之后AMS启动对应的Activity;AMS也需要调用App中Activity的生命周期方法(不同进程不可直接调用),AMS会发送跨进程请求,然后由App的ActivityThread中的ApplicationThread会来处理,ApplicationThread会通过主线程线程的Handler将执行逻辑切换到主线程。重点来了,主线程的Handler把消息添加到了MessageQueue,Looper.loop会拿到该消息,并在主线程中执行。这就解释了为什么主线程的Looper是个死循环,而Activity还能启动,因为四大组件的生命周期都是以消息的形式通过UI线程的Handler发送,由UI线程的Looper执行的。
问题2:和问题1原理一样,最终都是由系统发消息来处理的,都经过了Looper.loop()。
问题2详细分析请看原书作者的Android中MotionEvent的来源和ViewRootImpl
关于我
:HuDP
WeChat:mox1103
WeiBo:HuDP_
欢迎各位小伙伴留言交流[开心脸]