Android消息循环机制(二)Looper性能检测的缺陷

这一节介绍一个消息循环的特例。
上一节简单介绍了Android的Handler消息循环机制,大致基本就是

1、MessageQueue.next() // 没有消息就阻塞这里,有消息就取出消息
2、拿到Message返回给Looper.loop的调用处,处理消息。
3、处理结束又回到1

那现在有个疑问,有没有可能存在一段代码在主线程执行,但是没有构造Message的情况?答案其实是有。

什么情况下会出现没有构造Message但是被主线程执行的呢?

Java层代码,非常少从Looper.loop()-->>MessageQueue.next()一路把源码看一遍会发现应该不会出现这种情况。Java层代码执行结束会跑到nativePollonce方法,那有没有可能,nativePollonce方法执行后没有执行

nativePollOnce(ptr, nextPollTimeoutMillis);
 synchronized (this) {
     // Try to retrieve the next message.  Return if found.
    final long now = SystemClock.uptimeMillis();

而是去执行其他Java方法了?从代码实现上肯定是可以的。那实际有没有呢?其实是有。创建一个HelloWorld的App,不需要编写任何代码把App跑起来,然后进入debug模式。在nativePollOnce和final long now = SystemClock.uptimeMillis();打两个断点。dump堆栈数据,会发现这时候代码是跑道nativePollOnce里了。收到新Message后这一行代码会结束阻塞跑到final long now = SystemClock.uptimeMillis();来。也就是说这时候代码通常应该是主线程收到消息直接跑到第二个断点来。
关键地方来了,这时候你在屏幕上滑动下,看下有没有进入断点?确实会进入,打印这个Message消息会发现是绘制UI的Message并不是触摸事件的Message。
这时候我们把代码改造下,去MainActivity里复写方法dispatchTouchEvent然后在这个方法打印日志,并添加断点,这时候再次打开App,安静状态进入debug模式。这时候触摸下界面,神器的事情发生了,App没有进入final long now = SystemClock.uptimeMillis();这个断点,而是直接跑到dispatchTouchEvent了。这是为什么?我们打印堆栈会发现

java.lang.Thread.State: RUNNABLE
      at com.aesean.handler.MainActivity.dispatchTouchEvent(MainActivity.java:61)
      at android.support.v7.view.WindowCallbackWrapper.dispatchTouchEvent(WindowCallbackWrapper.java:71)
      at com.android.internal.policy.DecorView.dispatchTouchEvent(DecorView.java:375)
      at android.view.View.dispatchPointerEvent(View.java:10243)
      at android.view.ViewRootImpl$ViewPostImeInputStage.processPointerEvent(ViewRootImpl.java:4438)
      at android.view.ViewRootImpl$ViewPostImeInputStage.onProcess(ViewRootImpl.java:4306)
      at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:3853)
      at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:3906)
      at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:3872)
      at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:3999)
      at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:3880)
      at android.view.ViewRootImpl$AsyncInputStage.apply(ViewRootImpl.java:4056)
      at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:3853)
      at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:3906)
      at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:3872)
      at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:3880)
      at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:3853)
      at android.view.ViewRootImpl.deliverInputEvent(ViewRootImpl.java:6246)
      at android.view.ViewRootImpl.doProcessInputEvents(ViewRootImpl.java:6220)
      at android.view.ViewRootImpl.enqueueInputEvent(ViewRootImpl.java:6181)
      at android.view.ViewRootImpl$WindowInputEventReceiver.onInputEvent(ViewRootImpl.java:6349)
      at android.view.InputEventReceiver.dispatchInputEvent(InputEventReceiver.java:185)
      at android.os.MessageQueue.nativePollOnce(MessageQueue.java:-1)
      at android.os.MessageQueue.next(MessageQueue.java:323)
      at android.os.Looper.loop(Looper.java:136)
      at android.app.ActivityThread.main(ActivityThread.java:6119)
      at java.lang.reflect.Method.invoke(Method.java:-1)
      at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:886)
      at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:776)

nativePollOnce直接调用了android.view.InputEventReceiver.dispatchInputEvent。跟到这个方法

    // Called from native code.
    @SuppressWarnings("unused")
    private void dispatchInputEvent(int seq, InputEvent event) {
        mSeqMap.put(event.getSequenceNumber(), seq);
        onInputEvent(event);
    }

注释写着Called from native code.真相大白了。native直接调用了其他方法,没有返回到调用地方MessageQueue.next执行。至于为什么会这样,这种情况下的消息处理机制可以参考:http://blog.csdn.net/luoshengyang/article/details/6882903 或者请自行搜索:Android键盘消息处理机制。

AndroidPerformanceMonitor(BlockCanary)的缺陷

AndroidPerformanceMonitor本身是个很优秀的框架,封装的很完整。但是只要是通过监控Handler消息做的性能监控都会有不能处理touchEvent监控的缺陷。原因很简单,因为touch事件的dispatch根本没有Message,也就更没有dispatchMessage了。大家可以尝试在Activity添加下面的代码,会发现这个5秒的卡顿是不会被检测到的。

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return super.onTouchEvent(event);
    }

其实对于性能监控Android已经提供了TraceView来检测App流畅程度,在Android的Sdk里你到处可见
这里的Trace.traceBegin和Trace.traceEnd就是做打点检测性能的。

  if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
      Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
  }
  try {
      msg.target.dispatchMessage(msg);
  } finally {
     if (traceTag != 0) {
      Trace.traceEnd(traceTag);
      }
  }

包括touchEvent也有打点。只不过个人觉得TraveView追踪出来的数据虽然非常详细,但是用起来其实很不方便。关于TraceView如何使用请自行Google。

Trace.traceCounter(Trace.TRACE_TAG_INPUT, mPendingInputEventQueueLengthCounterName,mPendingInputEventCount);
......
Trace.asyncTraceEnd(Trace.TRACE_TAG_VIEW, "deliverInputEvent",q.mEvent.getSequenceNumber());

你可能感兴趣的:(Android消息循环机制(二)Looper性能检测的缺陷)