Android Handler,Looper 与 MessageQueue 使用与分析
本文最后更新于:2023年4月15日 下午
Handler 简介
一个Handler允许发送和处理Message,通过关联线程的 MessageQueue 执行 Runnable 对象。
每个Handler实例都和一个单独的线程及其消息队列绑定。
可以将一个任务切换到Handler所在的线程中去执行。一个用法就是子线程通过Handler更新UI。
Handler主要有2种用法:
- 做出计划,在未来某个时间点执行消息和Runnable
- 线程调度,在其他线程规划并执行任务
要使用好Handler,需要了解与其相关的 MessageQueue
, Message
和Looper
;不能孤立的看Handler。
Handler就像一个操作者(或者像一个对开发者开放的窗口),利用MessageQueue
和Looper
来实现任务调度和处理。
Handler持有 Looper 的实例,直接持有looper的消息队列。
属性与构造器
Handler类中持有的实例,持有looper,messageQueue等等。
1 |
|
在Handler的构造器中,我们可以看到Handler获取了Looper的消息队列。
1 |
|
Handler的使用方法
Handler发送和处理消息的几个方法
- void handleMessage( Message msg):处理消息的方法,该方法通常被重写。
- final boolean hasMessage(int what):检查消息队列中是否包含有what属性为指定值的消息
- final boolean hasMessage(int what ,Object object) :检查消息队列中是否包含有what好object属性指定值的消息
- sendEmptyMessage(int what):发送空消息
- final Boolean send EmptyMessageDelayed(int what ,long delayMillis):指定多少毫秒发送空消息
- final boolean sendMessage(Message msg):立即发送消息
- final boolean sendMessageDelayed(Message msg,long delayMillis):多少秒之后发送消息
Handler.sendEmptyMessage(int what) 流程解析
获取一个Message实例,并立即将Message实例添加到消息队列中去。
简要流程如下:
1 |
|
可以看到,最后是把message添加到了messageQueue中。
Handler 取消任务
要取消任务时,调用下面这个方法removeCallbacksAndMessages(Object token)
1 |
|
通过调用
Message.recycleUnchecked()
方法,取消掉与此Handler相关联的Message。
相关的消息队列会执行取消指令
1 |
|
消息驱动与Handler
Android是消息驱动的,实现消息驱动有几个要素
- 消息的表示:Message
- 消息队列:MessageQueue
- 消息循环,用于循环取出消息进行处理:Looper
- 消息处理,消息循环从消息队列中取出消息后要对消息进行处理:Handler
初始化消息队列
在Looper构造器中即创建了一个MessageQueue,Looper持有消息队列的实例。
发送消息
通过Looper.prepare初始化好消息队列后就可以调用Looper.loop进入消息循环了,然后我们就可以向消息队列发送消息,
消息循环就会取出消息进行处理,在看消息处理之前,先看一下消息是怎么被添加到消息队列的。
消息循环
Java层的消息都保存在了Java层MessageQueue的成员mMessages中,Native层的消息都保存在了Native Looper的
mMessageEnvelopes中,这就可以说有两个消息队列,而且都是按时间排列的。
Message 和 MessageQueue 简介
与Handler工作的几个组件Looper、MessageQueue各自的作用:
- 1.Handler:它把消息发送给Looper管理的MessageQueue,并负责处理Looper分给它的消息
- 2.MessageQueue:管理Message,由Looper管理
- 3.Looper:每个线程只有一个Looper,比如UI线程中,系统会默认的初始化一个Looper对象,它负责管理MessageQueue,不断的从MessageQueue中取消息,并将相对应的消息分给Handler处理。
Message
Message 属于被传递,被使用的角色。Message 是包含描述和任意数据对象的“消息”,能被发送给Handler
。Message包含2个int属性和一个额外的对象。
虽然构造器是公开的,但获取实例最好的办法是调用Message.obtain()
或Handler.obtainMessage()
。这样可以从他们的可回收对象池中获取到消息实例。一般来说,每个Message实例持有一个Handler。
Message部分属性值
1 |
|
从这里也不难看出,每个Message都持有Handler实例。如果Handler持有Activity的引用,Activity onDestroy后Message却仍然在队列中,因为Handler与Activity的强关联,会造成Activity无法被GC回收,导致内存泄露。
因此在Activity onDestroy 时,与Activity关联的Handler应清除它的队列由Activity产生的任务,避免内存泄露。
重置自身的方法,将属性全部重置
1 |
|
获取Message实例的常用方法,得到的实例与传入的Handler绑定
1 |
|
将消息发送给Handler
1 |
|
调用这个方法后,Handler会将消息添加进它的消息队列
MessageQueue
中。
MessageQueue
持有一列可以被Looper分发的Message。一般来说由Handler将Message添加到MessageQueue中。
获取当前线程的MessageQueue方法是Looper.myQueue()
。通过Looper.getMainLooper()
获取到主线程的looper。
Looper 简介
Looper与MessageQueue紧密关联。在一个线程中运行的消息循环。线程默认情况下是没有与之管理的消息循环的。
要创建一个消息循环,在线程中调用prepare,然后调用loop。即开始处理消息,直到循环停止。大多数情况下通过Handler来与消息循环互动。
Handler与Looper在线程中交互的典型例子
1 |
|
调用了Looper.loop()
之后,looper开始运行。当looper的messageQueue中没有消息时,相关的线程处于什么状态呢?
查看looper的源码,看到loop方法里面有一个死循环。queue.next()方法是可能会阻塞线程的。如果从queue中获取到null,则表明此消息队列正在退出。此时looper的死循环也会被返回。
1 |
|
调用looper的quit方法,实际上调用了
mQueue.quit(false)
。消息队列退出后,looper的loop死循环也被退出了。
进入MessageQueue的next方法去看,发现里面也有一个死循环。没有消息时,这个死循环会阻塞在nativePollOnce
这个方法。
1 |
|
我们知道Thread有New(新建,未运行),RUNNABLE(正常运行),BLOCKED,WAITING(线程拥有了某个锁之后, 调用了他的wait方法, 等待其他线程/锁拥有者调用 notify / notifyAll),TIMED_WAITING,TERMINATED(已经执行完毕)这几种状态。消息队列中没有消息,在nativePollOnce方法中“等待”。相关线程则处于RUNNABLE状态。
Looper中的属性
Looper持有MessageQueue;唯一的主线程Looper sMainLooper
;Looper当前线程 mThread
;
存储Looper的sThreadLocal
1 |
|
ThreadLocal并不是线程,它的作用是可以在每个线程中存储数据。
Looper 方法
准备方法,将当前线程初始化为Looper。退出时要调用quit
1 |
|
prepare
方法新建 Looper 并存入 sThreadLocal sThreadLocal.set(new Looper(quitAllowed))
ThreadLocal<T>
类
1 |
|
当要获取Looper对象时,从sThreadLocal
获取
1 |
|
在当前线程运行一个消息队列。结束后要调用退出方法quit()
1 |
|
准备主线程Looper。Android环境会创建主线程Looper,开发者不应该自己调用这个方法。
UI线程,它就是ActivityThread,ActivityThread被创建时就会初始化Looper,这也是在主线程中默认可以使用Handler的原因。
1 |
|
获取主线程的Looper。我们开发者想操作主线程时,可调用此方法
1 |
|
同一个Thread的不同Handler
与UI线程对应的MainLooper,可以关联多个Handler。
多个Handler之间的计划任务不会互相影响。比如有2个关联了UI线程的handler。
1 |
|
mMainHandler2取消它的任务并不会影响mMainHandler1。