结合此前的内容,咱们知道,ActivityThread的main函数中,实际上是做了几件事情:

  1. 初始化Looper
  2. Looper开端循环
  3. messageQueue.next()回来或许堵塞
  4. 处理音讯

Android消息机制(三)Handler、Looper、MessageQueue原理探索

3 ~ 4之间是一个循环,一旦退出这个Looper的循环,那么意味着App也就退出了。

其中messageQueue.next()堵塞,堵塞的正是线程,准确的说应该是用户线程。当ActivityThread对应的主线程履行到这一行时,假如音讯行列中没有音讯,主线程就会被堵塞在此处,后续假如有新的音讯到来了,就会唤醒,持续走4的处理音讯的过程,然后又进入3开端堵塞或许处理下一个音讯。

明显,messageQueue.next()是IO,而且是堵塞式IO,它会卡住咱们程序的履行。

咱们知道,线程是处理机调度的基本单位,而可持续交互程序的本质,是运用一个循环不断地去读取音讯,然后处理音讯,换句话说,是一个线程在不断地去履行一个循环,读取音讯,处理音讯,

Looper也很朴实,它首要的责任便是完结这么个循环,然后不断地去取音讯,履行音讯,可是它却是咱们主线程的核心。咱们的App的运行时,无论是从体系仍是App里边,都会有连绵不断的音讯流入。

今天的内容,则首要聚焦于一次Looper将会产生什么:

Android消息机制(三)Handler、Looper、MessageQueue原理探索

1. Message: 音讯和行为

咱们知道,Looper会不断地进行循环,有则处理,无则堵塞在MessageQueue.next处。而Looper不断等候的内容,正是Message。

咱们能够设置一个日志监听,最直观地感受一下不断地被Looper取出处理的Message:

Looper.getMainLooper().setMessageLogging {
    println(it)
}

而Message包括下面的首要内容:

val message = Message.obtain()
    message.what
    message.arg1
    message.arg2
    message.obj
    message.callback

大致上能够分为两类首先是callback,即Message能够携带一个Runnable,一个可履行的单元,这也是runOnUiThread函数的完结办法。第二类则是运用一系列的标识:what、arg1、arg2等等来发送一些预定的行为。

什么是预定的行为?

例如你能够在ActivityThread.java中,看到class H,中的handleMessage就依据what字段的不同,有着各式各样的回调处理:

