Android|Android 消息机制 Handler 详解
Handler
文章图片
一些Handler子类
Handler 允许你发送和处理,与线程的 MessageQueue 关联的 Message 和 Runnable 对象。每个 Handler 实例都与单个线程和该线程的消息队列相关联。当你创建一个新的 Handler 的时候,它绑定到正在创建它的线程的消息队列。从这时开始,Handler 可以把消息或可执行的任务传递给消息队列,并当消息被读取的时候执行它们。
- There are two main uses for a Handler
Handler的两个主要用处:- to schedule messages and runnables to be executed as some point in the future;
在将来的某个时候安排消息和子线程任务的执行。 - to enqueue an action to be performed on a different thread than your own.
添加在不同线程执行的事件。
- to schedule messages and runnables to be executed as some point in the future;
- post(Runnable),
- postAtTime(Runnable, long),
- postDelayed(Runnable, long),
- sendEmptyMessage(int),
- sendMessage(Message),
- sendMessageAtTime(Message, long),
- sendMessageDelayed(Message, long) methods.
当 post 或 sendMessage 到 handler 时,可以允许在消息队列准备就绪后立即处理该项目,或者在处理消息之前指定延迟或规定执行的时间。后两者允许你实现 timeout,ticks 和其他 timing-based (基于时间)的行为。
为应用程序创建一个进程时,其主线程将专门运行一个消息队列,负责管理顶层应用程序对象(活动,广播接收器等)以及它们创建的任何窗口。通过在子线程中调用 post 或 sendMessage 方法,并通过 Handler 与主线程进行通信。
Looper
文章图片
文章图片
Looper 是用于为线程运行消息循环的类。 线程默认情况下没有与它们相关的消息循环,因此必须创建一个,在要运行循环的线程中调用 prepare(),然后 loop() 使其处理消息,直到循环停止。 与消息循环的大多数交互是通过 Handler 类。
示例 这是执行 Looper 线程的一个典型例子,使用 prepare() 和 loop() 的分离来创建初始 Handler 来与 Looper 进行通信。
class LooperThread extends Thread {
public Handler mHandler;
@Override
public void run() {
Looper.prepare();
mHandler = new Handler() {
public void handleMessage(Message msg) {
// process incoming messages here
}
};
Looper.loop();
}
}
MessageQueue
文章图片
文章图片
MessageQueue 类是持有由 Looper 发送的消息列表的低级别类。 消息不直接添加到 MessageQueue,而是通过与 Looper 关联的 Handler 对象添加。 可以使用 Looper.myQueue() 恢复到当前线程的 MessageQueue。
文章图片
消息机制 Android 应用启动时,会默认有一个主线程(UI 线程),在这个线程中会关联一个消息队列,所有的操作都会被封装成消息然后交给主线程来处理。获取消息的操作放在一个死循环中,保证主线程不会主动退出。这样程序相当于一直执行死循环,因此不会退出。
UI 线程的消息循环是在
ActivityThread.main
方法中创建的:public static void main(String[] args) {
......
Process.setArgV0("");
//1.创建消息循环Looper
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
//UI线程的Handler
sMainThreadHandler = thread.getHandler();
}
......
//2.执行消息循环
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
在子线程中完成耗时操作后,很多时候要更新 UI,常用的方式就是通过 Handler 将一个消息
Post
到 UI 线程中,然后在 Handler 中的 handleMessage()
方法中进行处理。有一点要注意,该 Handler 必须在主线程中创建。一个简单的 Handler 示例:
public class MyHandler extends Handler {@Override
public void handleMessage(Message msg) {
//更新UI
}MyHandler mMyHandler = new MyHandler();
//开启新线程
new Thread(){
@Override
public void run() {
//耗时操作
mMyHandler.handleMessage(123);
}
}.start();
}
一、Handler 如何关联消息队列以及线程呢? 每个 Handler 都会关联一个消息队列,消息队列被封装在 Looper 中,而每个 Looper 又会关联一个线程(Looper 通过 ThreadLocal 封装),于是每个消息队列关联一个线程。
public Handler() {
this(null, false);
}public Handler(Callback callback, boolean async) {
......
mLooper = Looper.myLooper();
//获取Looper
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}mQueue = mLooper.mQueue;
//获取消息队列
mCallback = callback;
mAsynchronous = async;
}
从以上 Handler 的构造函数可以看出,Handler 在内部通过
Looper.myLooper()
来获取 Looper 对象,并且与之关联,最重要还获取了消息队列。public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
myLooper()
方法中是通过 sThreadLocal.get()
来获取 Looper 对象。那么 Looper 对象是什么时候存储在 sThreadLocal 中呢?
//设置UI线程的Looper
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}public static void prepare() {
prepare(true);
}//为当前线程设置一个 Looper,存储在 sThreadLocal 中
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
答案就是在
prepareMainLooper()
方法中,这个方法又调用了 prepare()
方法,在 prepare()
中创建了一个 Looper 对象,并设置给 sThreadLocal
。这样 Looper(消息队列)和线程就关联上了。所以,不同的线程不能访问对方的消息队列。消息队列通过 Looper 与线程关联上了,Handler 与 Looper 关联上,因此三者就彼此关联了。就是因为 Handler 要与主线程的消息队列关联上,这样 handleMessage 才会在 UI 线程中执行,此时更新 UI 才是线程安全的。
二、创建 Looper 后,如何进行消息循环? 消息循环的建立就是通过 Looper.loop() 方法。
/**
* Run the message queue in this thread. Be sure to call
* {@link #quit()} to end the loop.
*/
public static void loop() {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper;
Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;
//1.获取消息队列......
for (;
;
) {//2.死循环,即消息循环
Message msg = queue.next();
// 3.获取消息 (might block)
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}......
try {
msg.target.dispatchMessage(msg);
//4.处理消息
......
msg.recycleUnchecked();
//5.回收消息
}
}
通过 Looper.prepare() 来创建 Looper 对象,消息队列封装在 Looper 对象中,Looper 对象保存在 sThreadLocal 中,通过 Looper.loop() 来执行消息循环,这两步通常成对出现。三、消息如何被处理? 可以看到,Looper.loop() 中是通过第4步
msg.target.dispatchMessage(msg) 来处理消息的。
下面来看看 Message 的源码。
public final class Message implements Parcelable {
....../*package*/ Handler target;
//target处理/*package*/ Runnable callback;
//Runnable类型的callback// sometimes we store linked lists of these things
/*package*/ Message next;
//下一条消息,消息队列是链式存储的......
可以看到,target 是 Handler 类型,实际就是转了一圈,Handler 将消息投递给消息队列,消息队列又把消息分发给 Handler 来处理。
下面是 Handler 实际处理消息的源码
/**
* Subclasses must implement this to receive messages.
* 消息处理函数,子类必须覆写
*/
public void handleMessage(Message msg) {
}/**
* Handle system messages here.
* 分发消息
*/
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}private static void handleCallback(Message message) {
message.callback.run();
}
可以看到,dispatchMessage 只是一个分发方法,如果 Runnable 类型的 callback 为空,则执行 handleMessage() 方法来处理消息,通常我们需要覆写该方法,将更新 UI 的代码写在里面。如果 callback 不为空,则执行 handleCallback() 方法,该方法会调用 callback 的 run 方法。
其实就是 Handler 分发的两种类型,一般我们
post(Runnable callback)
则 callback 就不为空,当使用 sendMessage
时一般不会设置 callback,因此就执行 handleMessage 这个分支。下面来看下 post() 方法的具体实现
public final boolean post(Runnable r)
{
returnsendMessageDelayed(getPostMessage(r), 0);
}//将Runnable对象包装成Message对象
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
//设置消息的target为当前的Handler对象
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
//将消息插入到消息队列
}
在 post(Runnable r) 时,会将 Runnable 对象包装成 Message 对象,并将 Runnable 对象设置给 Message 对象的 callback 字段,最后将该 Message 对象插入消息队列。
sendMessage 方法的实现也是类似。
public final boolean sendMessage(Message msg)
{
return sendMessageDelayed(msg, 0);
}
无论是 post 一个 Runnable 还是 sendMessage,都会调用 sendMessageDelayed(msg, time) 方法。
Handler 不断将消息追加到 MessageQueue 中,而 Looper 不断从 MessageQueue 中读取消息,并调用 Handler 的 dispatchMessage 消息,这样 Android 应用就运转起来。
四、在子线程中创建 Handler 为何会抛出异常? 先来看看下面这个代码
new Thread() {
Handler mHandler = null;
@Override
public void run() {
mHandler = new Handler();
}
}.start();
这个代码是有问题的,会抛出
"Can't create handler inside thread that has not called Looper.prepare()"
异常,原因就出在 Handler 构造函数中。public Handler() {
this(null, false);
}public Handler(Callback callback, boolean async) {
......
mLooper = Looper.myLooper();
//获取Looper
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}mQueue = mLooper.mQueue;
//获取消息队列
mCallback = callback;
mAsynchronous = async;
抛出异常是因为该线程中 Looper 对象还没有创建,因此,sThreadLocal.get() 会返回 null。没有 Looper 就没有 MessageQueue,而 Handler 的原理就是要与 MessageQueue 建立关联。因此,创建 Handler 时 Looper 一定不能为空。
解决方案如下:
new Thread() {
Handler mHandler = null;
@Override
public void run() {
//1.为该线程创建Looper,并且会绑定到ThreadLocal中
Looper.prepare();
mHandler = new Handler();
//2.启动消息循环
Looper.loop();
}
}.start();
【Android|Android 消息机制 Handler 详解】顺带一提,一个 Looper 可以被多个 Handler 共同使用,在 Handler 的构造方法里可以指定 Looper。
文章图片
推荐阅读
- android第三方框架(五)ButterKnife
- 危险也是机会
- python学习之|python学习之 实现QQ自动发送消息
- Android中的AES加密-下
- 带有Hilt的Android上的依赖注入
- android|android studio中ndk的使用
- Android事件传递源码分析
- RxJava|RxJava 在Android项目中的使用(一)
- Android7.0|Android7.0 第三方应用无法访问私有库
- 深入理解|深入理解 Android 9.0 Crash 机制(二)