Android HandlerThread、Looper、MessageQueue 源码分析

Android HandlerThread、Looper、MessageQueue 源码分析

简介

Android 开发中,大家应该对 HandlerThread 有一定了解。顾名思义,HandlerThreadThread 的一个子类。与普通的 Thread 不同,Thread 通常一次只能执行一个后台任务,如果需要执行多个任务,就必须创建多个线程,这样容易导致资源管理复杂,甚至可能出现内存泄漏等问题。而 HandlerThread 有一个显著的特点,它能够串行地执行多个任务。每个任务通过消息的形式排队执行,线程池中的任务按顺序依次处理,无需手动管理线程的生命周期或调度过程。我们只需通过 Handler 将任务发送到 HandlerThread 中,它会自动按顺序执行,极大简化了开发过程。使用 HandlerThread 的最大优势是:它是单线程的,因此不需要担心线程安全问题。实际上,Android 源码中也有许多地方使用了 HandlerThread,它在设计思想上值得我们学习和借鉴。接下来,我们将通过源码进一步了解它的实现原理。

源码分析

HandlerThread内部持有一个Looper,Looper中持有一个消息队列MessageQueue

Looper

在了解HandlerThread前,我们有必要先认识一下Looper,Looper 是一个负责管理消息队列(MessageQueue)并循环处理其中消息的类。简单来说,Looper 通过不断地从消息队列中取出消息并处理它们,来实现线程的消息处理机制。每个线程都有自己的消息队列和 Looper,通过 Looper,线程能够不断地处理任务直到任务队列为空。接下来我们会涉及到它以下几个核心接口。

  • prepare() 是一个静态方法,用来构建一个Looper对象,quitAllowed表示这个Looper是否支持退出,默认是支持,像Android 主线程的Looper是不支持退出的
java 复制代码
    public static void prepare() {
        prepare(true);
    }
  • loopOnce 静态方法,顾名思义,循环一次,它的作用是从当前消息队列MessageQueue中取一条符合条件的消息进行分发操作
java 复制代码
private static boolean loopOnce(final Looper me,
            final long ident, final int thresholdOverride) {
    Message msg = me.mQueue.next(); // 取一条消息
    if (msg == null) {
        //正常没消息,队列会堵塞,不会返回null,所以这里是null肯定是已经要结束了,这里返回false 退出looper
        return false;
    }
    //分发消息代码逻辑省略
            }
  • loop() 静态方法,启动循环,在循环中不断调用loopOnce来处理消息
java 复制代码
//关键代码
  public static void loop() {
    final Looper me = myLooper();
//...
      for (;;) {
          if (!loopOnce(me, ident, thresholdOverride)) {
              //返回false 意味着循环结束
              return;
          }
      }

}
  • quit()quitSafely() 退出looper,前者是立即退出,后者是处理完当前队列中所有消息后退出,最终是调用消息队列MessageQueue对应的退出方法
java 复制代码
    public void quit() {
    mQueue.quit(false);
}

public void quitSafely() {
    mQueue.quit(true);
}

HandlerThread

  • 既然它是一个线程,那可以先从run方法入手:
java 复制代码
    @Override
    public void run() {
        mTid = Process.myTid();
        Looper.prepare();
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        Looper.loop();
        mTid = -1;
    }

可以看出,线程跑起来后,先初始化了一个Looper,然后启动死循环,HandlerThread在这里充当的作用是在子线程中开启死循环接受和分发消息

这里有个地方比较有意思,在mLooper = Looper.myLooper();后面,它调用了notifyAll(),它起到了什么作用呢?

  • getLooper(),我们应该知道要把任务提交给HandlerThread执行,需要借助Handler,但是Handler的构造参数是需要传入一个Looper对象,所以,这里对外公开了获取Looper的接口
java 复制代码
public Looper getLooper() {
        if (!isAlive()) {
            return null;
        }

        boolean wasInterrupted = false;

        // If the thread has been started, wait until the looper has been created.
        synchronized (this) {
            while (isAlive() && mLooper == null) {
                try {
                    wait();
                } catch (InterruptedException e) {
                    wasInterrupted = true;
                }
            }
        }

        /*
         * We may need to restore the thread's interrupted flag, because it may
         * have been cleared above since we eat InterruptedExceptions
         */
        if (wasInterrupted) {
            Thread.currentThread().interrupt();
        }

        return mLooper;
    }

这里是直接返回了在run中初始化的mLooper,但是呢,在非当前线程获取mLooper对象,就会引发线程安全问题,可能mLooper还没被初始化就调用了getLooper(),这样就有可能返回一个空的数据了,所以官方在这里做了while循环,并且使用了wait()堵塞,等待上面run初始化完成后再notifyAll()这里

  • getThreadHandler() 就是公开给外面向当前HandlerThread插入消息的接口,内部维护着一个Handler对象
