Android中的Handler消息机制

Android Handler 机制原理详解

四个关键角色:MessageHandlerMessageQueueLooper

需要传递的消息放在Message中。Message有一个成员变量target引用它绑定的Handler对象。

Handler在创建时需要一个Looper对象,Handler会在sendMessage方法中将Message添加到这个Looper的MessageQueue中排队等待触发回调。

每个线程只能有一个Looper,Looper和线程的绑定关系是靠ThreadLocal<Looper>实现,它在Looper.prepare()初始化时会给自己创建一个MessageQueue,然后Looper.loop()开启一个死循环,不停地从MessageQueue中取出Message,再通过Message的target获取到对应的Handler,调用Handler的handleMessage方法,从而执行回调。

MessageQueue是一个按时间排序的单向链表,负责存放等待Looper处理的Message。Handler调用MessageQueue的enqueueMessage()方法添加Message,Looper调用MessageQueue的next()方法获取Message。

建议通过Message.obtain()从缓存池中获取Message对象复用,而不要直接new,避免创建太多对象,减轻GC压力。Message缓存池的最大容量是50个。Looper在调用完handleMessage方法后会回收Message对象。

当MessageQueue中没有Message时,Looper并不会死循环一直空转占满CPU导致卡死ANR,而是Looper所在的线程会进入阻塞休眠状态,不会执行循环体。这是因为Looper调用MessageQueue的next()方法获取Message,而next()中判断如果MessageQueue为空,则调用nativePollOnce方法阻塞,该方法是在native层(C++)实现的,因此是调用Linux的epoll机制进行阻塞。

唤醒的时机在Handler添加Message时调用的enqueueMessage当中,里面会调用nativeWake方法,唤醒epoll阻塞的线程。

如果主线程Looper中执行太耗时的操作,会阻塞UI造成掉帧,因此耗时操作放在子线程中。

Q:如何创建Looper?

A:

Looper.prepare()或在创建线程时直接使用HandlerThread,这样创建出来的线程会自动创建Looper。

Q:为什么Android不允许子线程直接访问UI

A:

因为UI系统不是线程安全的,View体系里有大量状态,如果多个线程同时改UI,可能出现数据竞争、状态不一致、绘制中途被修改等问题。

Q:如果不主动往Handler传Context,它会持有Context导致泄漏吗?

A:

Handler本身不会凭空持有Context。但一些写法如非静态内部类Handler会隐式持有外部类引用。因此正确做法是静态内部类 + WeakReference,同时退出时removeCallbacksAndMessages移除消息。

Q:Handler要怎么实现让一个消息加急处理?

A:

Handler机制里没有加急优先级这种直接概念,消息的处理顺序本质上是由MessageQueue按时间顺序(when)+先入先出决定的。但是可以用handler.sendMessageAtFrontOfQueue(msg)方法将消息插到队列最前面。或是设置一个较早的时间,因为Handler内部是按when排序的。

Handler的同步屏障

TODO