现象 有用户反馈,手机在滑动的时候, 列表会一抖一抖的, 滑动桌面或者设置(只要是可以滑动的),都会出现,但是这个并不是必现,而是某些用户会出现,某些用户则不会出现。
吃瓜群众可以直接拉到下面看 罪魁祸首和自检 ,对分析问题比较感兴趣的可以看一下分析的过程。
Systrace 分析 本地测试有一台复现, 拿过来之后分析发现,手指滑动桌面或者设置,都会必现卡顿, 从 Trace 上看就是下面这样
红色箭头处就是掉帧的地方. 从上面的 Buffer 个数可以看到, SF没有绘制的原因是 Launcher 没有提交 Buffer 上来.
对应的 Launcher Trace如下, 可以看到 Launcher 没有绘制的原因是没有 Input 事件传上来. 所以 Launcher 的画面没有更新, 所以才会出现掉帧.
没有事件上来这个本身就是有问题的, 我们手指是连续从屏幕上划过的, 事件的上报应该是连续的才对, 我们怀疑是屏幕报点有问题, 不过 Check 硬件之前我们首先看一下 InputReader 和 InputDispatcher 线程是否正常工作
从图中可以看到 InputReader 线程是正常工作的, 但是 InputDIspatcher 线程却有问题, 大家可以看一下正常情况下这两个线程的对应关系
再回到有问题的那个图, 仔细看发现 InputDispatcher 线程的周期是和 Vsync 是相同的, 也就是说, InputDispatcher 的唤醒逻辑由 InputReader 唤醒变为由 Vsync 唤醒
再仔细看的话,点开 InputDIspatcher 的线程 cpu 状态可以看到, 唤醒执行任务的 InputDispatcher 线程并不是被 InputReader 线程唤醒的, 而是被 System_Server 的 UI Thread 唤醒的.
那么接下来, 就需要从代码的角度来看为什么 InputReader 没有唤醒 InputDIspatcher 。
代码分析 InputReader 唤醒 InputDispatcher 线程的逻辑如下(以本例中的 Move 手势为例。),
frameworks/native/services/inputflinger/InputDispatcher.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 void InputDispatcher::notifyMotion (const NotifyMotionArgs* args) { if (!validateMotionEvent (args->action, args->actionButton, args->pointerCount, args->pointerProperties)) { return ; } uint32_t policyFlags = args->policyFlags; policyFlags |= POLICY_FLAG_TRUSTED; android::base::Timer t; mPolicy->interceptMotionBeforeQueueing (args->eventTime, policyFlags); if (t.duration () > SLOW_INTERCEPTION_THRESHOLD) { ALOGW ("Excessive delay in interceptMotionBeforeQueueing; took %s ms" , std::to_string (t.duration ().count ()).c_str ()); } bool needWake; { mLock.lock (); if (shouldSendMotionToInputFilterLocked (args)) { mLock.unlock (); MotionEvent event; event.initialize (args->deviceId, args->source, args->action, args->actionButton, args->flags, args->edgeFlags, args->metaState, args->buttonState, 0 , 0 , args->xPrecision, args->yPrecision, args->downTime, args->eventTime, args->pointerCount, args->pointerProperties, args->pointerCoords); policyFlags |= POLICY_FLAG_FILTERED; if (!mPolicy->filterInputEvent (&event, policyFlags)) { return ; } mLock.lock (); } MotionEntry* newEntry = new MotionEntry (args->eventTime, args->deviceId, args->source, policyFlags, args->action, args->actionButton, args->flags, args->metaState, args->buttonState, args->edgeFlags, args->xPrecision, args->yPrecision, args->downTime, args->displayId, args->pointerCount, args->pointerProperties, args->pointerCoords, 0 , 0 ); needWake = enqueueInboundEventLocked (newEntry); mLock.unlock (); } if (needWake) { mLooper->wake (); } }
需要注意这里 ,mPolicy->filterInputEvent 直接 return了,也就是说这里如果返回 false,那么就直接 return 了, 不继续执行下面的步骤。
继续看 mPolicy->filterInputEvent
frameworks/base/services/core/jni/com_android_server_input_InputManagerService.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 bool NativeInputManager::filterInputEvent (const InputEvent* inputEvent, uint32_t policyFlags) { ATRACE_CALL (); jobject inputEventObj; JNIEnv* env = jniEnv (); switch (inputEvent->getType ()) { case AINPUT_EVENT_TYPE_KEY: inputEventObj = android_view_KeyEvent_fromNative (env, static_cast <const KeyEvent*>(inputEvent)); break ; case AINPUT_EVENT_TYPE_MOTION: inputEventObj = android_view_MotionEvent_obtainAsCopy (env, static_cast <const MotionEvent*>(inputEvent)); break ; default : return true ; } jboolean pass = env->CallBooleanMethod (mServiceObj, gServiceClassInfo.filterInputEvent, inputEventObj, policyFlags); if (checkAndClearExceptionFromCallback (env, "filterInputEvent" )) { pass = true ; } env->DeleteLocalRef (inputEventObj); return pass; }
这里从 jni 调回到 java 层, 也就是 InputManagerService 的 filterInputEvent 方法。
com/android/server/input/InputManagerService.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 final boolean filterInputEvent (InputEvent event, int policyFlags) { synchronized (mInputFilterLock) { if (mInputFilter != null) { try { mInputFilter.filterInputEvent (event, policyFlags); } catch (RemoteException e) { } return false ; } } event.recycle (); return true ; }
跟代码流程发现, 这个 mInputFilter 是 AccessibilityInputFilter 的一个实例, 在 辅助功能里面打开开关的时候,会调用 AccessibilityManagerService 的 updateInputFilter 方法来设置 InputFilter.
android/view/InputFilter.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 final public void filterInputEvent (InputEvent event, int policyFlags) { mH.obtainMessage(MSG_INPUT_EVENT, policyFlags, 0 , event).sendToTarget(); } case MSG_INPUT_EVENT: { final InputEvent event = (InputEvent)msg.obj; try { if (mInboundInputEventConsistencyVerifier != null ) { mInboundInputEventConsistencyVerifier.onInputEvent(event, 0 ); } onInputEvent(event, msg.arg1); } finally { event.recycle(); } break ; }
继续看 onInputEvent(event, msg.arg1); com/android/server/accessibility/AccessibilityInputFilter.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 @Override public void onInputEvent (InputEvent event, int policyFlags) { if (mEventHandler == null ) { if (DEBUG) Slog.d(TAG, "mEventHandler == null for event " + event); super .onInputEvent(event, policyFlags); return ; } EventStreamState state = getEventStreamState(event); if (state == null ) { super .onInputEvent(event, policyFlags); return ; } int eventSource = event.getSource(); if ((policyFlags & WindowManagerPolicy.FLAG_PASS_TO_USER) == 0 ) { state.reset(); mEventHandler.clearEvents(eventSource); super .onInputEvent(event, policyFlags); return ; } if (state.updateDeviceId(event.getDeviceId())) { mEventHandler.clearEvents(eventSource); } if (!state.deviceIdValid()) { super .onInputEvent(event, policyFlags); return ; } if (event instanceof MotionEvent) { if ((mEnabledFeatures & FEATURES_AFFECTING_MOTION_EVENTS) != 0 ) { MotionEvent motionEvent = (MotionEvent) event; processMotionEvent(state, motionEvent, policyFlags); return ; } else { super .onInputEvent(event, policyFlags); } } else if (event instanceof KeyEvent) { KeyEvent keyEvent = (KeyEvent) event; processKeyEvent(state, keyEvent, policyFlags); } }
继续看 processMotionEvent
1 2 3 4 5 6 7 8 9 10 11 12 private void processMotionEvent (EventStreamState state, MotionEvent event, int policyFlags) { if (!state.shouldProcessScroll() && event.getActionMasked() == MotionEvent.ACTION_SCROLL) { super .onInputEvent(event, policyFlags); return ; } if (!state.shouldProcessMotionEvent(event)) { return ; } batchMotionEvent(event, policyFlags); }
继续看 batchMotionEvent
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 private void batchMotionEvent (MotionEvent event, int policyFlags) { if (DEBUG) { Slog.i(TAG, "Batching event: " + event + ", policyFlags: " + policyFlags); } if (mEventQueue == null ) { mEventQueue = MotionEventHolder.obtain(event, policyFlags); scheduleProcessBatchedEvents(); return ; } if (mEventQueue.event.addBatch(event)) { return ; } MotionEventHolder holder = MotionEventHolder.obtain(event, policyFlags); holder.next = mEventQueue; mEventQueue.previous = holder; mEventQueue = holder; }
继续看 scheduleProcessBatchedEvents
1 2 3 4 private void scheduleProcessBatchedEvents () { mChoreographer.postCallback(Choreographer.CALLBACK_INPUT, mProcessBatchedEventsRunnable, null ); }
会在下一个 Vsync 周期的时候执行 mProcessBatchedEventsRunnable, 也就是 Choreographer.CALLBACK_INPUT , 熟悉 Choregrapher 的同学应该知道这里在做什么。
1 2 3 4 5 6 7 8 9 10 11 private final Runnable mProcessBatchedEventsRunnable = new Runnable () { @Override public void run () { final long frameTimeNanos = mChoreographer.getFrameTimeNanos(); } processBatchedEvents(frameTimeNanos); if (mEventQueue != null ) { scheduleProcessBatchedEvents(); } } };
那么代码在这里就比较清晰了, 是因为存在 AccessibilityInputFilter 导致 InputDIspatcher 线程没有被唤醒,而是把事件处理放到了下一个 Vsync 里面去处理。 结论
问题就在这个 Runnable 里面, 正常情况下, 如果没有打开 AccessibilityInputFilter, 那么上层不会对 Input 事件做任何的拦截, 一旦有 AccessibilityInputFilter, 那么就会走上面的逻辑, 这时候 InputDispatcher 不会跟着 InputReader 的节奏来走, 而是跟着 Vsync 的节奏来走, 从 Trace 上也可看到这点;
那么这个 AccessibilityInputFilter 是从哪里来的呢?答案就是 Accessibility 服务,也就是常说的无障碍服务。
罪魁祸首 经过上面的分析我们知道问题的原因是无障碍服务 ,无障碍服务的本质是为了服务哪些盲人之类的不方便操作的用户,但是某些 App 为了实现特定的功能,也加入了自己的 Accessibility 服务, 比如各大手机市场的“一键安装”功能,用户是方便了,但是用不好,也会有负面的作用,比如这一例,导致用户手机整机卡顿,不知道的用户,我估计都要退机了。
那么罪魁祸首是谁呢?目前发现有两个,一个讯飞输入法,一个是应用宝。打开 设置-系统-无障碍服务,可以看到里面的各种软件都有参与到,不过这个默认是关闭的,很多应用会引导用户去开启,许多用户不明所以,就稀里糊涂打开了。
无障碍服务页面如下:
关于无障碍服务有多NB,大家可以自己看看下面的弹框,这东西可以检测你的信用卡号和密码,至于短信内容、微信聊天内容那都是小 Case。
至于在这个例子里面引起整机卡顿的,就是下面这个 监听 ”执行手势“ 这个,一旦有应用监听这个的话, InputDIspatcher 线程就会走 Vsync 的周期,导致报点处理不及时,从而让滑动的对象以为这一帧没有事件进入,所以也没有内容的变更,就不会进行页面的更新,从而导致卡顿。
自检 如果你使用的是 Android 手机,强烈建议你关掉所有的无障碍服务(如果你不需要的话),像自动安装应用这种功能,不值得你为此付出这么大的风险。这个是 Android 原生的问题,我们在 Pixle 和 其他三方手机上都有发现这个问题。
关闭路径:设置-系统-无障碍服务 , 进去后把你已经打开的都关上。
强烈建议 应用宝、讯飞输入法 ,不要监听手势事件。
本文知乎地址 由于博客留言交流不方便,点赞或者交流,可以移步本文的知乎界面知乎 - Android 平台应用宝和讯飞输入法无障碍服务导致的全局卡顿分析
关于我 && 博客 下面是个人的介绍和相关的链接,期望与同行的各位多多交流,三人行,则必有我师!
博主个人介绍 :里面有个人的微信和微信群链接。
本博客内容导航 :个人博客内容的一个导航。
个人整理和搜集的优秀博客文章 - Android 性能优化必知必会 :欢迎大家自荐和推荐 (微信私聊即可)
Android性能优化知识星球 : 欢迎加入,多谢支持~
一个人可以走的更快 , 一群人可以走的更远