Android消息传递机制,主要用于线程间通信
Looper.javaMessageQueue.javaMessage.java
Message
Message类是个final类,不能被继承,同时Message类实现了Parcelable接口,链表结构,内部维护了一个链表缓存池,避免重复创建对象,以sPool作为链表头。
尽管Message的构造器是公开的,但 是获取Message对象的最好方法是调用Message.obtain()或者Handler.obtainMessage(), 这样是从一个 可回收对象池中获取Message对象。
Obtain方法
1 | //避免分配新的对象,静态方法 |
发送消息
1 | //target即handler实例 |
MessageQueue
MessageQueue依附于Looper,不应该单独创建它,可从Looper类中获取它
1 | public final class MessageQueue { |
- removeAllMessagesLocked() ,清除掉队列中所有的消息。
- removeAllFutureMessagesLocked() ,清除可能还没有被处理的消息。(不会清除当前正在处理的消息,较友好)
消息入队
1 | boolean enqueueMessage(Message msg, long when) { |
同步屏障
postSyncBarrier(long when) 方法也可以向 队列添加消息,,我们将它添加的特殊Message称为同步屏障。某个Message被调用了 setAsynchronous(true) 后才是异步消息,此方法返回一个token,之后可以根据token找到次消息,然后移除屏障
同步消息受队列限制依次 有序的等待处理,异步消息不受限制。
队列空闲处理器IdleHandler
每次队列中没有消息而进入的阻塞状态,我们叫它为“空闲状态”。
1 | public static interface IdleHandler { |
MessageQueue为我们提供了添加和删除IdleHandler的方法:
1 | ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>(); |
消息出队next()方法
// 阻塞线程操作
// 线程将被阻塞的时间
// -1:一直阻塞
// 0:不阻塞
// >0:阻塞nextPollTimeoutMillis 毫秒 int nextPollTimeoutMillis = 0;nativePollOnce(ptr, nextPollTimeoutMillis);
1.循环开始
2.根据msg的target是不是null判断是否有同步屏障,有的话将指针指向最近的那个异步消息
3.如果msg=null,则nextPollTimeoutMillis=-1,让线程阻塞
4.如果msg!=null,则根据when去处理消息,等待或者直接处理消息
5.如果msg=null或延时消息(now < mMessages.when),会去执行IdleHandler,否则直接return msg
Looper
单纯从MQ中取出消息分发给目标Handler
Looper是线程独立,每个线程只能有一个Looper,会根据自己的存活管理MQ
1 | // 与当前Looper对应的消息队列 |
MQ和线程都是final修饰的,只能赋值一次
Looper.prepare()初始化之后,可以调用以下方法获得对应实例
1 | public static Looper myLooper() { |
是否安全退出Looper在MQ源码中解释过
Looper.loop()比较简单,死循环不断取出消息分发给对应Handler,Looper更想强调的是线程的独立性和唯一性,
用ThreadLocal保证每个线程只有一个Looper实例
Handler
Handler可以在多线程中调用,不管在哪个线程中发消息,都会回到当初初始化的线程中接收消息
Handler有很多初始化方法,最终都是为了给四个变量赋值
1 | //如果使用其他构造函数,没有传looper实例,则使用Looper.mylopper()获取 |
2种发送消息的方式(最终都是传msg对象)postXXX()sendMessageXXX()
其实这两种方法本质都是发送一个Message对象到原线程,只不过 PostXXX() 方法是发送了一个只有
Runnable callback 属性的Message对象。
1 | public final boolean post( Runnable r) { |
不管调用何种发送消息的方法,最后真正调用的都是sendMessageAtTime()方法
真正发送的核心方法也就是入队方法是Handler的enqueueMessage()方法
1 | private boolean enqueueMessage(MessageQueue queue, Message msg, long |
Handler接受消息
Looper.loop()中,执行死循环MQ.next()不断取出msg,然后执行如下:msg.target.dispatchMessage(msg);
1 | public void dispatchMessage( Message msg) { |
可见post比send优先级高
Handler内存泄漏
内部类隐式的持有着外部类的引用,编译器在创建内部类时把外部类的引用传入了其中
当我们在子线程执行一项耗时操作时,用户退出程序,Activity需要被销毁,而Handler还在持有 Activity的引用导致无法回收,就会引发内存泄漏。
- 生成内部类时把内部类声明为静态的(静态内部类不会持有外部类的引用)
- 使用弱引用来持有外部类引用(弱引用不会阻止JVM回收对象)
Hanlder总结
- Message使用缓存池是因为msg使用量巨大,回收可以减少消耗
- Message.obtain()可以从缓存池取出msg,在Looper.loop中分发msg,等msg同步执行完,会进行msg的recycle
- 真正的阻塞发生在MQ的next()方法中的nativePollOnce方法,采用了linux的epoll机制
1
2
3
4
5
6
7Looper#loop()
-> MessageQueue#next()
-> MessageQueue#nativePollOnce()
-> NativeMessageQueue#pollOnce() //注意,进入 Native 层
-> Looper#pollOnce()
-> Looper#pollInner()
-> epoll_wait()epoll全称 eventpoll,是 Linux I/O 多路复用的其中一个实现Native层支持监听自定义Fd,比如Input事件就是通过epoll监听socketfd来实现将事件转发到APP进程的Java层的nativePollOnce最终调用到JNI层的epoll_wait方法,让线程进入阻塞状态,让出CPU调度