一、概览
类似于过安检,人(Handler)将行李(Message)放在安检机(MessageQueue)中,传送带(Looper)通过不断循环将行李从安检机中取出,最后由同一个人(Handler)取走处理。
Handler通过 sendMessage() 方法发送消息 → MessageQueue通过 enqueueMessage() 方法将消息插入队列 → Looper通过 loop() 开启死循环不断轮询消息 → 通过 MessageQueue 的 next() 方法将消息取出 → 通过 Handler 的 dispatchMessage() 方法判断消息携带的是 callback 还是对象,是 callback 就执行,是对象就回调 handleMessage() 处理。
二、Looper
- 轮询器。每个线程中只有一个Looper,切换线程和消息分发,唤醒与挂起靠的是 Linux 中的 epoll 机制来实现。
handler构造时,可以选择是否传入looper对象,若不传则默认从当前线程获取,若取不到则会抛出异常。这就是为什么在子线程里不能简单用消息循环的原因,因为handler没有和looper建立起关联。
2.1 prepareMainLooper( )
AMS 调用 ActivityThread.main() 进而调用 Looper.prepareMianLooper() 进而调用 parper(),将主线程和 Looper 绑定,然后调用 loop() 跑起来。
2.2 prepare( )
用来将当前线程和Looper绑定。先通过 sThreadLocal.get() 看有没有 value(这里是主线程是否绑定过Looper),value不为 null 就报错不让再次赋值保证了 Looper 的唯一性,value为 null 就调用 sThreadLocal.set() 新建一个 Looper 对象(构造函数中会创建MessageQueue对象)传入赋值实现了 Looper 和主线程绑定。

2.3 sThreadLocal
Looper 中创建了一个 static final 的 ThreadLocal 成员变量。

2.3.1 Thread
该类中有一个成员变量 threadLocals 它是一个空的 ThreadLocalMap。即每个线程都有自己的ThreadLocalMap,当不同线程访问代码被 ThreadLocal 操作的是它们各自持有的数据。

2.3.2 ThreadLocal
在一个线程中可以创建多个 ThreadLocal 对象(作用是当作key存入value),不管是调用 ThreadLocal 对象的 get() 还是 set() 内部都会先获取当前线程Thread(这里是主线程),然后获取 Thread 所持有的 ThreadLocalMap(即Thread的成员变量threadLocals),以该 ThreadLocal 对象(这里是sThreadLocal)为 key 往里面存取 value(这里是Looper)。由此通过 sThreadLocal 将 Looper 和主线程的绑定。


2.3.3 ThreadLocalMap
是 ThreadLocal 的静态内部类。Entry 继承于 WeakReference,key 是 ThreadLocal 类型,value是 Object 类型。
2.4 myLooper( )
返回当前线程绑定的 Looper 对象,如果未绑定过则返回null。

2.5 loop( )
拿到当前线程的 Looper 对象(此处为主线程),通过无限 for 循环保证了不会因为代码执行完而退出 main() 函数导致程序(进程)结束。loopOnce()中调用 MessageQueue.next() 从队列中取出 Message,再调用 Message.target.dispatchMessage() 分发给发送这个 Message 的 Handler 处理(即发送和处理该 Message 的是同一个 Handler)。


四、Handler
负责发送和接收(处理)Message。Handler 在构造时可以选择是否传入 Looper 对象,若不传则默认从当前线程获取,若取不到则会抛出异常。这就是为什么在子线程里不能简单用消息循环的原因,因为需要 Handler 和 Looper 绑定。
| sendMessageAtTime( ) | 发送消息的方法非常多 (post/send) 最终调用的都是这个,uptimeMillis即系统开机时间uptimeMillis和传入的延迟时间 delayMillis 相加。 |
| handlerMessage( ) | 由外部来重写该方法,以此来处理收到的Message。 |
| dispatchMessage( ) | 用于分发消息,判断Message的callback,不为null调用Runable(),为null调用handleMessage()。 |

4.1 Handler( )
Handler构造时需要传入一个 Looper 实现了 Handler 和 Looper 绑定,通过唯一的 Looper 拿到唯一的MessageQueue,使得 Handler 和 MessageQueue 绑定(即同一个 Handler 在不同线程中发送 Message 是发到同一个 MessageQueue 中),由于该 MessageQueue 所属的 Looper 所绑定的是主线程,因此子线程中发送的 Message 最终是在主线程中处理。

