Android必知必会Andorid的好东西学无止境

Android Handler机制 - MessageQueue

2018-08-23  本文已影响55人  未子涵

一次trouble-shooting

最近在查看应用的线上日志统计时,发现一个 MessageQueue.nativePollOnce() 的记录,具体信息如下:

  at android.os.MessageQueue.nativePollOnce(Native method)
  at android.os.MessageQueue.next(MessageQueue.java:325)
  at android.os.Looper.loop(Looper.java:142)
  at android.os.HandlerThread.run(HandlerThread.java:65)

因为是java层的异常收集,stack trace到这里就结束了,很明显问题实际上是在native层,所以只能通过traces.txt查看更多native层的信息了。

但是又拿不到用户的traces.txt,只好尝试拿自己的手机来看看,打开终端,通过以下命令获取traces.txt:

adb pull /data/anr/traces.txt C:\Users\wsj\Downloads\traces.txt

结果发现其中铺天盖地的都是nativePollOnce的日志,各种应用的不同线程都有报,摘录一段淘宝的日志:

// 显示进程id、ANR发生时间点、ANR发生进程包名
----- pid 32617 at 2018-08-17 19:55:27 -----
Cmd line: com.taobao.taobao
// 一些设备,内存,GC等信息,通常可以忽略
...
// 发生ANR的堆栈信息,重要部分
DALVIK THREADS (196):
// 其他一些错误信息
...

"main" prio=5 tid=1 Native
  | group="main" sCount=1 dsCount=0 flags=1 obj=0x73d92550 self=0xebc74000
  | sysTid=32617 nice=-10 cgrp=default sched=0/0 handle=0xf01ae4bc
  | state=S schedstat=( 6367184163 1210771881 20365 ) utm=458 stm=178 core=5 HZ=100
  | stack=0xff4ae000-0xff4b0000 stackSize=8MB
  | held mutexes=
  kernel: (couldn't read /proc/self/task/32617/stack)
  native: #00 pc 00019178  /system/lib/libc.so (syscall+28)
  native: #01 pc 000b3e35  /system/lib/libart.so (_ZN3art17ConditionVariable16WaitHoldingLocksEPNS_6ThreadE+88)
  native: #02 pc 0026302f  /system/lib/libart.so (_ZN3art3JNI16CallObjectMethodEP7_JNIEnvP8_jobjectP10_jmethodIDz+358)
  native: #03 pc 00002b57  /system/lib/libnativehelper.so (jniGetReferent+90)
  native: #04 pc 000c8b09  /system/lib/libandroid_runtime.so (_ZN7android26NativeDisplayEventReceiver13dispatchVsyncExij+20)
  native: #05 pc 00032555  /system/lib/libandroidfw.so (_ZN7android22DisplayEventDispatcher11handleEventEiiPv+104)
  native: #06 pc 000105cd  /system/lib/libutils.so (_ZN7android6Looper9pollInnerEi+572)
  native: #07 pc 000102f9  /system/lib/libutils.so (_ZN7android6Looper8pollOnceEiPiS1_PPv+32)
  native: #08 pc 000e47dd  /system/lib/libandroid_runtime.so (_ZN7android18NativeMessageQueue8pollOnceEP7_JNIEnvP8_jobjecti+24)
  native: #09 pc 001a8df5  /system/framework/arm/boot-framework.oat (Java_android_os_MessageQueue_nativePollOnce__JI+92)
  at android.os.MessageQueue.nativePollOnce(Native method)
  at android.os.MessageQueue.next(MessageQueue.java:325)
  at android.os.Looper.loop(Looper.java:142)
  at android.app.ActivityThread.main(ActivityThread.java:6938)
  at java.lang.reflect.Method.invoke(Native method)
  at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:327)
  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1374)

