You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 08:57:20