[Android][Handler]探索Android的心脏--一文搞懂Android Handler消息机制

探索Android的心脏--一文搞懂Android Handler消息机制

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主要有两种用途:

  1. 它可以将一个Runnable对象或者一个Message对象放入到MessageQueue中。
  2. 它可以处理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对象,而不是直接创建新对象,主要有两个优势:

  1. 性能优化:Message内部维护了一个静态的对象池,当我们调用obtain()方法时,它首先会尝试从对象池中获取一个已经不再使用的Message对象,如果对象池为空,则会新建一个Message对象。当我们调用Message的recycle()方法时,该Message对象就会被放回到对象池中。这样可以避免频繁地创建和销毁对象,减少了内存分配和垃圾收集的开销,提高了性能。

  2. 资源复用:因为Message内部有一个成员变量next,它被用来在MessageQueue中链接各个Message形成链表结构。如果我们直接创建新的Message对象,这个next变量就没有被利用起来。

当然,使用obtain()方法也存在一些风险:

  1. 错误使用:如果在recycle()之后再次使用Message对象,就会产生错误。因为一旦你调用了recycle(),该Message可能已经被其他地方重新使用。

  2. 内存泄漏:如果你忘记调用recycle()方法,那么这个Message就无法被放回到对象池中,可能会导致内存泄漏。

因此,在使用obtain()和recycle()方法时需要格外小心,确保正确地管理Message的生命周期。

Android系统中创建和销毁一个message对象的开销可能如何计算,具体是多少开销

在Java或者Android中,对象的创建和销毁主要涉及到内存分配和垃圾回收。

  1. 内存分配:当我们创建一个新的对象时,JVM需要在堆内存中为这个对象分配空间。这个过程涉及到寻找足够大的连续内存空间、更新内存管理信息等操作,会消耗一定的CPU时间。具体的开销取决于JVM的实现和当前的内存状况,一般来说,对于小对象(如Message),这个开销相对较小。

  2. 垃圾回收:当一个对象不再被使用时,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消息机制是非常重要的。这里有一些优化建议:

  1. 避免内存泄漏:Handler是一个常见的内存泄漏来源。如果Handler是一个匿名内部类或者非静态内部类,那么它会持有外部类(通常是Activity或者Fragment)的引用。如果我们在Handler中发送了一个延迟消息,然后在消息处理前就退出了Activity,那么由于Handler还持有Activity的引用,导致Activity无法被垃圾回收,从而产生内存泄漏。解决方法是使用静态内部类加弱引用。

  2. 复用Message对象:频繁地创建和销毁对象会增加垃圾回收的负担,可能导致应用卡顿。我们可以通过Message.obtain()方法来复用Message对象。

  3. 及时移除无用的消息:如果你知道某个消息不再需要被处理,那么应该尽快调用removeCallbacksAndMessages()方法来移除它。否则,这个消息会一直存在于MessageQueue中,占用内存。

  4. 避免在主线程中执行耗时操作:Handler通常被用来在主线程中更新UI。但是我们应该避免在主线程中执行耗时操作,因为这会阻塞UI线程,导致应用卡顿。耗时操作应该在子线程中执行。

1.8 Handler消息机制应用场景

在Android开发中,Handler消息机制被广泛用于各种场景。以下是一些常见的应用场景:

  1. 在子线程中更新UI:在Android中,只有主线程可以更新UI。如果我们需要在子线程中执行耗时操作,然后将结果反馈给主线程更新UI,那么就需要使用Handler。

  2. 实现定时任务:我们可以使用Handler的postDelayed方法来延迟一段时间后发送一个Runnable对象或者Message对象。

  3. 实现周期性任务:我们可以在Runnable对象或者handleMessage方法中再次发送消息,从而实现周期性任务。

  4. 其他需要跨线程通信的场景:除了上述场景外,其他所有需要跨线程通信的场景都可以使用Handler。

Looper循环节奏

