android线程消息传递机制——Looper,Handler,Message
在引入这些概念之前,我们先了解一下引入这些机制的背景。
出于性能优化的考虑,Android的UI操作并不是线程安全的(如果你不懂什么是线程安全,可以阅读一下<一起探究多进程与多线程>里的数据安全与可重入),这意味着如果有多个线程同时操作某个UI组件,可能导致线程安全问题。为了解决这个问题,Android制定了一条简单的规则:只允许UI线程修改Activity里的UI组件。这个UI线程也通常被我们称为主线程。
在此引入一个问题:如果你的Android程序想在网络上下载一张图片(我们暂时不考虑用Android提供的组件去实现),并且下载完后显示出来,那怎么办?在Android3.0之后,就不允许在主线程中执行网络请求了。好吧,我开一个子线程去下载它!但是下载完之后,子线程又不能操作ImageView显示图片,那就需要在下载完毕之后,子线程通知主线程去更新ImageView。这就涉及了线程间的通信机制了。而Android的线程间的通信就用到Android.os.Handler类了。
其实线程间的通信是需要几个类一起配合使用才行,这几个类分别是:Looper,Handler,Message。其实还有一个Message Queue(MQ)的,只不过封装在Looper里面了,我们不会直接跟MQ打交道。
Looper——负责循环从MQ中取出Message,然后消息扔给Hanlder的handleMessage方法,。
Handler——负责处理Looper分发过来的Message和发送消息给Looper。在实例化时要重写hanldeMessage方法,处理对应的消息。
Message——一个消息包而已,里面有一个target字段,表明这个Message是发给线程的哪个Handler的。
Message Queue——消息包组成的消息队列。
下面说一下他们是如何配合运作的。当你的A线程需要接受从别的B线程发过来的消息时,就需要在A线程中创建一个Handler,B线程就可以往你创建的Handler发消息。然后A线程就可以收到消息了。但是需要注意的是,在创建Hanlder的时候,需要绑定一个Looper。如果在创建Handler时不指定与其绑定的Looper对象,系统默认会将当前线程的Looper绑定到该Handler上。如果你的当前线程没有创建Looper,系统就会报错。那有同学就说了,我平时在UI线程里用Handler时没有创建Looper也没有报错啊!那是因为系统默认已经帮UI线程创建Looper了。如果你创建的子线程也想拥有一个handler,那也需要帮你的子线程先创建一个Looper才行哦。下面用一段代码看看线程、Looper、handler、Message是怎么个回事吧。
class MyThread extends Thread{
public Handler mHandler;
public void run()
{
Looper.prepare();
mHandler = new Handler()
{
@Override
public void handleMessage(Message msg)
{
//在这里处理消息
}
}
Looper.loop();
}}
需要注意的是,一个Thread只能有一个Looper对象。如果你硬是要为一个Thread创建两个Looper,那。。。那。。。那其实你办不到!因为Looper对象定义为ThreadLocal(如果你不理解ThreadLocal,可以上网查)。保证一个Thread最多只能有一个Looper。其实Looper是不能用new去实例化的,因为Looper的构造函数是private的,调用Loop.prepare()就创建Looper了,无论你调用多少次Looper.prepare,都是一个Looper对象,有点像单例模式。而每个Thread可以拥有多个Handler。创建Hanlder时,如果没指定Looper,那么在Handler的构造函数里会关联当前线程的Looper。那他们是怎么关联的?上面代码没有见他们有关联的行为啊。其实玄机就在Handler的构造函数里。public class handler {
final MessageQueue mQueue; // 关联的MQ
final Looper mLooper; // 关联的looper
final Callback mCallback;
// 其他属性
public Handler() {
// 这里有一堆代码直接略过,,,
// 用mLooper获取当前线程的looper
mLooper = Looper.myLooper();
// looper不能为空,如果为空,就是该线程没有Looper,直接报错!即该默认的构造方法只能在looper线程中使用
if (mLooper == null) {
throw new RuntimeException(
"Can‘t create handler inside thread that has not called Looper.prepare()");
}
// 重要!!!直接把关联looper的MQ作为自己的MQ,因此它的消息将发送到关联looper的MQ上
mQueue = mLooper.mQueue;
mCallback = null;
}
// 其他方法}
其实Looper里面的Loop()方法是个死循环,不断的从MQ里取消息,然后分发给对应的handler处理的。
到了这里,大家应该对这个线程的消息传递机制有一定的理解了吧?如果不理解也没关系,进代码零件官方群里讨论吧。
看到这里,感觉线程与Handler已经很完美啦,我要下载一个东西,就先创建一个线程,下载完了就通知UI更新,一个都很美好。其实不然,假如你要下载100张图片,你不可能开100多个线程去下载吧。这样的话系统资源估计撑不住,特别是低端的手机。那怎么办。正确的办法是使用一个线程池,但是自己维护一个线程池十分的麻烦。然后Android早已经帮我们想好这一点了,我们直接使用AsyncTask类即可!其实AsyncTask内部已经维护了一个线程池,有兴趣的同学可以阅读源码。这里就不做更多的讨论了。
最后再来梳理下多线程处理的步骤;
如果你对该内容感兴趣,可加入Q群187253654讨论,关注【代码零件】网站或微信公众号