第十章 Android消息机制
##10.1、Android消息机制概述
系统为什么不允许在子线程中去访问UI呢? 因为Android的UI控件不是线程安全的,多线程并发访问可能会导致UI控件处于不可预期的状态,为什么不加锁?因为加锁机制会让UI访问逻辑变得复杂;其次锁机制会降低UI访问的效率,因为锁机制会阻塞某些线程的执行。所以Android采用了高效的单线程模型来处理UI操作。
##10.2、ThreadLocal 的工作原理
ThreadLocal 是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储后也只有在指定的线程中获取存储的数据,其他线程无法获取该数据,所以各线程之间可以互不干扰的存储数据。
比如对于handler来说,要想获取当前线程的Looper,通过threadLocal很轻松实现Looper在线程中的存取,下面通过例子演示他的含义,
public class MainActivity extends AppCompatActivity {
private static final String TAG = "Chapter10_2_1";
private ThreadLocal<Boolean> mThreadLocal = new ThreadLocal<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mThreadLocal.set(true);
Log.i(TAG, Thread.currentThread().getName() + ",mThreadLocal--->" + mThreadLocal.get());
new Thread("Thread1"){
@Override
public void run() {
mThreadLocal.set(false);
Log.i(TAG, Thread.currentThread().getName() + ",mThreadLocal--->" + mThreadLocal.get());
}
}.start();
new Thread("Thread2"){
@Override
public void run() {
Log.i(TAG, Thread.currentThread().getName() + ",mThreadLocal--->" + mThreadLocal.get());
}
}.start();
}
}
运行后打印结果如下:
可以看到主线程中设置 mThreadLocal 的值为 true,所以主线程中打印为 ture,Thread1 设置为 false,则打印为 false,Thread2 中没有设置,则打印为 null。三个线程访问的是同一个 mThreadLocal 对象,获取的值却不一样,所以通过 ThreadLocal 可以在不同线程中可以互不干扰的维护一套数据的副本.
观察 ThreadLocal 的 set 和 get 方法:
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
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();
}
可以看到在 set 和 get 方法中首先获取当前线程,然后根据当前线程去获取一个 ThreadLocalMap 对象,该对象用于存储数据,不同线程的 ThreadLocalMap 对象是不同的,所以各线程之间的数据也就互不干扰了。
##10.3、消息队列的工作原理
消息队列指的是MessageQueue,主要包含两个操作:插入和读取。读取操作本身会伴随着删除操作。MessageQueue内部通过一个单链表的数据结构来维护消息列表,这种数据结构在插入和删除上的性能比较有优势。
插入和读取对应的方法分别是:enqueueMessage和next方法。
enqueueMessage()的源码实现主要操作就是单链表的插入操作
next()的源码实现也是从单链表中取出一个元素的操作,next()方法是一个无线循环的方法,如果消息队列中没有消息,那么next方法会一直阻塞在这里。当有新消息到来时,next()方法会返回这条消息并将其从单链表中移除。
##10.4、Looper的工作原理
Looper在Android的消息机制中扮演着消息循环的角色,具体来说就是它会不停地从MessageQueue中查看是否有新消息,如果有新消息就会立即处理,否则就一直阻塞在那里。
通过Looper.prepare()方法即可为当前线程创建一个Looper,再通过Looper.loop()开启消息循环。prepareMainLooper()方法主要给主线程也就是ActivityThread创建Looper使用的,本质也是通过prepare方法实现的。
Looper提供quit和quitSafely来退出一个Looper,区别在于quit会直接退出Looper,而quitSafely会把消息队列中已有的消息处理完毕后才安全地退出。 Looper退出后,这时候通过Handler发送的消息会失败,Handler的send方法会返回false。
在子线程中,如果手动为其创建了Looper,在所有事情做完后,应该调用Looper的quit方法来终止消息循环,否则这个子线程就会一直处于等待状态;而如果退出了Looper以后,这个线程就会立刻终止,因此建议不需要的时候终止Looper。
Looper.loop原理
loop方法是一个死循环,唯一跳出循环的方式是MessageQueue的next方法返回null,当Looper的quit方法被调用时,Looper就会调用MessageQueue的quit或者quitSafely方法来通知消息队列退出,当消息队列被标记为退出状态时,它的next方法就会返回null,也就是说looper必须退出,否则loop方法会无限循环。loop方法调用MessageQueue的next方法获取新消息,而next是一个阻塞操作,当没有消息时next会一直阻塞在那里,这也导致loop方法一直阻塞在那里。如果MessageQueue的next方法返回了新消息,Looper就会处理这条消息。msg.target.dispatchMessage(msg),这里的msg.target是发送这条消息的Handler对象,这样Handler发送的消息最终又交给它的dispatchMessage方法来处理。这样成功的将代码逻辑切换到指定到线程中执行了。
##10.5、Handler的工作原理
Handler的工作主要包含消息的发送和接收过程。通过post的一系列方法和send的一系列方法来实现。
Handler发送过程仅仅是向消息队列中插入了一条消息。MessageQueue的next方法就会返回这条消息给Looper,Looper拿到这条消息就开始处理,最终消息会交给Handler的dispatchMessage()来处理,这时Handler就进入了处理消息的阶段。
Handler的构造方法
1、派生Handler的子类
2、通过Callback
Handler handler = new Handler(callback)
其中,callback接口定义如下
public interface Callback{
public boolean handleMessage(Message msg);
}
3、通过Looper
public Handler(Looper looper){
this(looper,null,false);
}
10.6、 主线程的消息循环
Android的主线程就是ActivityThread,主线程的入口方法为main(String[] args),在main方法中系统会通过Looper.prepareMainLooper()来创建主线程的Looper以及MessageQueue,并通过Looper.loop()来开启主线程的消息循环。
ActivityThread通过ApplicationThread和AMS进行进程间通信,AMS以进程间通信的方式完成ActivityThread的请求后会回调ApplicationThread中的Binder方法,然后ApplicationThread会向H发送消息,H收到消息后会将ApplicationThread中的逻辑切换到ActivityTread中去执行,即切换到主线程中去执行。四大组件的启动过程基本上都是这个流程。
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。