Looper是如何循环的,循环节奏是怎么样的,messagequeue实际存储了什么内容,他是如何找到正确的handler去处理消息的

  1. Looper的循环机制:Looper类有一个loop()方法,这个方法中有一个无限循环。在这个循环中,Looper会从与之关联的MessageQueue中取出Message对象,并调用其target(即Handler)的dispatchMessage方法来处理这个消息。当MessageQueue为空时,Looper会阻塞在这里等待新的消息。

  2. 循环节奏:Looper的循环节奏取决于MessageQueue中消息的到达时间。每个Message都有一个when属性,表示这个消息应该在什么时候被处理。Looper会按照消息的when属性的顺序来处理消息。如果当前没有需要立即处理的消息(即所有消息的when属性都大于当前时间),那么Looper就会阻塞在这里等待。

  3. MessageQueue存储内容:MessageQueue实际上是一个链表结构,用来存储Message对象。每个Message对象都包含了一些基本信息,如what、arg1、arg2等,以及一个指向Handler对象的引用(target),和一个指向下一个Message对象的引用(next)。

  4. 找到正确Handler处理消息:每个Message对象都有一个target属性,这个属性就是一个指向Handler对象的引用。当Looper从MessageQueue中取出一个Message时,它会调用这个Message关联的Handler(即target)的dispatchMessage方法来处理这个消息。

Message对象的所有属性和方法以及属性的详细介绍和使用

Message类主要包含以下属性:

  1. int what: 用户定义的消息代码,用于描述消息的类型。
  2. int arg1int arg2: 附加的用户定义的消息数据。你可以使用这两个字段来传递简单的整数数据。
  3. Object obj: 附加的用户定义的对象数据。你可以使用这个字段来传递一个对象。
  4. Messenger replyTo: 如果这个Message是用来作为一个远程过程调用(RPC)的结果,那么replyTo字段会被设置为接收结果的Messenger。
  5. Bundle data: 用于存储复杂类型数据。

Message类主要包含以下方法:

  1. static Message obtain(): 获取一个新的Message实例。首先会尝试从全局池中获取,如果池中没有可用实例,则会创建一个新的实例。
  2. void recycle(): 将这个Message实例放回到全局池中。注意,在调用recycle后,这个Message实例不应再被访问。
  3. Bundle getData(): 返回消息携带的数据,如果没有数据则返回null。
  4. void setData(Bundle data): 设置消息携带的数据。

MessageQueue类主要包含以下属性:

  1. Message mMessages: 链表的头结点,存储了队列中的第一个消息。
  2. boolean mQuitting: 标识这个MessageQueue是否已经停止。

MessageQueue类主要包含以下方法:

  1. boolean enqueueMessage(Message msg, long when): 将一个Message对象添加到队列中。when参数指定了这个消息应该在何时被处理。
  2. Message next(): 从队列中取出一个消息用于处理。如果队列为空,则阻塞等待下一个消息。
  3. void quit(): 停止这个MessageQueue,使其不再接收新的消息。

在实际使用中,我们通常不直接使用MessageQueue类。相反,我们会通过Handler和Looper来间接操作MessageQueue。Handler提供了一系列方法(如sendMessage、postRunnable等)来将消息添加到MessageQueue,而Looper则负责从MessageQueue中取出并处理消息。

如何实现延时处理消息

Message的when属性是如何被标记的又是如何触发looper的工作的

  1. Message的when属性如何被标记:当我们通过Handler的sendMessageAtTime或者postDelayed方法发送一个消息时,会指定一个uptimeMillis参数,这个参数表示这个消息应该在何时被处理。这个uptimeMillis参数就会被赋值给Message的when属性。

  2. 如何触发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从消息队列中取出并处理这些消息,从而实现了跨线程通信。

相关推荐
掘金-我是哪吒14 分钟前
分布式微服务系统架构第152集:JavaPlus技术文档平台日更
分布式·微服务·云原生·架构·系统架构
雨白2 小时前
Android 音视频播放:MediaPlayer 与 VideoView
android
Harry技术2 小时前
Fragment 和 AppCompatActivity 两个核心组件设计的目的和使用场景对比
android·android studio
Renounce2 小时前
【Android】四大组件Activity
android
风铃喵游2 小时前
平地起高楼: 环境搭建
前端·架构
森焱森2 小时前
驱动开发,队列,环形缓冲区:以GD32 CAN 消息处理为例
c语言·单片机·算法·架构
Wgllss3 小时前
Kotlin + Flow 实现责任链模式的4种案例
android·架构·android jetpack
Digitally3 小时前
如何通过 7 种有线或无线方式将视频从 PC 传输到 Android
android
Carpoor奇3 小时前
Mybatis之Integer类型字段为0,入库为null
android·mybatis
草明4 小时前
解决: React Native android webview 空白页
android·react native·react.js