Android第三方键盘getTextBeforeCursor()耗时引发ANR问题咨询
第三方键盘应用InputDispatching超时ANR修复方案
报错详情
Input dispatching timed out (e716b5 InputMethod (server) is not responding. Waited 10001ms for MotionEvent)
ANR调用栈:
at sun.misc.Unsafe.park (Native method) at java.util.concurrent.locks.LockSupport.parkNanos (LockSupport.java:230) at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireSharedNanos (AbstractQueuedSynchronizer.java:1063) at java.util.concurrent.locks.AbstractQueuedSynchronizer.tryAcquireSharedNanos (AbstractQueuedSynchronizer.java:1358) at java.util.concurrent.locks.CountDownLatch.await (CountDownLatch.java:278) at com.android.internal.inputmethod.CancellationGroup$Completable$ValueBase.await (CancellationGroup.java:143) at com.android.internal.inputmethod.CancellationGroup$Completable$Values.await (CancellationGroup.java:203) at com.android.internal.view.InputConnectionWrapper.getResultOrNull (InputConnectionWrapper.java:100) at com.android.internal.view.InputConnectionWrapper.getTextBeforeCursor (InputConnectionWrapper.java:137) at func.keyboard.InputAnalyzer.onKeyChar (InputAnalyzer.java:25) at ime.BaseIME.callAnalyzer (BaseIME.java:435) at ime.LegacyIME.onKeyChar (LegacyIME.java:340) at ime.BaseIME.onKey (BaseIME.java:213) at manager.KeyInputManager.onKey (KeyInputManager.java:75) at common.PointerTracker.detectAndSendKey (PointerTracker.java:544) at common.PointerTracker.onUpEvent (PointerTracker.java:390) at common.KeyboardBaseView.onUpEvent (KeyboardBaseView.java:2423) at common.KeyboardBaseView.sendOnXEvent (KeyboardBaseView.java:2385) at ui.keyboard.common.KeyboardBaseView.onTouchEvent (KeyboardBaseView.java:2369) at android.view.View.dispatchTouchEvent (View.java:15199) at android.view.ViewGroup.dispatchTransformedTouchEvent (ViewGroup.java:3920) at android.view.ViewGroup.dispatchTouchEvent (ViewGroup.java:3594) at android.view.ViewGroup.dispatchTransformedTouchEvent (ViewGroup.java:3920) at android.view.ViewGroup.dispatchTouchEvent (ViewGroup.java:3594) at android.view.ViewGroup.dispatchTransformedTouchEvent (ViewGroup.java:3920) at android.view.ViewGroup.dispatchTouchEvent (ViewGroup.java:3594) at android.view.ViewGroup.dispatchTransformedTouchEvent (ViewGroup.java:3920) at android.view.ViewGroup.dispatchTouchEvent (ViewGroup.java:3594) at android.view.ViewGroup.dispatchTransformedTouchEvent (ViewGroup.java:3920) at android.view.ViewGroup.dispatchTouchEvent (ViewGroup.java:3594) at android.view.ViewGroup.dispatchTransformedTouchEvent (ViewGroup.java:3920) at android.view.ViewGroup.dispatchTouchEvent (ViewGroup.java:3594) at android.view.ViewGroup.dispatchTransformedTouchEvent (ViewGroup.java:3920) at android.view.ViewGroup.dispatchTouchEvent (ViewGroup.java:3594) at android.view.ViewGroup.dispatchTransformedTouchEvent (ViewGroup.java:3920) at android.view.ViewGroup.dispatchTouchEvent (ViewGroup.java:3594) at android.view.ViewGroup.dispatchTransformedTouchEvent (ViewGroup.java:3920) at android.view.ViewGroup.dispatchTouchEvent (ViewGroup.java:3594) at com.android.internal.policy.DecorView.superDispatchTouchEvent (DecorView.java:915) at com.android.internal.policy.PhoneWindow.superDispatchTouchEvent (PhoneWindow.java:1957) at android.app.Dialog.dispatchTouchEvent (Dialog.java:1162) at android.inputmethodservice.SoftInputWindow.dispatchTouchEvent (SoftInputWindow.java:188) at com.android.internal.policy.DecorView.dispatchTouchEvent (DecorView.java:873) at android.view.View.dispatchPointerEvent (View.java:15458) at android.view.ViewRootImpl$ViewPostImeInputStage.processPointerEvent (ViewRootImpl.java:7457) at android.view.ViewRootImpl$ViewPostImeInputStage.onProcess (ViewRootImpl.java:7233) at android.view.ViewRootImpl$InputStage.deliver (ViewRootImpl.java:6595) at android.view.ViewRootImpl$InputStage.onDeliverToNext (ViewRootImpl.java:6652) at android.view.ViewRootImpl$InputStage.forward (ViewRootImpl.java:6618) at android.view.ViewRootImpl$AsyncInputStage.forward (ViewRootImpl.java:6786) at android.view.ViewRootImpl$InputStage.apply (ViewRootImpl.java:6626) at android.view.ViewRootImpl$AsyncInputStage.apply (ViewRootImpl.java:6843) at android.view.ViewRootImpl$InputStage.deliver (ViewRootImpl.java:6599) at android.view.ViewRootImpl$InputStage.onDeliverToNext (ViewRootImpl.java:6652) at android.view.ViewRootImpl$InputStage.forward (ViewRootImpl.java:6618) at android.view.ViewRootImpl$InputStage.apply (ViewRootImpl.java:6626) at android.view.ViewRootImpl$InputStage.deliver (ViewRootImpl.java:6599) at android.view.ViewRootImpl.deliverInputEvent (ViewRootImpl.java:9880) at android.view.ViewRootImpl.doProcessInputEvents (ViewRootImpl.java:9718) at android.view.ViewRootImpl.enqueueInputEvent (ViewRootImpl.java:9671) at android.view.ViewRootImpl$WindowInputEventReceiver.onInputEvent (ViewRootImpl.java:10014) at android.view.InputEventReceiver.dispatchInputEvent (InputEventReceiver.java:220) at android.os.MessageQueue.nativePollOnce (Native method) at android.os.MessageQueue.next (MessageQueue.java:335) at android.os.Looper.loop (Looper.java:206) at android.app.ActivityThread.main (ActivityThread.java:8653) at java.lang.reflect.Method.invoke (Native method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:602) at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:1130)
关联业务代码
class InputAnalyzer { ... fun onKeyChar(ic: InputConnection) { this.ic = ic if (!enabled) return val inputText = ic.getTextBeforeCursor(16, 0) ?: return service.themeManager.animateFromText(inputText.toString()) } }
业务逻辑说明:用户触发按键抬起事件时,键盘应用会先向EditText发送字符,再调用
getTextBeforeCursor()获取光标前文本用于单词联想功能。
问题根因
InputConnection相关接口属于跨进程Binder调用,getTextBeforeCursor()需要向输入框所在的目标应用发起请求,等待目标应用主线程处理后才能返回结果,并非本地即时返回的方法。- 现有代码直接在主线程(触摸抬起事件的执行链路属于UI主线程)同步调用该跨进程方法,一旦目标应用主线程卡顿、输入框逻辑阻塞,会直接卡住键盘应用的主线程,阻塞时长超过10秒就会触发输入事件分发超时的ANR。
- 堆栈中主线程卡在
CountDownLatch.await()等待跨进程结果返回,和上述场景完全匹配。
修复方案
- 禁止在主线程同步调用InputConnection的取值类跨进程方法:将
getTextBeforeCursor、getSelectedText、getTextAfterCursor这类可能阻塞的调用全部挪到IO工作线程执行,拿到结果后再切回主线程执行UI动画、联想词更新等逻辑。 - 增加超时兜底:即使在工作线程执行跨进程调用,也要设置合理超时(建议200ms以内),超时直接放弃本次请求,避免无限等待占用线程资源。Android 10及以上版本系统对InputConnection调用有默认超时限制,低版本需要自行实现超时逻辑。
- 本地维护输入缓存减少跨进程调用:键盘侧自行维护当前输入框的文本状态,用户按键输入、删除、选中文本时同步更新本地缓存,仅在检测到输入框存在非键盘触发的变更(如用户手动粘贴、剪切、外部修改文本)时,才主动调用InputConnection接口同步一次最新文本,从根源上降低跨进程调用频次。
- 触摸事件链路轻量化:
onTouchEvent、onUp这类触摸回调中仅做基础事件分发,文本分析、联想请求、动画触发这类逻辑全部延后到下一帧或者工作线程执行,保证触摸事件在5ms内处理完成返回,不阻塞输入事件分发。
修复代码示例
// 分析逻辑使用独立IO协程域,不阻塞主线程 private val analyzerScope = CoroutineScope(Dispatchers.IO + SupervisorJob()) private const val IC_CALL_TIMEOUT = 200L class InputAnalyzer { // 本地缓存光标前文本,减少跨进程调用 private var cachedTextBeforeCursor: CharSequence = "" ... fun onKeyChar(ic: InputConnection, pressedChar: Char) { this.ic = ic if (!enabled) return // 先更新本地缓存,优先用本地数据触发轻量逻辑 cachedTextBeforeCursor = (cachedTextBeforeCursor.toString() + pressedChar).takeLast(16) // 异步执行跨进程同步和重逻辑 analyzerScope.launch { runCatching { // 带超时执行跨进程调用,避免无限等待 val latestText = withTimeoutOrNull(IC_CALL_TIMEOUT) { ic.getTextBeforeCursor(16, 0)?.toString() } latestText?.let { cachedTextBeforeCursor = it } // 切回主线程执行UI相关操作 withContext(Dispatchers.Main) { service.themeManager.animateFromText(cachedTextBeforeCursor.toString()) } } } } }
内容的提问来源于stack exchange,提问作者Heegyu Kim
相关产品推荐
相关产品推荐

