探索Android的心脏--一文搞懂Android Handler消息机制
- [一文搞懂Android Handler](#一文搞懂Android Handler "#%E4%B8%80%E6%96%87%E6%90%9E%E6%87%82android-handler")
- [1.1 Handler消息机制介绍](#1.1 Handler消息机制介绍 "#11-handler%E6%B6%88%E6%81%AF%E6%9C%BA%E5%88%B6%E4%BB%8B%E7%BB%8D")
- [1.2 Handler类详解](#1.2 Handler类详解 "#12-handler%E7%B1%BB%E8%AF%A6%E8%A7%A3")
- [1.3 Message类详解](#1.3 Message类详解 "#13-message%E7%B1%BB%E8%AF%A6%E8%A7%A3")
- [1.4 Looper类详解](#1.4 Looper类详解 "#14-looper%E7%B1%BB%E8%AF%A6%E8%A7%A3")
- [1.5 MessageQueue类详解](#1.5 MessageQueue类详解 "#15-messagequeue%E7%B1%BB%E8%AF%A6%E8%A7%A3")
- [1.6 Handler消息机制实战](#1.6 Handler消息机制实战 "#16-handler%E6%B6%88%E6%81%AF%E6%9C%BA%E5%88%B6%E5%AE%9E%E6%88%98")
- [1.7 Handler消息机制优化](#1.7 Handler消息机制优化 "#17-handler%E6%B6%88%E6%81%AF%E6%9C%BA%E5%88%B6%E4%BC%98%E5%8C%96")
- [1.8 Handler消息机制应用场景](#1.8 Handler消息机制应用场景 "#18-handler%E6%B6%88%E6%81%AF%E6%9C%BA%E5%88%B6%E5%BA%94%E7%94%A8%E5%9C%BA%E6%99%AF")
- 总结
1.1 Handler消息机制介绍
Handler消息机制是Android系统中非常重要的一部分,它主要用于在不同线程之间进行通信。Handler类提供了发送和处理Message对象和Runnable对象到MessageQueue的接口。每个Handler实例都与创建它的线程和该线程的MessageQueue相关联。
在Android中,我们知道UI操作必须在主线程(UI线程)中进行,而网络请求、数据库操作等耗时操作则需要在子线程中进行,那么当我们需要将子线程的结果反馈给主线程时,就需要用到Handler了。
Handler通过发送一个Message或者Runnable对象到其所在的MessageQueue,然后Looper会从MessageQueue中取出这些消息并交给对应的Handler处理。这样就实现了跨线程通信。
我们也可以使用Handler来定时发送消息,实现定时任务。例如,我们可以使用postDelayed方法来延迟一段时间后发送一个Runnable对象。
接下来,我们将深入分析Handler类、Message类、Looper类和MessageQueue类的内部结构和工作原理,并通过实例代码演示如何在实际开发中使用Handler消息机制。
1.2 Handler类详解
Handler类是Android消息机制中的核心类,它提供了发送和处理消息的接口。每个Handler实例都与创建它的线程和该线程的MessageQueue相关联。
Handler主要有两种用途:
- 它可以将一个Runnable对象或者一个Message对象放入到MessageQueue中。
- 它可以处理MessageQueue中的Message或者Runnable对象。
当我们在Handler对象上调用post方法或者sendMessage方法时,Handler会将Runnable对象或者Message对象放入到与其关联的MessageQueue中。然后Looper会从MessageQueue中取出这些消息并交给对应的Handler处理。
我们可以通过重写Handler类的handleMessage方法来处理Message。当Looper从MessageQueue中取出一个Message时,它会调用该Message关联的Handler的handleMessage方法。
下面是一个简单的例子,展示了如何使用Handler发送和处理消息:
java
Handler handler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
// 处理消息
switch (msg.what) {
case 1:
// 处理消息1
break;
case 2:
// 处理消息2
break;
default:
super.handleMessage(msg);
break;
}
}
};
// 发送一个空消息
handler.sendEmptyMessage(1);
// 发送一个带数据的消息
Message message = Message.obtain();
message.what = 2;
message.obj = "Hello, Handler!";
handler.sendMessage(message);
1.3 Message类详解
在Handler消息机制中,Message类就像是一个信使,它携带着我们需要传递的信息。每个Message对象都包含了一个描述消息的what属性,以及可选的arg1、arg2、obj和Bundle等属性用于携带数据。
我们可以通过Message的静态方法obtain来获取一个Message对象。这个方法会从一个内部的对象池中取出一个Message对象,如果对象池为空,则会新建一个Message对象。这样做的好处是可以避免频繁地创建和销毁对象,提高性能。
当我们处理完一个Message后,可以调用其recycle方法将其放回到对象池中。
下面是一个简单的例子,展示了如何使用Message:
java
// 获取一个Message对象
Message message = Message.obtain();
// 设置消息描述
message.what = 1;
// 设置消息参数
message.arg1 = 123;
message.arg2 = 456;
// 设置消息携带的对象
message.obj = "Hello, Message!";
// 设置消息携带的Bundle数据
Bundle bundle = new Bundle();
bundle.putString("key", "value");
message.setData(bundle);
// 处理完后回收Message对象
message.recycle();
使用Message.obtain()方法获取Message对象,而不是直接创建新对象,主要有两个优势:
-
性能优化:Message内部维护了一个静态的对象池,当我们调用obtain()方法时,它首先会尝试从对象池中获取一个已经不再使用的Message对象,如果对象池为空,则会新建一个Message对象。当我们调用Message的recycle()方法时,该Message对象就会被放回到对象池中。这样可以避免频繁地创建和销毁对象,减少了内存分配和垃圾收集的开销,提高了性能。
-
资源复用:因为Message内部有一个成员变量next,它被用来在MessageQueue中链接各个Message形成链表结构。如果我们直接创建新的Message对象,这个next变量就没有被利用起来。
当然,使用obtain()方法也存在一些风险:
-
错误使用:如果在recycle()之后再次使用Message对象,就会产生错误。因为一旦你调用了recycle(),该Message可能已经被其他地方重新使用。
-
内存泄漏:如果你忘记调用recycle()方法,那么这个Message就无法被放回到对象池中,可能会导致内存泄漏。
因此,在使用obtain()和recycle()方法时需要格外小心,确保正确地管理Message的生命周期。
Android系统中创建和销毁一个message对象的开销可能如何计算,具体是多少开销
在Java或者Android中,对象的创建和销毁主要涉及到内存分配和垃圾回收。
-
内存分配:当我们创建一个新的对象时,JVM需要在堆内存中为这个对象分配空间。这个过程涉及到寻找足够大的连续内存空间、更新内存管理信息等操作,会消耗一定的CPU时间。具体的开销取决于JVM的实现和当前的内存状况,一般来说,对于小对象(如Message),这个开销相对较小。
-
垃圾回收:当一个对象不再被使用时,JVM会通过垃圾回收机制来回收其占用的内存。垃圾回收涉及到对象可达性分析、标记、清除等操作,会消耗一定的CPU时间,并可能导致应用暂停(Stop-The-World)。具体的开销取决于JVM的垃圾回收算法和当前的内存状况。
对于Message这样的小对象,如果频繁地创建和销毁,虽然单个对象的开销可能不大,但累积起来也可能成为性能瓶颈。而且频繁地触发垃圾回收还可能导致应用出现卡顿。
使用Message.obtain()方法可以避免这些开销,因为它复用了已经不再使用的Message对象,避免了新的内存分配和垃圾回收。
然而,具体节省了多少开销是很难精确计算的,因为它取决于许多因素,如JVM的实现、垃圾回收算法、当前内存状况、Message对象的数量和使用频率等。
所以,我也没算出来😅
1.4 Looper类详解
在Android的Handler消息机制中,Looper类扮演着非常重要的角色。每个线程只能有一个Looper对象,它会创建一个MessageQueue并循环地从中取出Message对象。
当我们在Handler对象上调用post方法或者sendMessage方法时,Handler会将Runnable对象或者Message对象放入到与其关联的MessageQueue中。然后Looper会从MessageQueue中取出这些消息并交给对应的Handler处理。
我们可以通过Looper类的prepare()方法来为当前线程创建一个Looper对象和一个MessageQueue。然后通过loop()方法启动消息循环。在Android应用程序中,主线程的Looper对象是在Application的onCreate()方法中自动创建和启动的。
下面是一个简单的例子,展示了如何使用Looper:
java
// 创建新线程
new Thread(new Runnable() {
@Override
public void run() {
// 为当前线程准备Looper
Looper.prepare();
// 创建Handler
Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
// 处理消息
}
};
// 启动消息循环
Looper.loop();
}
}).start();
1.5 MessageQueue类详解
在Android的Handler消息机制中,MessageQueue类是用来存储Message对象的。每个Looper对象都有一个与之关联的MessageQueue。
MessageQueue内部实际上是一个链表结构,用来存储Message对象。当我们在Handler对象上调用post方法或者sendMessage方法时,Handler会将Runnable对象或者Message对象放入到与其关联的MessageQueue中。
然后Looper会从MessageQueue中取出这些消息并交给对应的Handler处理。Looper在取出消息时会按照消息的时间顺序进行,确保消息能够按照发送的顺序被处理。
下面是一个简单的例子,展示了如何使用Message和MessageQueue:
java
// 创建新线程
new Thread(new Runnable() {
@Override
public void run() {
// 为当前线程准备Looper
Looper.prepare();
// 创建Handler
Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
// 处理消息
}
};
// 发送消息到MessageQueue
handler.sendEmptyMessage(1);
// 启动消息循环
Looper.loop();
}
}).start();
1.6 Handler消息机制实战
现在,让我们通过一个实际的例子来看一下如何在Android应用中使用Handler消息机制。
假设我们需要在子线程中执行一些耗时操作,然后将结果回传给主线程更新UI。这是一个非常常见的场景,例如网络请求、数据库查询等。
首先,我们需要创建一个Handler对象。这个Handler对象需要在主线程中创建,这样它就与主线程的Looper和MessageQueue关联了。
java
// 在主线程中创建Handler
final Handler handler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
// 在主线程中处理消息
// 这里可以更新UI
String result = (String) msg.obj;
textView.setText(result);
}
};
然后,在子线程中执行耗时操作,并通过Handler发送消息到主线程:
java
new Thread(new Runnable() {
@Override
public void run() {
// 执行耗时操作
String result = doTimeConsumingOperation();
// 将结果发送到主线程
Message message = Message.obtain();
message.obj = result;
handler.sendMessage(message);
}
}).start();
这样,当耗时操作完成后,我们就可以将结果通过Handler发送到主线程,并在主线程中更新UI。
1.7 Handler消息机制优化
在Android开发中,正确且高效地使用Handler消息机制是非常重要的。这里有一些优化建议:
-
避免内存泄漏:Handler是一个常见的内存泄漏来源。如果Handler是一个匿名内部类或者非静态内部类,那么它会持有外部类(通常是Activity或者Fragment)的引用。如果我们在Handler中发送了一个延迟消息,然后在消息处理前就退出了Activity,那么由于Handler还持有Activity的引用,导致Activity无法被垃圾回收,从而产生内存泄漏。解决方法是使用静态内部类加弱引用。
-
复用Message对象:频繁地创建和销毁对象会增加垃圾回收的负担,可能导致应用卡顿。我们可以通过Message.obtain()方法来复用Message对象。
-
及时移除无用的消息:如果你知道某个消息不再需要被处理,那么应该尽快调用removeCallbacksAndMessages()方法来移除它。否则,这个消息会一直存在于MessageQueue中,占用内存。
-
避免在主线程中执行耗时操作:Handler通常被用来在主线程中更新UI。但是我们应该避免在主线程中执行耗时操作,因为这会阻塞UI线程,导致应用卡顿。耗时操作应该在子线程中执行。
1.8 Handler消息机制应用场景
在Android开发中,Handler消息机制被广泛用于各种场景。以下是一些常见的应用场景:
-
在子线程中更新UI:在Android中,只有主线程可以更新UI。如果我们需要在子线程中执行耗时操作,然后将结果反馈给主线程更新UI,那么就需要使用Handler。
-
实现定时任务:我们可以使用Handler的postDelayed方法来延迟一段时间后发送一个Runnable对象或者Message对象。
-
实现周期性任务:我们可以在Runnable对象或者handleMessage方法中再次发送消息,从而实现周期性任务。
-
其他需要跨线程通信的场景:除了上述场景外,其他所有需要跨线程通信的场景都可以使用Handler。
Looper循环节奏
Looper是如何循环的,循环节奏是怎么样的,messagequeue实际存储了什么内容,他是如何找到正确的handler去处理消息的
-
Looper的循环机制:Looper类有一个loop()方法,这个方法中有一个无限循环。在这个循环中,Looper会从与之关联的MessageQueue中取出Message对象,并调用其target(即Handler)的dispatchMessage方法来处理这个消息。当MessageQueue为空时,Looper会阻塞在这里等待新的消息。
-
循环节奏:Looper的循环节奏取决于MessageQueue中消息的到达时间。每个Message都有一个when属性,表示这个消息应该在什么时候被处理。Looper会按照消息的when属性的顺序来处理消息。如果当前没有需要立即处理的消息(即所有消息的when属性都大于当前时间),那么Looper就会阻塞在这里等待。
-
MessageQueue存储内容:MessageQueue实际上是一个链表结构,用来存储Message对象。每个Message对象都包含了一些基本信息,如what、arg1、arg2等,以及一个指向Handler对象的引用(target),和一个指向下一个Message对象的引用(next)。
-
找到正确Handler处理消息:每个Message对象都有一个target属性,这个属性就是一个指向Handler对象的引用。当Looper从MessageQueue中取出一个Message时,它会调用这个Message关联的Handler(即target)的dispatchMessage方法来处理这个消息。
Message对象的所有属性和方法以及属性的详细介绍和使用
Message类主要包含以下属性:
int what
: 用户定义的消息代码,用于描述消息的类型。int arg1
和int arg2
: 附加的用户定义的消息数据。你可以使用这两个字段来传递简单的整数数据。Object obj
: 附加的用户定义的对象数据。你可以使用这个字段来传递一个对象。Messenger replyTo
: 如果这个Message是用来作为一个远程过程调用(RPC)的结果,那么replyTo字段会被设置为接收结果的Messenger。Bundle data
: 用于存储复杂类型数据。
Message类主要包含以下方法:
static Message obtain()
: 获取一个新的Message实例。首先会尝试从全局池中获取,如果池中没有可用实例,则会创建一个新的实例。void recycle()
: 将这个Message实例放回到全局池中。注意,在调用recycle后,这个Message实例不应再被访问。Bundle getData()
: 返回消息携带的数据,如果没有数据则返回null。void setData(Bundle data)
: 设置消息携带的数据。
MessageQueue类主要包含以下属性:
Message mMessages
: 链表的头结点,存储了队列中的第一个消息。boolean mQuitting
: 标识这个MessageQueue是否已经停止。
MessageQueue类主要包含以下方法:
boolean enqueueMessage(Message msg, long when)
: 将一个Message对象添加到队列中。when参数指定了这个消息应该在何时被处理。Message next()
: 从队列中取出一个消息用于处理。如果队列为空,则阻塞等待下一个消息。void quit()
: 停止这个MessageQueue,使其不再接收新的消息。
在实际使用中,我们通常不直接使用MessageQueue类。相反,我们会通过Handler和Looper来间接操作MessageQueue。Handler提供了一系列方法(如sendMessage、postRunnable等)来将消息添加到MessageQueue,而Looper则负责从MessageQueue中取出并处理消息。
如何实现延时处理消息
Message的when属性是如何被标记的又是如何触发looper的工作的
-
Message的when属性如何被标记:当我们通过Handler的sendMessageAtTime或者postDelayed方法发送一个消息时,会指定一个uptimeMillis参数,这个参数表示这个消息应该在何时被处理。这个uptimeMillis参数就会被赋值给Message的when属性。
-
如何触发Looper的工作:Looper通过调用MessageQueue的next方法来获取需要处理的下一个消息。在next方法内部,如果当前没有需要立即处理的消息(即所有消息的when属性都大于当前时间),那么Looper就会阻塞在这里等待。当有一个新的消息到达,且这个消息的when属性小于或等于当前时间时,next方法就会返回这个消息,然后Looper就会处理这个消息。
Looper唤醒节奏?
如果当前message的when还没到,那么looper会间隔多久检查一次
Looper并不是以固定的时间间隔去检查MessageQueue中的消息。实际上,当Looper调用MessageQueue的next方法时,如果当前没有需要立即处理的消息,那么Looper就会阻塞在这里等待。
当一个新的消息被添加到MessageQueue时,或者当一个延迟消息到达处理时间时,Looper就会被唤醒。这个过程是由操作系统底层来管理和调度的,对于开发者来说是透明的。
也就是说,Looper不需要周期性地检查MessageQueue中的消息。它只需要在有新消息到达时被唤醒即可。这种机制可以有效地节省CPU资源,提高应用性能。
Looper被谁唤醒?
一个延迟消息到达处理时间时,Looper就会被唤醒,是被谁唤醒的, 是如何实现的
当一个延迟消息到达处理时间时,Looper会被操作系统唤醒。这是通过操作系统的定时器机制实现的。
当我们使用Handler发送一个延迟消息时,会指定一个uptimeMillis参数,表示这个消息应该在何时被处理。这个时间信息会被传递给操作系统,操作系统会设置一个定时器,在指定的时间到达时唤醒Looper。
这个过程是由操作系统底层来管理和调度的,对于开发者来说是透明的。开发者只需要知道,当一个延迟消息到达处理时间时,Looper就会被唤醒并处理这个消息
enqueueMessage的代码实现
在Android源码中,Handler
类的enqueueMessage
方法的实现如下:
java
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;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
这段代码首先检查mQueue
(即这个Handler关联的MessageQueue)是否为null。如果为null,那么抛出一个运行时异常。
然后,调用enqueueMessage
方法将消息添加到队列中。在这个方法中,首先将msg.target
设置为这个Handler,然后根据Handler是否为异步来设置消息的异步标志。最后,调用queue.enqueueMessage
将消息添加到队列中。
MessageQueue里面enqueueMessage的实现
在Android源码中,MessageQueue
类的enqueueMessage
方法的实现如下:
java
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
synchronized (this) {
if (mQuitting) {
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
Log.w("MessageQueue", e.getMessage(), e);
msg.recycle();
return false;
}
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
// Inserted within the middle of the queue. Usually we don't have to wake
// up the event queue unless there is a barrier at the head of the queue
// and the message is the earliest asynchronous message in the queue.
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
这段代码首先检查消息是否有目标(即Handler),如果没有则抛出一个异常。然后检查这个消息是否已经在使用中,如果是则抛出一个异常。
接着,这段代码进入一个同步块,在这个同步块中,首先检查MessageQueue是否已经停止,如果是则抛出一个异常并回收这个消息。
然后,将这个消息标记为正在使用,并设置它的when属性。
最后,根据when属性将这个消息插入到队列中的适当位置。如果这个消息是队列中的第一个消息,或者它的when属性比队列中的第一个消息小,则将它插入到队列的头部。否则,将它插入到队列中的适当位置。
在插入消息后,如果需要唤醒事件队列,则调用nativeWake方法来唤醒它。
removeAllMessages的实现
在Android源码中,Handler
类的removeAllMessages
方法的实现如下:
java
public final void removeCallbacksAndMessages(Object token) {
mQueue.removeCallbacksAndMessages(this, token);
}
这段代码调用了与Handler关联的MessageQueue的removeCallbacksAndMessages
方法,移除了队列中所有与指定token关联的回调和消息。
MessageQueue
类的removeCallbacksAndMessages
方法实现如下:
java
public void removeCallbacksAndMessages(Handler h, Object object) {
synchronized (this) {
Message p = mMessages;
// Remove all messages at front.
while (p != null && p.target == h
&& (object == null || p.obj == object)) {
Message n = p.next;
mMessages = n;
p.recycleUnchecked();
p = n;
}
// Remove all messages after front.
while (p != null) {
Message n = p.next;
if (n != null) {
if (n.target == h && (object == null || n.obj == object)) {
Message nn = n.next;
n.recycleUnchecked();
p.next = nn;
continue;
}
}
p = n;
}
}
}
总结
用一句来总结handler是如何实现跨线程通信的
Handler通过将消息对象(Message)发送到与其关联的消息队列(MessageQueue),然后由目标线程中的Looper从消息队列中取出并处理这些消息,从而实现了跨线程通信。