4.2 sendMessageAtTime( )
发送消息的方法非常多(各种post/send)最终调用的都是这个,在确保存在 MessageQueue 后就调用 enqueueMessage() 将 Message 入列。

4.3 enqueueMessage( )
Handler先将自己赋值给 Messgae 的 target 实现了 Handler 和 Message 绑定(即发送和处理该 Message 的是同一个 Handler),然后调用 MessageQueue.enqueueMessage() 将 Message 存入队列中。

4.4 dispatchMessage( )
判断 Message 携带的是 callback 还是对象,是 callback 就直接运行,是对象就回调到 handleMessage() 中让我们在创建 Handler 对象的地方重写该方法处理。

五、MessageQueue
消息队列。每个线程中只有一个MessageQueue,存放 Handler 发送来的Message。是单链表结构(非线性非顺序的物理结构。采用见缝插针的存储方式不要求内存连续,也不需要随机访问(只需要取头值),靠 Message中 的 next 指向下一个,随机存储顺序访问)。
5.1 Looper.mQueue
Looper构造是私有的,其中会创建 MessageQueue 并赋值给 final 修饰的成员变量mQueue,因此 MessageQueue 也是唯一。


5.2 enqueueMessage( )
enqueueMessage()里的同步锁让 Handler 在不同线程中发送过来的 Message 同步入列,并通过形参 when 来对队列中的 Message 进行排序。形参 when 给 Message 加了时间戳排序。

5.3 next( )
next()里的同步锁与 enqueueMessage() 里的同步锁相互协作,即便是在子线程发消息在主线程取消息,实现了消息入列和出列的互斥性(不能同时进行),保证了有序性和线程安全。
- 什么消息都没有的时候,执行 nativePollOnce() 处于休眠状态节省CPU给其它地方。(早期使用的是 wait/notify,Android2.3后来使用 epoll 机制为了可以同时处理 native 消息。)
- 当 if(msg != null && msg.target == null) 说明是消息屏障,立马轮询优先执行异步消息。
- 当 if(msg != null) 为普通消息,根据时间戳取头部执行。
- 当非延时的 Message 都执行完的时候(主线程没有重要的事情做的时候),通过 for 循环处理 idleMessage。


六、Message
可以携带需要的数据在线程间传递。根据优先级分为:异步消息 > 普通消息 > idleMessage。
| 消息屏障 | 判断依据 msg.target == null。发送屏障消息后会发送异步消息(判断依据msg.isAsynchronous),获取的是当前时间戳,会加入到队列头部,由于优先刷新UI。 |
| idleMessage(不重要消息) | 当所有延时为0的消息都处理完后,主线程没有重要的事情做的时候(即不影响主线程的时候),才会去执行idleMessage。例如GC、AMS管理Activity的 stop destroy。 |
| Message的字段 | 说明 |
| what: Int | 唯一标识。 |
| target: Handler | 绑定Handler(该 Message 是由同一个 Handler 发送和处理的)。 |
| data: Bundle | 携带 Bundle 数据。 |
| arg1: Int arg2: Int | 携带 Int 数据。 |
| obj: Object | 携带可序列化数据。 |
| callback: Runnable | 携带 Runnable 数据(Handler的post()就是将一个Runnable对象复制过来封装为Message对象)。 |
| when:Long | 时间戳。 |
| next: Message | 指向下一个节点(Message 通过 next 字段指向下一个Message,从而串联成队列。enqueueMessage()、recycleUnchecked()、obtain() 操作的就是 next 的指向)。 |
6.1 Message.sPool
回收后用来复用的 Message,它是 static 的。MAX_POOL_SIZE = 50 缓存池最多存50个,超过就会 new 了。sPoolSize = 0 用来记录池中数量,缓存一个就+1取出一个就-1。spoolSync用来做同步锁。

6.2 obtain( )
构建 Message 实例,可以通过 new 关键字,推荐通过 obtain() 方法。APP中会有大量管理事件需要通过 Handler 来发送消息(60Hz屏幕光UI刷新每秒就有60个Message更别其它提系统服务了),使用缓存池为了避免频繁创建销毁 Message 对象内存抖动造成卡顿。

