• Android-Handler源码解析-Looper


    成员变量

    1. // Log的TAG
    2. private static final String TAG = "Looper";
    3. // 线程本地变量,保证了每个线程仅有唯一的Looper对象。
    4. @UnsupportedAppUsage
    5. static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    6. // 主线程的Looper,由ActivityThread的main方法内调用Looper.prepareMainLooper()进行创建。
    7. @UnsupportedAppUsage
    8. private static Looper sMainLooper;
    9. // 消息分发状态的观察者,有分发开始、分发异常、分发结束。
    10. private static Observer sObserver;
    11. // 消息队列
    12. @UnsupportedAppUsage
    13. final MessageQueue mQueue;
    14. // 创建Looper的线程
    15. final Thread mThread;
    16. // 是否在loop中
    17. private boolean mInLoop;
    18. // 日志打印类
    19. @UnsupportedAppUsage
    20. private Printer mLogging;
    21. // Trace追踪标记
    22. private long mTraceTag;
    23. // 慢分发阈值时间,如果设置了这个参数,如果消息分发的时间超过这个值,则这个looper将显示一个警告日志。
    24. private long mSlowDispatchThresholdMs;
    25. // 慢交付阈值时间,如果设置了这个参数,如果消息执行(实际交付时刻dispatchStart - post时刻msg.when)的时间超过这个值,则这个looper将显示一个警告日志。
    26. private long mSlowDeliveryThresholdMs;
    27. // 是否慢交付检测,如果消息交付时间超过mSlowDeliveryThresholdMs,则为true
    28. private boolean mSlowDeliveryDetected;
    29. 复制代码

    说明:

    1. Looper为什么需要持有MessageQueue,因为Looper轮询的时候需要知道轮询哪个MessageQueue

    创建Looper

    想要使用Looper,首先要创建Looper,所以我们接下来看下它是如何被创建的。

    Looper.prepare()

    1. public static void prepare() {
    2. prepare(true);
    3. }
    4. private static void prepare(boolean quitAllowed) {
    5. if (sThreadLocal.get() != null) {
    6. // 调用线程已经有Looper了,再进行prepare,则抛出异常(每个线程只能创建一个Looper)。
    7. throw new RuntimeException("Only one Looper may be created per thread");
    8. }
    9. // 创建Looper,并存入到sThreadLocal中。
    10. sThreadLocal.set(new Looper(quitAllowed));
    11. }
    12. 复制代码

    prepare()方法,为静态方法,为调用线程创建一个Looper存入sThreadLocal中,以便后续获取此线程LooperquitAllowed参数传入为ture,以允许退出Looper

    说明:

    1. 一个线程,只能调用一次prepare()prepare(boolean)方法,否则抛出异常
    2. 一个线程,只能有一个Looper
    3. prepare()方法创建的Looper允许退出

    接下来我们来看一下Looper构造方法

    1. private Looper(boolean quitAllowed) {
    2. // 创建消息队列
    3. mQueue = new MessageQueue(quitAllowed);
    4. // 记录创建线程
    5. mThread = Thread.currentThread();
    6. }
    7. 复制代码

    Looper构造方法,为私有方法,不能通过new创建,参数quitAllowed是否允许退出,创建Looper会创建一个MessageQueue,并记录创建线程

    说明:

    1. 一个Looper只有一个MessageQueue只有一个关联Thread

    Looper.prepareMainLooper()

    1. @Deprecated
    2. public static void prepareMainLooper() {
    3. // 准备Looper,如没有则进行创建,不允许退出。
    4. prepare(false);
    5. // 同步,保证线程安全。
    6. synchronized (Looper.class) {
    7. if (sMainLooper != null) {
    8. // 已经创建MainLooper了,再准备,则抛出异常。
    9. throw new IllegalStateException("The main Looper has already been prepared.");
    10. }
    11. // 获取调用线程的Looper,为MainLooper。
    12. sMainLooper = myLooper();
    13. }
    14. }
    15. 复制代码

    prepareMainLooper()方法,为静态方法,为主线程准备Looper,并传入quitAllowedfalse使其不允许退出,并将其标记apploopermyLooper()相关介绍,看后面的-获取Looper

    已被声明@Deprecated(弃用),因为app主线程looper是由Android环境创建的(ActivityThreadmain方法内调用Looper.prepareMainLooper()创建),因此永远不需要自己调用这个函数。

    说明:

    1. 多个线程只能调用一次prepareMainLooper()方法,否则抛出异常。
    2. 主线程Looper,是由Android环境创建的(ActivityThreadmain方法),不需要我们关心。
    3. 主线程Looper不允许退出

    小结

    1. 创建-非主线程Looper只能通过Looper.prepare()方法创建,它是允许退出的。
    2. 创建-主线程Looper,它是通过Android环境创建ActivityThreadmain方法内调用Looper.prepareMainLooper()创建),它是不允许退出的。
    3. 一个线程,一个Looper,一个MessageQueue

    获取Looper

    创建完Looper后,便可以获取Looper使用了,所以我们接下来看下它是如何被创建的。

    Looper.myLooper()

    1. public static @Nullable Looper myLooper() {
    2. return sThreadLocal.get();
    3. }
    4. 复制代码

    myLooper()方法,为静态方法,返回与当前线程关联的Looper对象。如果调用线程没有Looper关联,则返回null

    Looper.getMainLooper()

    1. public static Looper getMainLooper() {
    2. synchronized (Looper.class) {
    3. return sMainLooper;
    4. }
    5. }
    6. 复制代码

    getMainLooper()方法,为静态方法,返回主线程Looper对象。

    小结

    1. 获取Looper,有两个方法:Looper.myLooper()Looper.getMainLooper()
    2. Looper.myLooper()方法,获取调用线程Looper,有可能为null
    3. Looper.getMainLooper()方法,获取主线程Looper,不会为null

    轮询Looper

    Looper创建好后,需要调用Looper.loop()方法,使其进入消息循环中,所以我们接下来看下它的Looper.loop()方法。

    Looper.loop()

    1. public static void loop() {
    2. // 获取当前线程Looper对象
    3. final Looper me = myLooper();
    4. if (me == null) {
    5. // Looper对象为空,抛出异常(没有Looper; 这个线程上没有调用Looper.prepare())。
    6. throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    7. }
    8. if (me.mInLoop) {
    9. // 已经在loop中,再调用loop(),则提示警告(再次loop将使队列中的消息在此消息完成之前被执行)。
    10. Slog.w(TAG, "Loop again would have the queued messages be executed"
    11. + " before this one completed.");
    12. }
    13. // 标记在loop中
    14. me.mInLoop = true;
    15. // 确保这个线程的标识是本地进程的标识,并跟踪标识token实际上是什么。
    16. Binder.clearCallingIdentity();
    17. final long ident = Binder.clearCallingIdentity();
    18. // 允许用系统道具(adb命令)覆盖一个阈值,例如:
    19. // adb shell 'setprop log.looper.1000.main.slow 1 && stop && start'
    20. final int thresholdOverride =
    21. SystemProperties.getInt("log.looper."
    22. + Process.myUid() + "."
    23. + Thread.currentThread().getName()
    24. + ".slow", 0);
    25. // 慢交付检测,恢复默认值。
    26. me.mSlowDeliveryDetected = false;
    27. // 死循环,遍历消息队列里的消息。
    28. for (;;) {
    29. // 调用loopOnce方法,进行处理。
    30. if (!loopOnce(me, ident, thresholdOverride)) {
    31. // loopOnce方法返回false,退出循环,结束loop。
    32. return;
    33. }
    34. }
    35. }
    36. 复制代码

    loop()方法,为静态方法,内部执行死循环进行轮询,调用loopOnce()方法轮询单个消息loopOnce()方法返回true,则继续轮询下一个消息返回false,则退出循环结束loop

    说明:

    1. 当前线程调用Looper.loop()方法前,一定要为当前线程准备Looper(调用Looper.prepare()),否则抛出异常

    接下来,我们来看一下loopOnce()方法。

    1. private static boolean loopOnce(final Looper me,
    2. final long ident, final int thresholdOverride) {
    3. // 获取此Looper的消息队列里面的下个消息,有可能会阻塞线程。
    4. Message msg = me.mQueue.next();
    5. if (msg == null) {
    6. // 没有消息,表示消息队列正在退出(调用quit()方法),直接返回false以退出loop。
    7. return false;
    8. }
    9. // 获取日志打印类,这必须在一个局部变量中,以防UI事件设置logger。
    10. final Printer logging = me.mLogging;
    11. if (logging != null) {
    12. // 打印日志:Dispatching(分发中)
    13. logging.println(">>>>> Dispatching to " + msg.target + " "
    14. + msg.callback + ": " + msg.what);
    15. }
    16. // 获取Looper的全局观察者,使用变量,确保观察者在处理事务时不会更改。
    17. final Observer observer = sObserver;
    18. // 获取追踪标记
    19. final long traceTag = me.mTraceTag;
    20. // 获取消息分发阈值
    21. long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
    22. // 获取消息交付阈值
    23. long slowDeliveryThresholdMs = me.mSlowDeliveryThresholdMs;
    24. if (thresholdOverride > 0) {
    25. // 有覆盖的(adb命令传入),则用覆盖的值。
    26. slowDispatchThresholdMs = thresholdOverride;
    27. slowDeliveryThresholdMs = thresholdOverride;
    28. }
    29. // 是否记录慢交付
    30. final boolean logSlowDelivery = (slowDeliveryThresholdMs > 0) && (msg.when > 0);
    31. // 是否记录慢分发
    32. final boolean logSlowDispatch = (slowDispatchThresholdMs > 0);
    33. // 是否需要获取开始时刻
    34. final boolean needStartTime = logSlowDelivery || logSlowDispatch;
    35. // 是否需要获取结束时刻
    36. final boolean needEndTime = logSlowDispatch;
    37. if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
    38. // 开始追踪
    39. Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
    40. }
    41. // 分发开始时刻
    42. final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
    43. // 分发结束时刻
    44. final long dispatchEnd;
    45. // token,用于Observer回调间传递。
    46. Object token = null;
    47. if (observer != null) {
    48. // 获取token,通知Observer消息分发开始。
    49. token = observer.messageDispatchStarting();
    50. }
    51. // 设置当前线程的Uid
    52. long origWorkSource = ThreadLocalWorkSource.setUid(msg.workSourceUid);
    53. try {
    54. // 通知Message的Handler进行分发消息(重要!!!)
    55. msg.target.dispatchMessage(msg);
    56. if (observer != null) {
    57. // 通知Observer消息分发结束
    58. observer.messageDispatched(token, msg);
    59. }
    60. // 记录分发结束时刻
    61. dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
    62. } catch (Exception exception) {
    63. if (observer != null) {
    64. // 通知Observer消息分发异常
    65. observer.dispatchingThrewException(token, msg, exception);
    66. }
    67. throw exception;
    68. } finally {
    69. // 恢复当前线程的Uid
    70. ThreadLocalWorkSource.restore(origWorkSource);
    71. if (traceTag != 0) {
    72. // 结束追踪
    73. Trace.traceEnd(traceTag);
    74. }
    75. }
    76. if (logSlowDelivery) {
    77. // 记录慢交付,如果(dispatchStart - msg.when)时间大于阈值,则进行Slog提示警告。
    78. if (me.mSlowDeliveryDetected) {
    79. if ((dispatchStart - msg.when) <= 10) {
    80. Slog.w(TAG, "Drained");
    81. me.mSlowDeliveryDetected = false;
    82. }
    83. } else {
    84. if (showSlowLog(slowDeliveryThresholdMs, msg.when, dispatchStart, "delivery",
    85. msg)) {
    86. me.mSlowDeliveryDetected = true;
    87. }
    88. }
    89. }
    90. if (logSlowDispatch) {
    91. // 记录慢分发,如果(dispatchEnd - dispatchStart)时间大于阈值,则进行Slog提示警告。
    92. showSlowLog(slowDispatchThresholdMs, dispatchStart, dispatchEnd, "dispatch", msg);
    93. }
    94. if (logging != null) {
    95. // 打印日志:Finished(已完成)
    96. logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
    97. }
    98. // 确保在分发过程中线程的标识没有损坏。
    99. final long newIdent = Binder.clearCallingIdentity();
    100. if (ident != newIdent) {
    101. // 已损坏,提示。
    102. Log.wtf(TAG, "Thread identity changed from 0x"
    103. + Long.toHexString(ident) + " to 0x"
    104. + Long.toHexString(newIdent) + " while dispatching to "
    105. + msg.target.getClass().getName() + " "
    106. + msg.callback + " what=" + msg.what);
    107. }
    108. // 回收消息(不检查,直接回收)
    109. msg.recycleUnchecked();
    110. return true;
    111. }
    112. 复制代码

    loopOnce()方法,为静态方法,为轮询单个消息。它会先获取消息队列下个Message,然后调用Message目标Handler进行分发

    说明:

    1. LooperMessageQueue里面没有消息时,则线程进入阻塞,释放CPU,它不会因为没有数据而停止loop(),所以如果此Looper不使用了一定要调用quit()结束循环,它会使MessageQueuenext()方法返回null,从而结束循环
    2. 可通过调用looper.setMessageLogging()指定Looper设置日志打印者,以监听消息状态>>>>> Dispatching to分发开始<<<<< Finished to分发结束,可判断时间差来判断分发是否超时

    小结

    1. 轮询Looper前,调用线程一定要有Looper(需调用Looper.prepare()),否则抛出异常
    2. 非主线程Looper,如果不使用了,一定要调用quit()结束循环
    3. MessageQueue里面没有消息时,则线程进入阻塞,释放CPU,它不会因为没有数据而停止loop(),所以如果此Looper不使用了一定要调用quit()结束循环

    退出Looper

    Looper不使用了,便需要退出,所以我们接下来看下它是如何退出的。

    quit()

    1. public void quit() {
    2. mQueue.quit(false);
    3. }
    4. 复制代码

    quit()方法,为不安全退出Looper

    说明:

    1. 此方法,导致loop()方法,在不处理消息队列中的任何消息的情况下终止,因为它会删除消息队列所有消息
    2. looper被请求退出后,任何向队列发送消息的尝试都将失败。例如,Handler.sendMessage(Message)方法将返回false
    3. 此方法,可能不安全的,因为在looper终止之前可能无法传递一些消息。考虑使用quitSafely,以确保所有等待的工作都以有序的方式完成

    quitSafely()

    1. public void quitSafely() {
    2. mQueue.quit(true);
    3. }
    4. 复制代码

    quitSafely()方法,为安全退出Looper

    说明:

    1. 此方法,导致loop()方法,在处理消息队列中的已经到期所有消息不处理消息队列中的未来(当前时刻以后)到期所有消息的情况下终止,因为它会删除消息队列所有未来的消息

    小结

    1. 退出Looper,有两个方法:quit()不安全退出、quitSafely()安全退出。
    2. quit()方法,在不处理消息队列中的任何消息的情况下终止(此方法,可能不安全的,因为在looper终止之前可能无法传递一些消息)。
    3. quitSafely()方法,在处理消息队列中的已经到期所有消息不处理消息队列中的未来(当前时刻以后)到期所有消息的情况下终止
    4. looper被请求退出后,任何向队列发送消息的尝试都将失败。例如,Handler.sendMessage(Message)方法将返回false

    属性set、get方法

    LoopermLoggingmThreadmQueue属性对外提供了setget方法,我们接下来看下它们的实现。

    setMessageLogging()

    1. public void setMessageLogging(@Nullable Printer printer) {
    2. mLogging = printer;
    3. }
    4. 复制代码

    设置Looper处理消息控制消息日志记录。如果启用日志消息将在每次消息分发开始结束时写到printer标识目标Handler和消息内容。

    getThread()

    1. public @NonNull Thread getThread() {
    2. return mThread;
    3. }
    4. 复制代码

    获取与此looper关联线程

    getQueue()

    1. public @NonNull MessageQueue getQueue() {
    2. return mQueue;
    3. }
    4. 复制代码

    获取looper消息队列

    其它

    Looper.myQueue()

    1. public static @NonNull MessageQueue myQueue() {
    2. return myLooper().mQueue;
    3. }
    4. 复制代码

    获取当前线程关联的MessageQueue对象。此线程必须Looper否则抛出NullPointerException

    isCurrentThread()

    1. public boolean isCurrentThread() {
    2. return Thread.currentThread() == mThread;
    3. }
    4. 复制代码

    判断调用线程是否是该looper线程,如果是,则返回true

    dump()

    1. public void dump(@NonNull Printer pw, @NonNull String prefix) {
    2. pw.println(prefix + toString());
    3. mQueue.dump(pw, prefix + " ", null);
    4. }
    5. 复制代码

    转储looper状态,以进行调试,调用MessageQueuedump()方法。

  • 相关阅读:
    常用测试用例模板大全
    ZLMediaKit学习(一):Window环境下推拉流
    ubuntu 输入法
    微信、支付宝修改步数【小米运动】
    【玩转Redhat Linux 8.0系列 | 从命令行管理文件(二)】
    Scala中如何使用Jsoup库处理HTML文档?
    Java-Day15 常用类解析 (包装类、Junit测试单元、Object类、String类及StringBuffer和StringBuilder)
    ROS安装
    前端培训技术Nodejs连接 MongoDB
    第四章 文件管理 二、文件的逻辑结构
  • 原文地址:https://blog.csdn.net/xiaopangcame/article/details/127895080