java 复制代码
    @NonNull
    public Handler getThreadHandler() {
        if (mHandler == null) {
            mHandler = new Handler(getLooper());
        }
        return mHandler;
    }
  • quit()quitSafely() 同样,HandlerThread也有退出的方法,其实现也是调用looper对应的函数退出

MessageQueue

由于关于消息分发逻辑在其他地方讲过,这里只要分析退出队列的逻辑。

它实现了退出和安全退出的方法,这两个操作有什么区别呢,请看源码

  • next() looper每调用一次loopOnce,内部就会调用messageQueue获取一条消息
java 复制代码
Message next() {
    //只放关键代码

    for (; ; ) {
        //堵塞,等待消息或者时机合适或主动唤醒
        nativePollOnce(ptr, nextPollTimeoutMillis);
        //...
        //符合分发条件的 返回这条消息给looper
        if (msg != null) {
            //...
            return msg;
        } else {
            // No more messages.
            nextPollTimeoutMillis = -1;
        }
        //...
        //退出状态 则释放队列,结束循环
        if (mQuitting) {
            dispose();
            return null;
        }

    }
}
  • quit()实际逻辑由下面两个removeAllFutureMessagesLocked和removeAllMessagesLocked函数执行
java 复制代码
void quit(boolean safe) {
    if (!mQuitAllowed) {
        throw new IllegalStateException("Main thread not allowed to quit.");
    }

    synchronized (this) {
        if (mQuitting) {//如果已经给队列设置了退出信号,下面的逻辑就不用走了
            return;
        }
        mQuitting = true;//标记当前队列处于退出状态,此时不再接受新的消息

        if (safe) {//安全退出
            removeAllFutureMessagesLocked();
        } else {//立即退出
            removeAllMessagesLocked();
        }

        //把队列的消息标记完成后,唤醒上面next的堵塞位置nativePollOnce,执行剩下的退出逻辑
        nativeWake(mPtr);
    }
}
//移除所有消息
private void removeAllMessagesLocked() {
    //mMessages 在这里是队列的头
    Message p = mMessages;
    while (p != null) {//从头开始,把所有消息标记为回收状态
        Message n = p.next;
        p.recycleUnchecked();
        p = n;
    }
    mMessages = null;//队头消息置空,next()执行时会判断这个,为空直接退出队列
}

//移除所有未来消息,退出这一瞬间之前提交的消息保留继续执行
private void removeAllFutureMessagesLocked() {
    final long now = SystemClock.uptimeMillis();
    Message p = mMessages;
    if (p != null) {
        if (p.when > now) {//队头的时间比当前新,说明全是后面新加的,全部回收掉
            removeAllMessagesLocked();
        } else {//以下为从消息队列中找到退出那一瞬间时间一样的分割点,把分割点前的消息与队列断开链接
            Message n;
            for (;;) {
                n = p.next;
                if (n == null) {
                    return;
                }
                if (n.when > now) {
                    break;
                }
                p = n;
            }
            p.next = null;//断开链接,这里把队列从分割点切断
            do {
                p = n;
                n = p.next;
                p.recycleUnchecked();//把所有未来消息标记为回收状态
            } while (n != null);
        }
    }
}

从上面可以看出,安全退出时,队列会把所有未来消息移除掉,并且不再接受新的消息,队列中剩下的消息会继续被looper取,一直到取完为止,然后结束队列退出循环,而普通退出就会把队列中所有消息移除,然后紧接着结束队列退出循环

quit和quitSafely 应用场景有哪些呢

;//把所有未来消息标记为回收状态

} while (n != null);

}

}

}

复制代码
从上面可以看出,安全退出时,队列会把所有未来消息移除掉,并且不再接受新的消息,队列中剩下的消息会继续被looper取,一直到取完为止,然后结束队列退出循环,而普通退出就会把队列中所有消息移除,然后紧接着结束队列退出循环

### quit和quitSafely 应用场景有哪些呢
举一个简单的例子,在Android 使用Room操作数据库,由于操作数据库需要在子线程,所以,我们可以构造一个`HandlerThread`,专门处理操作数据库的任务,如果操作过程非常耗时,然后又要关闭数据库,
相关推荐
踢球的打工仔3 小时前
PHP面向对象(7)
android·开发语言·php
安卓理事人3 小时前
安卓socket
android
安卓理事人9 小时前
安卓LinkedBlockingQueue消息队列
android
万能的小裴同学10 小时前
Android M3U8视频播放器
android·音视频
q***577411 小时前
MySql的慢查询(慢日志)
android·mysql·adb
JavaNoober11 小时前
Android 前台服务 "Bad Notification" 崩溃机制分析文档
android
城东米粉儿12 小时前
关于ObjectAnimator
android
zhangphil13 小时前
Android渲染线程Render Thread的RenderNode与DisplayList,引用Bitmap及Open GL纹理上传GPU
android
火柴就是我14 小时前
从头写一个自己的app
android·前端·flutter
lichong95115 小时前
XLog debug 开启打印日志,release 关闭打印日志
android·java·前端