6.3 recycleUnchecked( )
使用完的 Message 会被回收,将字段携带的数据都清除掉后,通过 next 将下一个节点指向当前 sPool,再将自己赋值给sPool,这样就像该 Message 插入到了缓存池队列头部。

七、使用步骤
- 在主线程里创建一个全局的 handler 对象并重写 handleMessage( ) 方法。
- 在子线程里构造 message,并使用 handler 将 message 发送出去。
- 在主线程里的 handleMessage( ) 方法中判断是哪个 message 然后做相应处理。
Activity {
private val updateText = 123 //定义Message唯一值
//【第一步】创建一个全局的Handler并重写handleMessage()
private val handler = object : Handler(Looper.getMainLooper()) {
//【第三步】处理消息
override fun handleMessage(msg: Message) {
when (msg.what) { //判断是哪个Message
updateText -> {
msg.obj //获取携带的数据
}
}
}
}
//【第二步】创建Messgae并发送
fun show() {
val str = "一条消息"
//方式一:直接新建
val msg1 = Message()
msg1.what = updateText
msg1.obj = str
handler.sendMessage(msg1)
//方式二:避免频繁创建销毁Message推荐使用obtain()
val msg2 = Message.obtain()
val msg3 = handler.obtainMessage() //相对于上一个,会自动将target字段设置为这个handler
val msg4 = handler.obtainMessage(updateText, str) //相对于上一个,构造里就能携带what和obj字段
handler.sendMessage(msg4)
//方式三:利用post在子线程中更新UI,利用postDelayed延时
handler.postDelayed({
//此处执行在Runnable中
}, 2000) //延迟2秒
}
}
八、内存泄漏风险(Java)
8.1 原因
- 非静态的内部类无法直接 new 对象,需要先创建外部类对象再创建内部类对象,因此内部类持有外部类的引用,匿名内部类(定义一个类的同时对其进行实例化)同理。
- 跳转 Activity 给 Intent 赋值的时候,第一个参数会写外部类名.this ,这就是持有外部类引用的很好例子。创建 Handler 对象的时候就是通过匿名内部类的方式
- 非静态的 Handler 内部类,无论是否是匿名创建,便会持有外部 Activity 的引用。在 Activity 销毁时,若此时消息队列中有未处理完的 Message(特别是带有延时的),那么 Handler 也仍然存在(在enqueueMessage()的时候Handler被赋值给了Message.target,所以Message持有Handler),由于 Handler 持有外部 Activity 的引用,那么Activity就无法正常回收(GC可达性分析),Activity中所有占内存的东西就成了内存泄露。
8.2 解决
- 方式一:被static修饰后调用便不需要外部类的实例,但是总不能把Activity中的控件全static才能操作,我们通过使Handler持有Activity的一个弱引用来解决这个问题,直接持有Activity的话,我们便与之前的匿名内部类直接持有外部类的引用没区别了,而持有了弱引用,在Activity有用的情况下,其会被AMS持有强引用,GC不会回收,而当其finish了,便没有强引用对象持有了,此时GC时便会回收该Activity。
- 方式二:在 onDestroy() 中调用 handler.removeCallbackAndMessage() 也可以避免内存泄漏。
public class MainActivity extends Activity {
private MyHandler mMyHandler;
//自定义一个静态的Handler
private static class MyHandler extends Handler {
private WeakReference<MainActivity> mActivityWeakReference;
//构造中传入当前Activity并使用弱引用包装
public void MyHandler(MainActivity activity) {
mActivityWeakReference = new WeakReference<>(activity);
}
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
MainActivity mainActivity = mActivityWeakReference.get();
if (mainActivity != null) {
switch (msg.what) {
case 0:
//TODO...
break;
}
}
}
}
onCreate() {
mMyHandler = new MyHandler(this);
}
click() {
mMyHandler.sendMessage();
}
}
九、在子线程中创建Handler(HandlerThread)
要在子线程中创建 Handler 就在构造的时候传入子线程中的Looper,进而需要拿到子线程的 ThreadLocal,进而需要拿到子线程对象,因此子线程不能写成匿名内部类,需要定义成一个类。
新创建一个子线程并不会自动建立 Looper,需要在类中重写 run() 方法调用 Looper.prepare() 创建 Looper 和该线程绑定,再调用 looper.loop() 将循环跑起来,然后提供一个 getLooper() 方法供外部获取。外部先创建线程对象,调用 start() 跑起来就初始化了Looper,然后通过获取 getLooper() 的在创建 Handler 的时候传入。注意:子线程在 run() 中创建Looper,外部创建 Handler 时是在主线程中 getLooper() 传入的,可能为null,主线程拿子线程结果存在多线程并发问题。使用 sleep() 存在性能问题,要使用同步锁机制。
实际开发中不会手写,而是使用HandlerThread。Looper在 run() 中创建在 getLooper() 中获取,两个方法中的同步代码块使得创建和获取互斥,保证了获取前 Looper 已经被初始化。
9.1 getLooper( )
获取 Looper。同步代码块中的判断用来确保获取前 Looper 已经被初始化,否则释放锁让线程进到 run( ) 方法的同步代码块中创建Looper。

