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讨论,关注【代码零件】网站或微信公众号


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值