探究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发送和处理音讯:

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:

// 获取一个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:

// 创立新线程
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:

// 创立新线程
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相关了。

// 在主线程中创立Handler
final Handler handler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
// 在主线程中处理音讯
// 这里能够更新UI
String result = (String) msg.obj;
textView.setText(result);
}
};

然后,在子线程中履行耗时操作,并经过Handler发送音讯到主线程:

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办法的完成如下:

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办法的完成如下:

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办法的完成如下:

public final void removeCallbacksAndMessages(Object token) {
mQueue.removeCallbacksAndMessages(this, token);
}

这段代码调用了与Handler相关的MessageQueue的removeCallbacksAndMessages办法,移除了行列中一切与指定token相关的回谐和音讯。

MessageQueue类的removeCallbacksAndMessages办法完成如下:

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从音讯行列中取出并处理这些音讯,然后完成了跨线程通讯。

[Android][Handler]探究Android的心脏--一文搞懂Android Handler音讯机制