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 | private Handler handler=new Handler(){ |
- 在子线程向主线程发送信息
1 | new Thread(){ |
其实需要我们写的代码就这么多,系统在后台自动帮们实时获取到队列中的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 | if (android.os.Build.VERSION.SDK_INT > 9) { |
这几行代码的作用是修改当前线程的限制策略,表示允许所有行为,即所有不规范的的操作都是被允许的。
这几行代码其实是相当危险的,这以后的所有的不规范操作都不会被编译器识别并提醒,或许你当时没有发现什么错误,但是以后会有很多的坑等着你往里跳呢。所以最好不要使用这种方法,按照Android官方的规范,把所有与网络请求的操作放入子线程运行。