Android HandlerThread、Looper、MessageQueue 源码分析

时间:2024-12-21 22:40:21

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是不支持退出的
    public static void prepare() {
        prepare(true);
    }
  • loopOnce 静态方法,顾名思义,循环一次,它的作用是从当前消息队列MessageQueue中取一条符合条件的消息进行分发操作
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来处理消息
//关键代码
  public static void loop() {
    final Looper me = myLooper();
//...
      for (;;) {
          if (!loopOnce(me, ident, thresholdOverride)) {
              //返回false 意味着循环结束
              return;
          }
      }

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

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

HandlerThread

  • 既然它是一个线程,那可以先从run方法入手:

    @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的接口
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对象
    @NonNull
    public Handler getThreadHandler() {
        if (mHandler == null) {
            mHandler = new Handler(getLooper());
        }
        return mHandler;
    }
  • quit()quitSafely() 同样,HandlerThread也有退出的方法,其实现也是调用looper对应的函数退出

MessageQueue

由于关于消息分发逻辑在其他地方讲过,这里只要分析退出队列的逻辑。
它实现了退出和安全退出的方法,这两个操作有什么区别呢,请看源码

  • next() looper每调用一次loopOnce,内部就会调用messageQueue获取一条消息
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函数执行
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`,专门处理操作数据库的任务,如果操作过程非常耗时,然后又要关闭数据库,