Android消息传递机制总结(一)
安卓业务层的核心灵魂就是层层的消息传递,今天就来总结一下andorid的应用层的各种消息传递。
1.线程间通讯 ——— Handler,HandlerThread等。
2.组件间通信 ——— BroadcastReceiver,接口回调等。
3. 第三方通信 ——— EventBus,rxBus
4.进程间通信 ——— Content Provider ,Broadcast ,AIDL等。
5.长连接推送 ——— WebSocket,XMPP等。
1.线程间通信
Android通过Handler消息机制来实现线程之间的通讯。
Handler机制主要角色
1 | Message:消息,其中包含了消息ID,消息处理对象以及处理的数据等,由MessageQueue统一列队,终由Handler处理。 |
Handler机制主要运用
1 | sendEmptyMessage(int);//发送一个空的消息 |
主进程定义Handler
1 | private Handler mHandler = new Handler() { |
子线程执行耗时操作然后发消息,通知Handler完成UI更新
1 | private void getDataFromNet() { |
Handler机制扩展:
Activity.runOnUiThread(Runnable)
View.post(Runnable)
以上也可以从子线程切换到主线程。
1 | runOnUiThread(new Runnable() { |
HandlerThread:
HandlerThread本质上就是一个普通Thread,只不过内部建立了Looper.
HandlerThread用法实例
1 | //创建一个线程,线程名字:handler-thread |
Looper的quit方法或quitSafely方法
相同点:
将不在接受新的事件加入消息队列。
不同点
当我们调用Looper的quit方法时,实际上执行了MessageQueue中的removeAllMessagesLocked方法,该方法的作用是把MessageQueue消息池中所有的消息全部清空,无论是延迟消息(延迟消息是指通过sendMessageDelayed或通过postDelayed等方法发送的需要延迟执行的消息)还是非延迟消息。
当我们调用Looper的quitSafely方法时,实际上执行了MessageQueue中的removeAllFutureMessagesLocked方法,通过名字就可以看出,该方法只会清空MessageQueue消息池中所有的延迟消息,并将消息池中所有的非延迟消息派发出去让Handler去处理,quitSafely相比于quit方法安全之处在于清空消息之前会派发所有的非延迟消息。
无论是调用了quit方法还是quitSafely方法只会,Looper就不再接收新的消息。即在调用了Looper的quit或quitSafely方法之后,消息循环就终结了,这时候再通过Handler调用sendMessage或post等方法发送消息时均返回false,表示消息没有成功放入消息队列MessageQueue中,因为消息队列已经退出了。
需要注意的是Looper的quit方法从API Level 1就存在了,但是Looper的quitSafely方法从API Level 18才添加进来。
我在直播推流中应用了HandlerThread,每编码组装出来一帧视频就发送一个handler消息,然后HandlerThread线程接收消息数据并用libRtmp推倒服务器。此时可以监控队列中有多少消息在循环,可以监听进出队列的比例,如果超出一定的范围说明网络不好,需要执行丢帧策略。
Handler 面试八问
为什么主线程不会因为
Looper.loop()
里的循环卡死?主线程确实是通过
Looper.loop()
进入了循环状态,因为这样主线程才不会像我们一般创建的线程一样,当可执行代码执行完后,线程生命周期就终止了。
在主线程的MessageQueue
没有消息时,便阻塞在MeqsageQueue.next()
中的nativePollOnce()
方法里,此时主线程会释放CPU
资源进入休眠状态,直到新消息达到。所以主线程大多数时候都是处于休眠状态,并不会消耗大量CPU
资源。
这里采用的linux
的epoll
机制,是一种IO
多路复用机制,可以同时监控多个文件描述符,当某个文件描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作拿到最新的消息,进而唤醒等待的线程。post
和sendMessage
两类发送消息的方法有什么区别?post
一类的方法发送的是Runnable
对象,但是其最后还是会被封装成Message
对象,将Runnable
对象赋值给Message
对象中的callback
变量,然后交由sendMessageAtTime()
方法发送出去。在处理消息时,会在dispatchMessage()
方法里首先被handleCallback(msg)
方法执行,实际上就是执行Message
对象里面的Runnable
对象的run 方法。
而sendMessage
一类的方法发送的直接是Message
对象,处理消息时,在dispatchMessage
里优先级会低于handleCallback(msg)
方法,是通过自己重写的handleMessage(msg)
方法执行。为什么要通过
Message.obtain()
方法获取 Message 对象?obtain
方法可以从全局消息池中得到一个空的Message
对象,这样可以有效节省系统资源。同时,通过各种obtain
重载方法还可以得到一些Message
的拷贝,或对Message
对象进行一些初始化。Handler实现发送延迟消息的原理是什么?
我们常用
postDelayed()
与sendMessageDelayed()
来发送延迟消息,其实最终都是将延迟时间转为确定时间,然后通过sendMessageAtTime()
->enqueueMessage
->
queue.enqueueMessage
这一系列方法将消息插入到MessageQueue
中。所以并不是先延迟再发送消息,而是直接发送消息,再借助MessageQueue
的设计来实现消息的延迟处理。
消息延迟处理的原理涉及MessageQueue
的两个静态方法MessageQueue.next()
和
MessageQueue.enqueueMessage()
。通过Native
方法阻塞线程一定时间,等到消息的执行时间到后再取出消息执行。同步屏障
SyncBarrier
是什么?有什么作用?在一般情况下,同步和异步消息处理起来没有什么不同。只有在设置了同步屏障后才会有差异。同步屏障从代码层面上看是一个
Message
对象,但是其target
属性为null
,用以区分普通消息。在MessageQueue.next()
中如果当前消息是一个同步屏障,则跳过后面所有的同步消息,找到第一个异步消息来处理。
但是开发者调用不了。在ViewRootlmpl
的UI测绘流程有体现IdleHandler
是什么?有什么作用?当消息队列没有消息时调用或者如果队列中仍有待处理的消息,但都未到执行时间时,也会调用此方法。用以监听主线程空闲状态。
为什么非静态类的
Handler
导致内存泄漏?如何解决?首先,非静态的内部类、匿名内部类、局部内部类都会隐式的持有其外部类的引用。也就是说在
Activity
中创建的,Handler会因此持有Activity
的引用。
当我们在主线程使用Handler
的时候,Handler会默认绑定这个线程的Looper
对象,并关联其MessageQueue,Handler
发出的所有消息都会加入到这个MessageQueue
中。Looper
对象的生命周期贯穿整个主线程的生命周期,所以当Looper对象中的MessageQueue里还有未处理完的Message
时,因为每个Message
都持有Handler
的引用,所以Handler
无法被回收,自然其持有引用的外部类Activity
也无法回收,造成泄漏。
使用静态内部类+弱引用的方式如何让在子线程中弹出toast
调用
Looper.prepare
以及Looperloop()
,但是切记线程任务执行完,需要手动调用Looper.quitSafely()
否则线程不会结束。
下一章: 组件间通信