Android消息机制浅析

Android消息机制可以说是Android系统的核心也不为过,所有与UI相关的操作基本都需要依赖消息机制,而Handler就是消息机制的上层开放的接口。一般我们不论是更新UI还是异步传递消息都只需要通过Handler即可。

使用消息机制的原因

  • android4.0以后不允许主线程进行网络请求

    现在几乎每个app都需要访问网络,而在android4.0以前,访问网络操作是可以在UI线程中进行的,但在4.0以后,Android默认禁止在UI线程中进行网络请求和相关操作(会报NetworkOnMainThreadException的异常,但其实可以强制在主线程里进行)。另外我们知道,Android的UI线程的等待时间,也就是Activity的等待时间为5秒,Service(服务也是运行于UI线程下的)的为20秒,所以比较耗时的操作也不能放在主线程里运行。

  • 子线程不能操作主线程UI

    子线程完成了耗时操作,按照业务逻辑一般需要在UI中做出反馈,但是却不能直接修改主线程UI,因为android规定只有创建了View的线程才能操作这个view。所以反过来主线程也不能操作子线程创建的View。

  • 多线程消息传递

    子线程执行完毕,需要返回执行结果或取得的数据,就得借助于消息机制来传递。

而以上问题,借助消息机制,通过Handler就全部都能搞定。

消息机制的框架

android中的消息机制由Handler,MessageQueue,Looper,Message组成,其架构如图:

Handler: 线程之间通信的桥梁,通过Handler可以发送消息(sendMessage)和处理消息(handleMessage);
Message: 被发送的消息,可以存放一些关键性的指信息,如文本,对象等,另外可以指定消息的标志,表明该消息应该如何处理;
MessageQueue: 存放消息的队列,所有由子线程发送给主线程的消息都暂存在这里,等待Looper来取;
Looper: 通过loop()无限循环不断从MessageQueue取消息并将消息提交至Handler;每个线程只能存在一个Looper的实例,但一个Looper实例可以被多个线程引用;

消息的处理过程(代码)

  • 在主线程中创建一个Handler的实例并重写handleMessage方法:
1
2
3
4
5
6
7
8
private Handler handler=new Handler(){
@Override
public void handleMessage(Message msg) {
switch (msg.what){
// TODO: 操作UI
}
}
};
  • 在子线程向主线程发送信息
1
2
3
4
5
6
7
8
9
new Thread(){
@Override
public void run() {
...
Message msg=new Message();
msg.what=LOAD_IMAGE;
handler.sendMessage(msg);
}
}.start();

其实需要我们写的代码就这么多,系统在后台自动帮们实时获取到队列中的Message并提交到了Handler,但是我们还是需要了解其背后的一些原理。

为什么主线程可以更新UI

事实上所有的线程本身都不具有消息循环能力,而是通过Looper.prepare()方法可以将一个线程转换为Looper线程,同时也就具备了消息循环能力。
当主线程被创建的时候,系统会自动调用Looper.prepare()将当前线程转换为Looper线程,然后在创建Handler的时候系统会自动调用Looper.loop()进行轮询。
所以其实我们在子线程中也是可以更新UI的,只要将操作UI对象的代码写在Looper.prepare()和Looper.loop()之间,当然大前提还是操作的UI对象必须得是子线程自己创建的。
因此即使子线程可以操作自己的UI对象,比如弹出一个Toast(使用getApplicationContext),但是子线程仍然不能直接操作主线程的UI。
同理主线程也不能操作子线程创建的UI对象,想要操作也必须引用子线程中的Handler发送消息来更新。

消息的处理过程(原理)

通过上面的解释,接着来深入分析消息机制的实现流程:

  • 当我们创建Handler的实例:

    • 与该线程唯一的Looper进行绑定
    • 与Looper下的MessageQueue进行绑定
  • 当我们发送一个消息的时候

    • 调用过程:sendMessage() -> sendMessageDelayed() -> sendMessageAtTime() -> enqueueMessage()
    • 在sendMessageAtTime()方法中会将Message与MessageQueue进行关联,然后在enqueueMessage()中实际将消息加入消息队列;
    • 在enqueueMessage()方法中,会将Message与发送消息的Handler通过target属性进行绑定,确保最后调用正确的Handler来处理消息,也就是确保消息处理的唯一性;
  • MessageQueue建立完成

    • Looper.loop() 以无限循环遍历Looper内部的MessageQueue,通过msg.target.dispatchMessage(msg)调用Handler的事件分发实现消息回调处理。
    • handleMessage()方法被调用

看似神奇的post()方法

或许你见过有的人直接调用对象的post()或者postXXX()方法可以直接在子线程中进行UI操作,但是如果深入去看这些方法的源码,就会发现其实他们也是依赖于消息机制,例如post()的流程:
post() -> attachInfo.mHandler.post(action) -> sendMessageDelayed() -> sendMessageAtTime() -> enqueueMessage()

所以post()类型的方法底层也是使用消息机制进行UI操作,只是Android官方帮我们做了封装而已。

强制在主线程请求网络

虽然Android官方禁止在4.0以后的版本中在主线程中进行网络请求,但是仍然可以通过特殊的方法强制在主线程中进行:
在UI线程的onCreate()方法中setContentView()之后添加如同下代码:

1
2
3
4
if (android.os.Build.VERSION.SDK_INT > 9) {
StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
StrictMode.setThreadPolicy(policy);
}

这几行代码的作用是修改当前线程的限制策略,表示允许所有行为,即所有不规范的的操作都是被允许的。

这几行代码其实是相当危险的,这以后的所有的不规范操作都不会被编译器识别并提醒,或许你当时没有发现什么错误,但是以后会有很多的坑等着你往里跳呢。所以最好不要使用这种方法,按照Android官方的规范,把所有与网络请求的操作放入子线程运行。