9.2 run( )
创建 Looper 并循环起来。同步代码块保证了在 getLooper( ) 中获取的安全性。可能有很多地方等着获取该 Looper 来创建 Handler,因此要 notifyAll() 全部唤醒。

9.3 使用
val handlerThread = HandlerThread("demo") //创建HandlerThread子线程
handlerThread.start() //启动线程
val handler = Handler(handlerThread.looper) { msg -> //创建Handler
//此处为handleMessage
when (msg.what) {
123 -> {}
else -> {}
}
false
}
handler.sendEmptyMessage(123) //发送消息
面试
.1 Handler、Loop、MessageQuee关系?
一个线程对应一个 Looper 对应一个 MessageQueue 对应无数个 Handler。
.2 非UI线程真的不能操作View吗?
并发访问会造成安全问题,而上锁会变得复杂低效,因此 UI 体系采用单线程模型,系统只是限制了必须在同个线程内进行 ViewRootImpl 的创建和更新。
更新 UI 会调用 View 的 requestLayout(),然后开始递归查找 ViewParent,最终找到顶层的DecorView。DecorView 的 ViewParent 是 ViewRootImpl 它的 requestLayout() 会调用 checkThread() 检查此线程是不是初始化自己的线程,不是就抛异常。ViewRootImpl 初始化的时候会绑定当前线程,而它是由在主线程的 ActivityThread 的 handleResumeActivity() 中初始化的。
由调用链可知 ActivityThread 的 handleResumeActivity() 是在 Activity 生命周期 onResume() 之后执行的,因此在 onCreate()、onStart()、onResume() 时 ViewRootImpl 还未初始化,也就可以在子线程中更新了。
public void click(View view) {
new Thread(new Runnable() {
public void run() {
Looper.prepare();
boolean b1 = Looper.myLooper() == Looper.getMainLooper(); //fale,当前线程不是主线程
Toast.makeText(Drawer99_Test_Activity.this,b1+","+b2,Toast.LENGTH_LONG).show();
Looper.loop();
}
}).start();
}
.3 为什么不用wait/notify而用epoll?
早期使用的是 wait/notify,Android2.3后来使用 epoll 机制为了可以同时处理 native 消息。
.4 View.post() 和 Handler.post()的区别?
View.post() 最终调用的是 Handler.post()。
.5 如何进行线程切换的?
子线程持有的 Handler 如果绑定到的是主线程的 Looper (Handler创建的时候需要传入Looper) 的话,那么子线程发送的 Message 就可以由主线程来消费,以此来实现线程切换,执行 UI 更新操作等目的。
.6 主线程 Looper.loop() 为什么不会导致 ANR?
loop() 循环本身不会导致 ANR,会出现 ANR 是因为在 loop 循环之内 Message 处理时间过长,无法处理其他事件导致。让程序不退出的话,写一个死循环,那么main方法中的代码永远不会执行完,这样程序就不会自己退出了。正是因为Looper.loop方法这个死循环,它阻塞了主线程,所以我们的app才不会退出。本质上Android就是事件驱动的程序,界面刷新也好,交互也好,本质上都是事件,这些事件最后通通被作为了Message发送到了MessageQueue中。由Looper来进行分发,然后在进行处理。也就是我们的Android程序就是运行在这个死循环中的,一旦这个死循环结束,app也就结束了。
本文深入剖析了Android中Handler机制的工作原理,包括Handler、Looper、MessageQueue之间的关系,消息发送与处理流程,以及Looper.loop()的执行过程。同时探讨了内存泄漏的风险及解决方案。

515

被折叠的 条评论
为什么被折叠?



