ANR 问题分析:后台 Activity 输入分发超时(无焦点窗口)

104 阅读5分钟

一、ANR 核心信息提取

首先从日志中定位关键上下文,明确问题基本属性:

关键维度具体信息
ANR 类型Input dispatching timed out(输入事件分发超时)
目标进程com.mytech.myapp(PID: 7214,UID: 1000,非前台进程:Foreground: No)
目标页面com.mytech.myapp/.ui.page.music.MusicPlayerActivity
核心异常点ActivityRecord 无焦点窗口(does not have a focused window

二、输入分发超时(无焦点窗口)的根本原因分析

Android 输入事件分发(InputDispatcher)的核心逻辑是:仅向前台有焦点的窗口(Focused Window)分发输入事件。后台 Activity 出现该 ANR,本质是「系统错误地向无焦点窗口分发事件」+「事件处理链路阻塞」的叠加问题,具体拆解为以下 4 点:

1. 核心矛盾:后台 Activity 窗口状态异常(未正确失焦 / 销毁)

正常情况下,Activity 切换到后台时(onStop 生命周期),系统会通过 WindowManager 将其窗口标记为「非焦点」并从「输入接收窗口列表」中移除。但日志显示:

  • 该 Activity 虽为后台进程(Foreground: No),但 UID=1000(系统权限) :拥有系统权限的应用可能绕过部分窗口管理逻辑,导致窗口未被正确隐藏或失焦。
  • InputDispatcher 仍尝试向其分发事件:说明 WindowManager 中该 Activity 的窗口状态未同步为「非焦点」,或 Input 事件队列中残留了针对该窗口的事件,最终分发时发现无焦点窗口而超时。

可能场景

  • Activity 切换后台时,onPause()/onStop() 生命周期执行异常(如被主线程阻塞),未触发窗口失焦逻辑。
  • 音乐播放场景下,Activity 可能通过「后台播放服务」保持窗口资源(如封面渲染),但未正确告知 WindowManager 窗口无需接收输入。

2. 系统级阻塞:system_server 高 CPU 占用导致 InputDispatcher 调度延迟

system_server 是 Android 系统核心进程(负责 InputDispatcher、ActivityManager、WindowManager 等核心服务),日志中其 CPU 占用高达 63%(36% user + 26% kernel) ,直接导致:

  • InputDispatcher 线程(sysTid=883,属于 system_server)无法及时调度:即使事件本身无需处理,InputDispatcher 也需耗时从队列中筛选「目标窗口」,高 CPU 导致该筛选过程超时。
  • 佐证:系统总 CPU 使用率仅 30%(13% user + 15% kernel),但 system_server 独占 63%,说明系统服务内部存在资源争抢(如 ActivityManager 频繁刷新进程状态)。

3. 目标进程 RenderThread 阻塞:主线程等待 GPU 渲染导致无响应

目标进程(7214)的 RenderThread(sysTid=7335)  处于 D 状态(不可中断睡眠) ,栈信息显示卡在 GPU 纹理上传 操作:

native: #12 pc 00000000000c0490  /vendor/lib64/egl/libGLESv2_adreno.so (glTexSubImage2D+144)
native: #13 pc 00000000005d1420  /system/lib64/libhwui.so (GrGLGpu::uploadTexData(...))
  • RenderThread 是负责 UI 渲染的子线程,卡在 glTexSubImage2D(纹理数据上传到 GPU),说明音乐封面等图像资源在后台仍持续渲染,且 GPU 资源不足(如其他进程占用 GPU)。
  • 主线程(sysTid=7214)处于 S 状态(睡眠) ,等待 RenderThread 完成渲染(栈信息:HardwareRenderer.setStopped → ViewRootImpl.performDraw):主线程无法处理任何消息(包括 InputDispatcher 发来的「事件拒绝」响应),最终触发超时。

4. 第三方进程干扰:com.mytech.navi 高 CPU 占用抢占资源

日志中 com.mytech.navi(PID:16720)CPU 占用达 36%(16% user + 19% kernel) ,该进程可能是车载导航类应用(从包名「autonavi」推测),其高 CPU 占用会:

  • 抢占 CPU 时间片:导致目标进程(7214)的主线程 / RenderThread 无法及时调度。
  • 可能占用 GPU 资源:导航应用通常需频繁渲染地图,与 MusicPlayerActivity 的封面渲染争抢 GPU,间接导致 RenderThread 阻塞。

三、解决方案与优化建议

针对上述原因,分「应用层优化」和「系统层排查」两维度给出方案:

1. 应用层:修复 MusicPlayerActivity 窗口与生命周期管理

(1)确保后台时窗口正确失焦 / 暂停渲染

  • 在 onPause()/onStop() 中主动暂停 UI 渲染:

    @Override
    protected void onStop() {
        super.onStop();
        // 1. 暂停音乐封面等图像渲染(如 Glide/Picasso 暂停加载)
        Glide.with(this).pauseRequests();
        // 2. 通知 WindowManager 窗口失焦(系统权限应用需额外处理)
        if (getWindow() != null) {
            getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
            getWindow().setFocusable(false);
            getWindow().setFocusableInTouchMode(false);
        }
        // 3. 停止后台不必要的绘制任务(如自定义 View 的 invalidate())
        if (musicCoverView != null) {
            musicCoverView.stopAnimation();
        }
    }
    
    @Override
    protected void onResume() {
        super.onResume();
        // 恢复时重新启用焦点和渲染
        if (getWindow() != null) {
            getWindow().setFocusable(true);
            getWindow().setFocusableInTouchMode(true);
        }
        Glide.with(this).resumeRequests();
    }
    

(2)避免主线程等待 RenderThread

  • 将耗时的图像处理(如封面裁剪、模糊)移到 子线程,避免阻塞主线程:

    // 错误:主线程处理图像
    Bitmap blurredCover = BlurUtils.blur(originalBitmap); // 耗时操作,阻塞主线程
    
    // 正确:子线程处理 + 主线程更新
    new AsyncTask<Void, Void, Bitmap>() {
        @Override
        protected Bitmap doInBackground(Void... voids) {
            return BlurUtils.blur(originalBitmap); // 子线程执行
        }
        @Override
        protected void onPostExecute(Bitmap result) {
            musicCoverView.setImageBitmap(result); // 主线程更新 UI
        }
    }.execute();
    

2. 系统层:降低 system_server 与第三方进程资源占用

(1)排查 system_server 高 CPU 原因

  • 通过 adb shell top -p 439 实时观察 system_server 内部线程占用,定位高耗线程(如 ActivityManager、WindowManager)。
  • 检查系统日志(adb logcat -s ActivityManager WindowManager),看是否存在「频繁进程切换」「窗口状态刷新异常」(如反复创建 / 销毁窗口)。

(2)限制 com.mytech.navi 资源占用

  • 若该导航应用为同厂商应用,优化其 GPU 渲染逻辑(如减少地图纹理更新频率)。
  • 若为第三方应用,通过系统权限限制其 CPU 优先级(adb shell renice 10 16720),避免抢占核心进程资源。

3. 通用优化:输入事件分发防护

  • 为后台 Activity 拦截输入事件:在 onWindowFocusChanged(boolean hasFocus) 中处理,避免事件传递到主线程:

    @Override
    public void onWindowFocusChanged(boolean hasFocus) {
        super.onWindowFocusChanged(hasFocus);
        if (!hasFocus) {
            // 后台时,拦截所有输入事件
            getWindow().setFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE,
                    WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE);
        } else {
            getWindow().clearFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE);
        }
    }
    

四、总结

本次 ANR 的本质是「窗口状态异常 + 系统资源争抢」的叠加:

  1. 系统权限应用的后台窗口未正确失焦,导致 InputDispatcher 错误分发事件;
  2. system_server 与导航应用高 CPU 占用,导致 InputDispatcher 调度延迟;
  3. 目标进程 RenderThread 卡在 GPU 渲染,主线程无法响应事件分发。

通过「修复窗口生命周期管理」「降低系统资源占用」「拦截后台输入事件」三方面优化,可彻底解决该问题。