public void handleMessage(Message msg) {
    switch (msg.what) {
        case BIND_APPLICATION:
            // ......
            break;
        case EXIT_APPLICATION:
            // ......
            break;
        case RECEIVER:
            // ......
}

说白了,便是写死在ActivityThread.java中的回调,这些内容都是体系预置的,假如你经过绑定在主线程Looper上的Handler发送一个what = EXIT_APPLICATION的Message,程序是会被直接退出的,当然,你需求用一点手法才干拿到这样的Handler或许调用对应的办法。

比较早的版别是能够直接经过反射拿到Activity基类的mHandler变量的,可是比较新的版别现已被打上了@UnsupportedAppUsage标记,经过反射一般是拿不到该方针的。

总而言之,Message能够传递两种内容,音讯(what/arg1/arg2/obj),也能够运用callback变量传递一段代码Runnable。尽管不存在二者都有的情况,可是逻辑上是Runnable会优先被履行的。

此外Message经过target属性来标记方针Handler

并不是一切Message都会被压在行列中履行,也不会一切的Message都会切线程,详细的细节取决于你对Handler的运用,详见#5。

2.MessageQueue.next()是怎么取音讯的

上面咱们提到了,程序可能会堵塞在messageQueue.next()处,那么Looper是怎么堵塞而且获取Message的呢?

Message next() {
    // ……
    for (;;) {
        if (nextPollTimeoutMillis != 0) {
            Binder.flushPendingCommands();
        }
        nativePollOnce(ptr, nextPollTimeoutMillis);
        synchronized (this) {
           // ……
        }
        // run idlehandler
    }
}

咱们对上述的代码应该比较熟悉,又是一个循环,然后调用nativePollOnce(ptr,nextPollTimeoutMillis(),咱们能够猜测堵塞和它相关,而上面的Binder.flushPendingCommands(),咱们能够看看它的注释:

将当时线程中挂起的任何Binder命令刷新到内核驱动程序。这关于在履行可能会堵塞很长时刻的操作之前进行调用十分有用,以保证已开释任何挂起的方针引证,以避免进程对方针的占用时刻超过所需的时刻。

关键在于,在这之后的代码可能会产生长时刻的堵塞。 总归,想要知其所以然,咱们需求看看nativePollOnce的源码究竟做了些什么,咱们快速定位到源码的位置:

android_os_MessageQueue.cpp – OpenGrok cross reference for /frameworks/…

static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj,189          jlong ptr, jint timeoutMillis)
{
    NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
    nativeMessageQueue->pollOnce(env, obj, timeoutMillis);
}

然后是pollOnce

void NativeMessageQueue::pollOnce(JNIEnv* env, jobject pollObj, int timeoutMillis) {108      mPollEnv = env;
      mPollObj = pollObj;
 mLooper->pollOnce(timeoutMillis); 
      mPollObj = NULL;
      mPollEnv = NULL;
      if (mExceptionObj) {
          env->Throw(mExceptionObj);
          env->DeleteLocalRef(mExceptionObj);
          mExceptionObj = NULL;
      }
}

然后又落到Looper的pollOnce之上,可是差异在于此时的mLooper不再是Java方针了,而是C++方针:

int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {174      int result = 0;
    for (;;) {
        // 省略号
        result = pollInner(timeoutMillis); 
    }
}

你会发现, 底层的代码,也是循环:

Looper.cpp – OpenGrok cross reference for /system/core…

int Looper::pollInner(int timeoutMillis) {
    // ……
    int eventCount = epoll_wait(mEpollFd.get(), eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
    // ……
}

所以,native层的Handler机制,实际上是运用epoll来进行驱动的。

你并不必十分详细地知道epoll的细节,你暂时只需求知道epoll能够堵塞IO,而且完结唤醒即可,正如手册中说的那样:event notification facility,即事情告诉设备。

epoll有一个参数是timeoutMillis,假如有数据则回来;没有数据则sleep,假如超过这个时刻没有音讯,此时也会回来,所以在倒数第二层的Looper::pollOnece中,依然是运用了一个循环,用于轮询,也便是:

for(;;){
  // 有音讯->回来;
  // 没有音讯 -> 睡眠一段时刻,持续循环
}

所以,你会发现只要和IO相关的当地,基本上都离不开循环。假如循环是在用户空间,例如ActivityThread中的代码,一般是经过体系调用,陷入内核;内核也是运用循环来读取音讯,可是内核代码一般有更高的权限,它能够去请求调度的履行,让当时等候IO的程序让出CPU,在IO完结之后,再去响应等等,此前用户空间的代码在内核空间完结处理之后就会完结堵塞。

3.MessageQueue的next()和enqueue()

假如你仔细看了前面的内容,你会发现,MessageQueue.next的堵塞是调用native,运用epoll机制完结的音讯获取。可是假如你简单地看一看MessageQueue的enqueueMessage,即增加音讯的代码,你会发现,增加事情的代码几乎全部都产生在Java层,可是增加完了之后,依据needWake标记位去调用nativeWake。也便是说,音讯是在Java层存储的,可是堵塞 + 唤醒的机制是运用Native层的。 之前咱们现已看过了enqueue,现在来看看nativeWake做了什么:

void Looper::wake() {
    ALOGD("%p ~ wake", this);
    uint64_t inc = 1;
    ssize_t nWrite = TEMP_FAILURE_RETRY(write(mWakeEventFd. get (), &inc, sizeof(uint64_t)) );
    if (nWrite != sizeof(uint64_t)) {
        if (errno != EAGAIN) {
            LOG_ALWAYS_FATAL("Could not write wake signal to fd %d (returned %zd): %s",
                 mWakeEventFd.get(), nWrite, strerror(errno));
        }
    }
}

便是往需求wake的fd中写入内容,然后唤醒对应的next,而在enqueue中会等候epoll_wait,epoll_wait回来时,堵塞完结,则持续向下履行,也便是说在IO完结之后,终究会依据不同的eventCount和eventItem去调用awoken办法,

int Looper::pollInner(int timeoutMillis) {#if DEBUG_POLL_AND_WAKEALOGD("%p ~ pollOnce - waiting: timeoutMillis=%d", this, timeoutMillis);
    // ……
    int eventCount = epoll_wait(mEpollFd.get(), eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
    // ……
    for (int i = 0; i < eventCount; i++) {
        int fd = eventItems[i].data.fd;
        uint32_t epollEvents = eventItems[i].events;
        if (fd == mWakeEventFd.get()) {
            if (epollEvents & EPOLLIN) {
                awoken(); 
            } else {
                ALOGW("Ignoring unexpected epoll events 0x%x on wake event fd.", epollEvents);
            }
        } else {
            // …… 
        }
    // ……
    return result;
}

而awoken的完结:

void Looper::awoken() {
    uint64_t counter;
    TEMP_FAILURE_RETRY(read(mWakeEventFd.get(), &counter, sizeof(uint64_t)));
}

明显,MessageQueue的休眠 + 唤醒便是一组搭配上epoll机制进行唤醒的,读空堵塞 + 写后唤醒,真实的事情是从上层Java代码里下发的,在Java中的MessageQueue里边进行enqueue,写完后调用native进行唤醒正在休眠的线程。

当然,当Looper对应的MessageQueue中没有音讯导致堵塞了才需求唤醒,假如此刻的MessageQueue特别忙压根没时刻歇息也没必要去唤醒。

读到这儿,你应该对MessageQueue和Looper有了新的的知道,无论是取音讯时会在epoll机制的效果下堵塞,可是一旦有生产者向音讯行列中发送音讯,就会经过native层,唤醒被堵塞的线程。

Android消息机制(三)Handler、Looper、MessageQueue原理探索

4. 发送音讯的句柄:Handler

所谓的句柄,便是一个引证,更浅显地来说,便是一个“抓手”,让咱们随时能够拿到发送音讯的这么一个结构。

Handler的运用无非便是以下几个过程:

  1. 声明Handler,最重要的是设置方针Looper,也便是确认方针循环,终究的目的是确认经过本Handler发送的音讯一切履行的方针线程

  2. 传递Handler引证。

  3. 运用Handler引证,调用post办法发送音讯。

代码中,散落在遍地的Handler绝大部分都指向主线程,比方Activity基类是自带一个Handler的,假如Handler的结构参数传空,会默认调用线程的静态办法Looper.myLoop获取Looper实例,终究便是一个指向Main线程Looper的Handler:

@UnsupportedAppUsage
final Handler mHandler = new Handler();

Activity中的runOnUiThread便借助了这个Handler:

public final void runOnUiThread(Runnable action) {
    if (Thread.currentThread() != mUiThread) {
        mHandler.post(action);
    } else {
        action.run();
    }
}

它帮咱们将一个Handler抛向UI线程(主线程)履行,实质是抛向主线程的Looper,终究会在Looper循环中不断地将音讯取出,之后履行或许压入行列。

关于线程来说,Looper也只能有一个,MessageQueue和线程是锁死的,只能1对1,而Looper和线程也是锁死的,所以Looper在初始化的时候就生成了自己的MessageQueue,MessageQueue实际上是Looper实例的一个变量。

详细是怎么做到和线程绑定的,能够看看ThreadLocal相关的内容。在这儿你只需求知道,经过Looper.myLooper()取出的必定是调用Looper.myLooper()对应的当时线程的Looper即可。

明显,咱们经过Handler能够发送Message,可是Handler不光要管发送,还要管处理。前面提到了,Message的分类大致上能够分为两类:1是数据类,2是Runnable类。

  • 数据类首要传递一些数据,交给Handler的handleMessage回调办法去处理;

  • Runnable则首要能够传递一些动态的代码,让其在Handler指定的线程处理;

4.1 dispatchMessage办法:当即履行(同步履行)

二者运用的办法也不同,假如你运用Handler.dispatchMessage来派发一个办法,那么处理的先后顺序便是:

  1. msg的回调,即当即履行Runnable。
  2. 当即履行经过代码动态设置Handler的Callback。UnsupportedAppUsage
  3. 当即履行handleMessage的处理计划;

留意,经过dispatchMessage传递的音讯是顺序履行的,将不会被压入MessageQueue行列中履行,不经过MessageQueue、Looper这一套流程就意味着不会被方针线程「捞起来」履行,所以dispatchMessage办法都是在调用线程中履行的,并不会切换线程。真实切换线程的机遇在于音讯进入行列之后,被方针线程的Looper取出这个动作,以完结工作线程的切换。换言之,假如希望切线程你必须运用post办法来发送Message。

4.2 post办法:压入行列履行(异步履行)

只要post系列的办法才会压入行列履行,可是终究Looper从行列取出音讯之后,依然调用的是dispatchMessage()办法去当即履行,post系列的办法自带必定的推迟(不多,几毫秒到几百毫秒都有可能,视主线程的任务数量、处理时长而定)。

Android消息机制(三)Handler、Looper、MessageQueue原理探索

  1. 已然直接调用dispatchMessage()是同步的,没有推迟,那么假如子线程调用主线程的Handler.dispatchMessage的时候,主线程现已有一个Message在履行了怎么办?

上面提到了,dispatchMessage并不会切线程,子线程调用dispatchMessage,那么无论是Message.callback仍是Handler的handleMessage办法,都仍是在子线程履行的,并不会影响主线程的履行。

  1. **为什么Handler不直接去处理,而是要压到一个行列里边?

Handler有多个,可是线程只要一个。

以主线程为例,对应到主线程的Handler实例也是十分多的,可是一个线程同一时刻只能处理一段代码,假如多个Message宣布的Runnable产生并发请求履行,一个线程是无法一起处理的,必定要这么个结构来做音讯的存储,按顺序履行。所以Handler的发送音讯(postMessage)和履行音讯(callback)是分开的。

而线程对应的Looper、MessageQueue也只要一个。

这就会出现MessageQueue中有十分多Message,可是可能各个MessageQueue对应的Handler都不一样,可是他们之间一般绝大多数时候都是有序的,对应的主线程就在不断地履行Handler的回调办法,无一例外的,一切的代码终究都是在主线程上履行的。

Android消息机制(三)Handler、Looper、MessageQueue原理探索
假如你熟悉一些基于事情行列的单线程编程言语,比方咱们熟悉的Flutter、JavaScript等等。它们正是这样完结所谓的异步操作的,也被叫做伪异步,例如Flutter,它异步的本质上是往事情行列的结尾加入一个新的Future,尽管JavaScript、Flutter这类的言语只提供了一个线程(Flutter中对应的ISolate)给咱们操作,可是又不能影响绘制,依赖的便是这样的伪异步操作。两个伪异步操作之间依然有先后顺序,并不是彻底并行的。

5.实践:运用ActivityThread下的Handler退出咱们的App

首先,咱们需求切到较低的Android版别,这儿选择的是API23,即6.0,这样咱们能够经过反射拿到被@UnsupportedAppUsage注解标记的变量,可是高版别上是行不通的,所以这并不能当成一种惯例的手法在开发中运用。

  • 需求拿到ActivityThread下的Handler方针

可是ActivityThread的引证并不好获取,可是咱们查找ActivityThread方针的创立时,咱们能够看到:

ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);
if (sMainThreadHandler == null) {
    sMainThreadHandler = thread.getHandler();
}
@UnsupportedAppUsage
public Handler getHandler() {
    return mH;
}

即体系预置的这个Handler被挂在sMainThreadHandler之下,而thread.getHandler()中,对应的正是在ActivityThread方针中创立的主线程Handler方针:final H mH = new H();

  • 获取对应的MessageQueue

第一步中获取Handler的目的是此前提到的,运用Handler的第二步:获取Handler引证,经过引证发送Message,然后在Handler完结handleMessage的当地处理回调。

其实按理说咱们只需求这样就能够完结这个实践了:

val message = Message.obtain()
message.what = 111; // public static final int EXIT_APPLICATION        = 111;
mInnerHandler.dispatchMessage(message)

可是直接跑起来是直接会抛出反常的,由于主线程是不答应被退出的:

Main thread not allowed to quit.

咱们盯梢H类中EXIT_APPLICATION的完结,咱们能够发现这个约束是被MessageQueue的mQuitAllowed变量操控,所以咱们还需求反射修改MessageQueue的mQuitAllowed变量值为true。

由于线程、MessageQueue、Looper的关系是一一对应的,所以咱们能够直接运用上面获取到的Handler拿到Looper再拿到MessageQuque,也能够直接运用Looper.getMainLooper()获取主线程的Looper然后再去拿MessageQueue,结果都是同一个MessageQueue方针,然后再反射调用,修改mQuitAllowed的值为true即可:

val mMessageQueue: MessageQueue =
    mInnerHandler.looper.fastGetDeclaredFieldDynamic("mQueue")
        ?: return@setOnClickListener
// 反射设置主线程MessageQueue答应退出
mMessageQueue.fastSetDeclaredFieldsDynamic("mQuitAllowed", true)
// 或许
Looper.getMainLooper().fastGetDeclaredFieldDynamic<MessageQueue>("mQueue")
    ?.fastSetDeclaredFieldsDynamic("mQuitAllowed", true)

其中反射相关的代码需求自己去完结,完整代码大概长这样:

// 获取activityThread中,静态的sMainThreadHandler实例
val activityThread = classLoader.loadClass("android.app.ActivityThread")
val mInnerHandler: Handler =
    activityThread.fastGetDeclaredFieldStatic("sMainThreadHandler")
        ?: return@setOnClickListener
// 反射获取InnerHandler对应的Looper的MessageQueue
val mMessageQueue: MessageQueue =
    mInnerHandler.looper.fastGetDeclaredFieldDynamic("mQueue")
        ?: return@setOnClickListener
// 反射设置主线程MessageQueue答应退出
mMessageQueue.fastSetDeclaredFieldsDynamic("mQuitAllowed", true)
val message = Message.obtain()
message.what = 111; // public static final int EXIT_APPLICATION        = 111;
mInnerHandler.dispatchMessage(message)

履行上述代码,实践是成功了,尽管最终程序仍是溃散了,可是是由于主线程循环被退出了,最终抛出了一个反常:

Main thread loop unexpectedly exited

由于ActivityThread.java中main函数便是这样写的,Looper.loop()完结之后自然而然就走到throw new Execption傍边。

public static void main(String[] args) {
    // 省略号
    Looper.prepareMainLooper();
    // 省略号
    Looper.loop(); 
    // 省略号
    throw new RuntimeException("Main thread loop unexpectedly exited");
}

假如你在主线程的反常兜底里边将这个反常兜住会产生什么?

Thread.currentThread().setUncaughtExceptionHandler { t, e ->
    e.printStackTrace()
}

这样一来程序不会溃散退出,可是界面就卡死在那里了,UI尽管还在,可是现已Debug中现已无法找到对应的程序了:

Android消息机制(三)Handler、Looper、MessageQueue原理探索

假如此时你再去看函数的堆栈,你会发现此前的两个main函数现已消失了,只剩下catch反常的栈帧了。

Android消息机制(三)Handler、Looper、MessageQueue原理探索

Android消息机制(三)Handler、Looper、MessageQueue原理探索