"com.taobao.taobao_mytaobao_preferences.sp" prio=5 tid=66 Native
  | group="main" sCount=1 dsCount=0 flags=1 obj=0x148ddde8 self=0xb9c7da00
  | sysTid=2852 nice=0 cgrp=default sched=0/0 handle=0xbce0f970
  | state=S schedstat=( 999426 52084 4 ) utm=0 stm=0 core=5 HZ=100
  | stack=0xbcd0d000-0xbcd0f000 stackSize=1038KB
  | held mutexes=
  kernel: (couldn't read /proc/self/task/2852/stack)
  native: #00 pc 0004a4d0  /system/lib/libc.so (__epoll_pwait+20)
  native: #01 pc 0001bcd5  /system/lib/libc.so (epoll_pwait+60)
  native: #02 pc 0001bd05  /system/lib/libc.so (epoll_wait+12)
  native: #03 pc 00010407  /system/lib/libutils.so (_ZN7android6Looper9pollInnerEi+118)
  native: #04 pc 000102f9  /system/lib/libutils.so (_ZN7android6Looper8pollOnceEiPiS1_PPv+32)
  native: #05 pc 000e47dd  /system/lib/libandroid_runtime.so (_ZN7android18NativeMessageQueue8pollOnceEP7_JNIEnvP8_jobjecti+24)
  native: #06 pc 001a8df5  /system/framework/arm/boot-framework.oat (Java_android_os_MessageQueue_nativePollOnce__JI+92)
  at android.os.MessageQueue.nativePollOnce(Native method)
  at android.os.MessageQueue.next(MessageQueue.java:325)
  at android.os.Looper.loop(Looper.java:142)
  at android.os.HandlerThread.run(HandlerThread.java:65)

// 其他一些错误信息
...

----- end 32617 -----

以上是我手机中traces.txt记录的淘宝的ANR日志摘录,我只摘出了两条nativePollOnce 相关的,第一个是发生在 main 线程,第二个是发生在 com.taobao.taobao_mytaobao_preferences.sp 线程。来看看第二个,即便是native层的部分似乎也没有什么错误信息。直接看第一行native信息:

native: #00 pc 0004a4d0  /system/lib/libc.so (__epoll_pwait+20)

epoll_wait其实是native层用于实现等待的,在管道上等待消息写入,一有消息到来时立马从管道中读取出来并返回结果,这会在后面的 消息循环 中讲到。这么看来,这段日志也只是说明:

目标线程在等待下一条消息的到来。

既然这样,只好先找谷歌了,能搜到的内容不多,stackoverflow上面有一个解释:

The nativePollOnce method is used to "wait" till the next Message becomes available. If the time spent during this call is long, your main (UI) thread has no real work to do and waits for next events to process. There's no need to worry about that.

android - what is message queue native poll once in android?

似乎不需要太关注这个问题,但又不是很放心,索性再看下 MessageQueue.next() 的源码,了解一下这个 nativePollOnce() 究竟是干嘛的,查了一番,大概是这样:

Java层和Native层其实各有一个MessageQueue,Java层的MessageQueue实际上是从Native层去获取其中的消息。而next方法显然就是用于获取下一条消息的,其中主要通过nativePollOnce从native层的MessageQueue中获取,并且该方法会阻塞线程,如果获取不到消息(比如消息队列中是空的),就一直阻塞,直到获取到消息为止。

另外还有一个说法:
Android - how do I investigate an ANR?

An ANR happens when the system realizes a message loop is spending to much time processing a message, and not processing other messages in the queue. If the loops is waiting for messages, obviously this is not happening.

当系统发现一次消息循环中花费了过多的时间处理这个消息,而没法处理其他消息时就会发生ANR。但我做了个Demo试验了一下,无论是在HandlerThread还是主线程中,让线程休眠20分钟以上(模拟长时间任务),都没有发生ANR,也没在traces.txt中找到 nativePollOnce 相关日志。

本次调查的结论

鉴于没法获得用户的traces.txt文件,我自己又没法重现,所以只能以网上的信息为结果:

  1. 不需要在意这个日志,这只是说明当前线程在等待消息的到来。
    问题是既然它是正常的,不会导致ANR,又为什么会出现在traces.txt中?
  2. 当系统发现一次消息循环中花费了过多的时间处理这个消息,而没法处理其他消息时就会发生ANR。
    可我使用demo却没能验证出来,而且我也确信最近这段时间使用淘宝时没有发生过任何crash现象,那么我手机中的traces.txt里为何有淘宝的nativePollOnce的日志?

最终还是没能得到一个确定的答案,只好放弃,如果有人有明确答案的话,也希望告知一下。

鉴于我手机的traces.txt中有很多nativePollOnce的日志,而我近期手机使用过程中又从没发生过ANR现象,只能认为不用太在意这个nativePollOnce了。

这个问题的调查中,明白MessageQueue的工作原理也是很重要的。接下来的内容转载自 Android应用程序消息处理机制 ,对于MessageQueue讲的非常简单明了。

Android消息处理机制概述

Android的消息处理机制主要分为四个部分:

主要涉及三个类:

创建消息队列

整个创建过程涉及到两个类:MessageQueueLooper。它们在C++层有两个对应的类:NativeMessageQueueLooper。其关系如下图所示:

      +------------+     +------+
      |MessageQueue+----^+Looper|
      +-----+------+     +------+
            |                    
            |                    
            |                    
+-----------+------+     +------+
|NativeMessageQueue+^----+Looper|
+------------------+     +------+

    A----^B表示B中保存A的引用

创建过程如下所示:

  1. Looper的prepare或者prepareMainLooper静态方法被调用,将一个Looper对象保存在ThreadLocal里面。
  2. Looper对象的初始化方法里,首先会新建一个MessageQueue对象。
  3. MessageQueue对象的初始化方法通过JNI初始化C++层的NativeMessageQueue对象。
  4. NativeMessageQueue对象在创建过程中,会初始化一个C++层的Looper对象。
  5. C++层的Looper对象在创建的过程中,会在内部创建一个管道(pipe),并将这个管道的读写fd都保存在mWakeReadPipeFd和mWakeWritePipeFd中。
    然后新建一个epoll实例,并将两个fd注册进去。
  6. 利用epoll的机制,可以做到当管道没有消息时,线程睡眠在读端的fd上,当其他线程往管道写数据时,本线程便会被唤醒以进行消息处理。

消息循环

          +------+    +------------+  +------------------+  +--------------+                    
          |Looper|    |MessageQueue|  |NativeMessageQueue|  |Looper(Native)|                    
          +--+---+    +------+-----+  +---------+--------+  +-------+------+                    
             |               |                  |                   |                           
             |               |                  |                   |                           
+-------------------------------------------------------------------------------+               
|[msg loop]  |   next()      |                  |                   |           |               
|            +------------>  |                  |                   |           |               
|            |               |                  |                   |           |               
|            |               |                  |                   |           |               
|            |               | nativePollOnce() |                   |           |               
|            |               |    pollOnce()    |                   |           |               
|            |               +----------------> |                   |           |               
|            |               |                  |                   |           |              
|            |               |                  |                   |           |               
|            |               |                  |                   |           |               
|            |               |                  |                   |           |               
|            |               |                  |     pollOnce()    |           |               
|            |               |                  +-----------------> |           |               
|            |               |                  |                   |           |               
|            |               |                  |                   | epoll_wait()              
|            |               |                  |                   +--------+  |               
|            |               |                  |                   |        |  |               
|            |               |                  |                   |        |  |               
|            |               |                  |                   | <------+  |               
|            |               |                  |                   | awoken()  |               
|            +               +                  +                   +           |               
|                                                                               |               
|                                                                               |               
+-------------------------------------------------------------------------------+               
  1. 首先通过调用Looper的loop方法开始消息监听。loop方法里会调用MessageQueue的next方法。next方法会堵塞线程直到有消息到来为止。
  2. next方法通过调用nativePollOnce方法来监听事件。next方法内部逻辑如下所示(简化):
    a. 进入死循环,以参数timout=0调用nativePollOnce方法。
    b. 如果消息队列中有消息,nativePollOnce方法会将消息保存在mMessage成员中。nativePollOnce方法返回后立刻检查mMessage成员是否为空。
    c. 如果mMessage不为空,那么检查它指定的运行时间。如果比当前时间要前,那么马上返回这个mMessage,否则设置timeout为两者之差,进入下一次循环。
    d. 如果mMessage为空,那么设置timeout为-1,即下次循环nativePollOnce永久堵塞。
  3. nativePollOnce方法内部利用epoll机制在之前建立的管道上等待数据写入。接收到数据后马上读取并返回结果。

消息发送

          +-------+     +------------+   +------------------+   +--------------+                        
          |Handler|     |MessageQueue|   |NativeMessageQueue|   |Looper(Native)|                        
          +--+----+     +-----+------+   +---------+--------+   +-------+------+                        
             |                |                    |                    |                               
             |                |                    |                    |                               
sendMessage()|                |                    |                    |                               
+----------> |                |                    |                    |                               
             |                |                    |                    |                               
             |enqueueMessage()|                    |                    |                               
             +--------------> |                    |                    |                               
             |                |                    |                    |                               
             |                |                    |                    |                               
             |                |                    |                    |                               
             |                |  nativeWake()      |                    |                               
             |                |    wake()          |                    |                               
             |                +------------------> |                    |                               
             |                |                    |                    |                               
             |                |                    |    wake()          |                               
             |                |                    +------------------> |                               
             |                |                    |                    |                               
             |                |                    |                    |                               
             |                |                    |                    |write(mWakeWritePipeFd, "W", 1)
             |                |                    |                    |                               
             |                |                    |                    |                               
             |                |                    |                    |                               
             |                |                    |                    |                               
             |                |                    |                    |                               
             +                +                    +                    +                               

消息发送过程主要由 Handler 对象来驱动。

  1. Handler对象在创建时会保存当前线程的looper和MessageQueue,如果传入Callback的话也会保存起来。
  2. 用户调用handler对象的sendMessage方法,传入msg对象。handler通过调用MessageQueue的enqueueMessage方法将消息压入MessageQueue。
  3. enqueueMessage方法会将传入的消息对象根据触发时间(when)插入到message queue中。然后判断是否要唤醒等待中的队列。
    a. 如果插在队列中间。说明该消息不需要马上处理,不需要由这个消息来唤醒队列。
    b. 如果插在队列头部(或者when=0),则表明要马上处理这个消息。如果当前队列正在堵塞,则需要唤醒它进行处理。
  4. 如果需要唤醒队列,则通过nativeWake方法,往前面提到的管道中写入一个"W"字符,令nativePollOnce方法返回。

消息处理

         +------+       +-------+                                                                   
         |Looper|       |Handler|                                                                   
         +--+---+       +---+---+                                                                   
            |               |                                                                       
            |               |                                                                       
loop()      |               |                                                                       
[after next()]              |                                                                       
+---------> |               |                                                                       
            |               |                                                                       
            |dispatchMessage()                                                                      
            +-------------> |                                                                       
            |               |                                                                       
            |               |                                                                       
            |               | handleMessage()                                                       
            |               +-------+                                                               
            |               |       |                                                               
            |               |       |                                                               
            |               | <-----+                                                               
            |               |   (callback or subclass)                                              
            |               |                                                                       
            +               +                                                                       

Looper对象的loop方法里面的queue.next方法如果返回了message,那么handler的dispatchMessage会被调用。
a. 如果新建Handler的时候传入了callback实例,那么callback的handleMessage方法会被调用。
b. 如果是通过post方法向handler传入runnable对象的,那么runnable对象的run方法会被调用。
c. 其他情况下,handler方法的handleMessage会被调用。

上一篇下一篇

猜你喜欢

热点阅读