Android的消息机制主要是指Handler的运行机制,及Handler所附带MassageQueue和Looper的工作过程。Handler的主要任务是将一个任务切换到指定的线程中去执行。且这种功能大部分是用于从子线程切到Android UI线程中去更新UI。
这里延伸一点,Android系统为什么不允许在子线程中访问UI呢?这是因为Android的UI控件是线程不安全的,如果多线程并发访问会导致UI控件处于不可预期的状态。系统不对IU控件的访问添加锁机制,是因为加上锁机制会让UI访问的逻辑变得复杂,并且锁机制会降低UI访问的效率,因为锁机制会阻塞某些线程的执行。最简单且高效的方法就是采用单线程模型来处理UI操作。
handler的工作原理
Handler创建时会采用当前现成的Looper来构建内部的消息循环系统,如果当前线程没有Looper,就会报如下错误:
new Thread(){
@Override
public void run() {
super.run();
Log.e("cyy","handler run");
Handler handler1 = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
};
}
}.start();
报错的原因:就是在我们new的这个Thread中,没有创建Looper,就直接创建Handler了。
解决办法:在当前线程创建Looper即可(稍后给出实例代码)。
这里我们插播说下ThreadLocal,这个类与Handler相关。
ThreadLocal介绍
定义:ThreadLocal是一个线程内部的数据存储类,通过它可以在指定线程中存储数据,数据存储以后,只有在指定的线程中获取到存储的数据,其他线程是无法获取到这个数据的。
举个例子:创建3个线程,并在各个线程中各向同一个ThreadLocal中存储一个数据,然后再取出。
threadLocal = new ThreadLocal<>();
new Thread("ThreadAAA"){
@Override
public void run() {
super.run();
threadLocal.set("AAA");
Log.e("cyy","ThreadAAA:"+threadLocal.get());
}
}.start();
new Thread("ThreadBBB"){
@Override
public void run() {
super.run();
threadLocal.set("BBB");
Log.e("cyy","ThreadBBB:"+threadLocal.get());
}
}.start();
new Thread("ThreadCCC"){
@Override
public void run() {
super.run();
threadLocal.set("CCC");
Log.e("cyy","ThreadCCC:"+threadLocal.get());
}
}.start();
运行结果如下
在不同线程中获取的数据就是 当前线程设置的数据,互不干扰。
回到我们Handler的消息队列的讲解中
Handler的消息机制是由Handler、Looper、MessageQueue组成的。
MessageQueue主要包含两个操作,插入和读取
插入是enqueueMessage;读取是next (读取并移除)
尽管MessageQueue叫消息队列,但是它的内部实现并不是队列,而是通过单链表的结构来维护消息队列。插入和读取消息就是通过操作链表来实现的。
Looper介绍
我们先来看下Looper的构造方法
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
从Looper的构造方法中可知,Looper在构造的时候会去创建MessageQueue。
我们从本篇最开始的例子可以看到,在没有Looper的Thread中使用Handler会报此线程没有Looper的错,我们现在就来创建并开启Looper。
开启Looper通过Looper的prepare方法
//Looper的prepare源码
public static void prepare() {
prepare(true);
}
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));// new Looper 并添加到ThreadLocal中
}
这里用到了ThreadLocal,将创建的Looper对象放到ThreadLocal中。
然后通过Looper.loop来开启循环。loop方法比较长,但是其主要的思路就是从MessageQueue的消息链表中拿出一个msg,并交与Handler处理。
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
修改后的Handler使用如下:
new Thread(){
@Override
public void run() {
super.run();
Looper.prepare();//创建Looper
Handler handler1 = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
};
Looper.loop();//开启Looper循环
}
}.start();
在次运行,就不会报错啦。
Looper运行起来了,那怎么退出Looper循环呢?
Looper提供了quit和quitSafely来退出。二者区别如下:
quit:直接退出Looper
quitSafely:设置一个退出标志,然后把消息队列中已有的消息处理完毕后才安全退出。
在子线程中,如果手动创建了Looper,那么在所有的事情完成以后应该调用quit方法来终止消息循环。否则这个子线程就会一直处于等待状态,而如果退出Looper以后,这个线程就会立刻终止。因此建议不需要的时候终止Looper。
我们再来改进下上面带Handler的子Thread。
给Handle加上两个消息处理类型,然后在通过两个按钮的click事件去发送消息。
new Thread("zi Thread"){
@Override
public void run() {
super.run();
Looper.prepare();
handler1 = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
int what = msg.what;
if(what == 1){
Log.e("cyy","what = 1:"+Thread.currentThread().getName());
}else if( what == 2){
Log.e("cyy","what == 2 停止线程了");
Looper.myLooper().quit();
}
}
};
Looper.loop();
}
}.start();
我们多次点击按钮 “发送Message 1”,可以看到what = 1的log正常打出,然后在点击“发送Message 2”,what == 2的log正常打印了,此时再去点击 “发送Message 1”,发现没有任何log打出了。这个子线程已经正常退出了。
到这里Handler的机制就讲完了,最后在啰嗦一句,不知道大家看完之后有没有疑问呢?Handler是我们在开发中经常使用的类,在不知道上述知识之前,我们都在正常使用Handler,并没有操作Looper相关的内容呢?
那是因为你是在主线程中使用的handler,主线程会在启动的时候自动创建并开启Looper循环,因此在主线程中使用Handler,就不需要关注Looper是否创建并开启循环了。