Android按键超时的ANR原理小结

本文详细剖析了Android系统的ANR(应用无响应)机制,从WindowManagerService到InputManager的按键处理流程,再到InputDispatcher的事件分发及ANR触发过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1) 按键处理主要的功臣是WindowManagerService. mInputManager

@WindowManagerService.java

mInputManager = new InputManager(context, this);

 

@InputManager.java

    

 public InputManager(Context context, WindowManagerService windowManagerService) {
        this.mContext = context;
        this.mWindowManagerService = windowManagerService;
        this.mCallbacks = new Callbacks();

        Looper looper = windowManagerService.mH.getLooper();

        Slog.i(TAG, "Initializing input manager");
        nativeInit(mContext, mCallbacks, looper.getQueue());

        // Add ourself to the Watchdog monitors.
        Watchdog.getInstance().addMonitor(this);
    }


@com_android_server_InputManager.cpp


static void android_server_InputManager_nativeInit(JNIEnv* env, jclass clazz,
        jobject contextObj, jobject callbacksObj, jobject messageQueueObj) {
    if (gNativeInputManager == NULL) {
        sp<Looper> looper = android_os_MessageQueue_getLooper(env, messageQueueObj);
        gNativeInputManager = new NativeInputManager(contextObj, callbacksObj, looper);
    } else {
        LOGE("Input manager already initialized.");
        jniThrowRuntimeException(env, "Input manager already initialized.");
    }
}


NativeInputManager::NativeInputManager(jobject contextObj,
        jobject callbacksObj, const sp<Looper>& looper) :
        mLooper(looper) {
    JNIEnv* env = jniEnv();

    mContextObj = env->NewGlobalRef(contextObj);
    mCallbacksObj = env->NewGlobalRef(callbacksObj);

    {
        AutoMutex _l(mLock);
        mLocked.displayWidth = -1;
        mLocked.displayHeight = -1;
        mLocked.displayExternalWidth = -1;
        mLocked.displayExternalHeight = -1;
        mLocked.displayOrientation = DISPLAY_ORIENTATION_0;

        mLocked.systemUiVisibility = ASYSTEM_UI_VISIBILITY_STATUS_BAR_VISIBLE;
        mLocked.pointerSpeed = 0;
        mLocked.pointerGesturesEnabled = true;
        mLocked.showTouches = false;
    }

    sp<EventHub> eventHub = new EventHub();
    mInputManager = new InputManager(eventHub, this, this);
}

@inputManager.cpp

InputManager::InputManager(
        const sp<EventHubInterface>& eventHub,
        const sp<InputReaderPolicyInterface>& readerPolicy,
        const sp<InputDispatcherPolicyInterface>& dispatcherPolicy) {
    mDispatcher = new InputDispatcher(dispatcherPolicy);
    mReader = new InputReader(eventHub, readerPolicy, mDispatcher);
    initialize();
}


2) 接下来,重点来了,inputDispatcher负责事件(keytouch)的分发,而事件处理延时的ANR也在它这里。InputReader主要负责读取底层传上来的事件,这里就不介绍了

@inputDispatcher.cpp


// If the currently focused window is still working on previous events then keep waiting.
    if (! isWindowFinishedWithPreviousInputLocked(mFocusedWindowHandle)) {
#if DEBUG_FOCUS
        LOGD("Waiting because focused window still processing previous input.");
#endif
        injectionResult = handleTargetsNotReadyLocked(currentTime, entry,
                mFocusedApplicationHandle, mFocusedWindowHandle, nextWakeupTime);
        goto Unresponsive;
    }

...

int32_t InputDispatcher::handleTargetsNotReadyLocked(nsecs_t currentTime,
        const EventEntry* entry,
        const sp<InputApplicationHandle>& applicationHandle,
        const sp<InputWindowHandle>& windowHandle,
        nsecs_t* nextWakeupTime) {

...

 if (currentTime >= mInputTargetWaitTimeoutTime) {
        onANRLocked(currentTime, applicationHandle, windowHandle,
                entry->eventTime, mInputTargetWaitStartTime);

        // Force poll loop to wake up immediately on next iteration once we get the
        // ANR response back from the policy.
        *nextWakeupTime = LONG_LONG_MIN;
        return INPUT_EVENT_INJECTION_PENDING;
    } else {
        // Force poll loop to wake up when timeout is due.
        if (mInputTargetWaitTimeoutTime < *nextWakeupTime) {
            *nextWakeupTime = mInputTargetWaitTimeoutTime;
        }
        return INPUT_EVENT_INJECTION_PENDING;
    }
}

void InputDispatcher::onANRLocked(
        nsecs_t currentTime, const sp<InputApplicationHandle>& applicationHandle,
        const sp<InputWindowHandle>& windowHandle,
        nsecs_t eventTime, nsecs_t waitStartTime) {
    LOGI("Application is not responding: %s.  "
            "%01.1fms since event, %01.1fms since wait started",
            getApplicationWindowLabelLocked(applicationHandle, windowHandle).string(),
            (currentTime - eventTime) / 1000000.0,
            (currentTime - waitStartTime) / 1000000.0);

    CommandEntry* commandEntry = postCommandLocked(
            & InputDispatcher::doNotifyANRLockedInterruptible);
    commandEntry->inputApplicationHandle = applicationHandle;
    commandEntry->inputWindowHandle = windowHandle;
}

void InputDispatcher::doNotifyANRLockedInterruptible(
        CommandEntry* commandEntry) {
    mLock.unlock();

    nsecs_t newTimeout = mPolicy->notifyANR(
            commandEntry->inputApplicationHandle, commandEntry->inputWindowHandle);

    mLock.lock();

    resumeAfterTargetsNotReadyTimeoutLocked(newTimeout,
            commandEntry->inputWindowHandle != NULL
                    ? commandEntry->inputWindowHandle->getInputChannel() : NULL);
}

这里的mPlicy是在构造的时候初始化的。追溯前面的inputManager.cpp,com_android_server_InputManager.cpp,很快

发现mPlicy其实就是NativeInputManager。

InputDispatcher::InputDispatcher(const sp<InputDispatcherPolicyInterface>& policy) :
    mPolicy(policy),

@ com_android_server_InputManager.cpp


nsecs_t NativeInputManager::notifyANR(const sp<InputApplicationHandle>& inputApplicationHandle,
        const sp<InputWindowHandle>& inputWindowHandle) {
#if DEBUG_INPUT_DISPATCHER_POLICY
    LOGD("notifyANR");
#endif

    JNIEnv* env = jniEnv();

    jobject inputApplicationHandleObj =
            getInputApplicationHandleObjLocalRef(env, inputApplicationHandle);
    jobject inputWindowHandleObj =
            getInputWindowHandleObjLocalRef(env, inputWindowHandle);

    jlong newTimeout = env->CallLongMethod(mCallbacksObj,
                gCallbacksClassInfo.notifyANR, inputApplicationHandleObj, inputWindowHandleObj);
    if (checkAndClearExceptionFromCallback(env, "notifyANR")) {
        newTimeout = 0; // abort dispatch
    } else {
        assert(newTimeout >= 0);
    }

    env->DeleteLocalRef(inputWindowHandleObj);
    env->DeleteLocalRef(inputApplicationHandleObj);
    return newTimeout;
}

3) 仔细回忆下前面从java层到native层的inputManger和inputDispatcher的构造,不难发现mPlicy的notifyANR()最终回到inputManager.java中对应Callbacks的notifyANR()。(JNI:不仅java层访问本地方法,同样在本地方法中也可以访问java层方法)

@inputManager.java


public long notifyANR(InputApplicationHandle inputApplicationHandle,
                InputWindowHandle inputWindowHandle) {
            return mWindowManagerService.mInputMonitor.notifyANR(
                    inputApplicationHandle, inputWindowHandle);
        }

@WindowManagerService.java 


final InputMonitor mInputMonitor = new InputMonitor(this);

@InputMonitor.java


public long notifyANR(InputApplicationHandle inputApplicationHandle,
            InputWindowHandle inputWindowHandle) {
        AppWindowToken appWindowToken = null;
        if (inputWindowHandle != null) {
            synchronized (mService.mWindowMap) {
                WindowState windowState = (WindowState) inputWindowHandle.windowState;
                if (windowState != null) {
                    Slog.i(WindowManagerService.TAG, "Input event dispatching timed out sending to "
                            + windowState.mAttrs.getTitle());
                    appWindowToken = windowState.mAppToken;
                }
            }
        }
        
        if (appWindowToken == null && inputApplicationHandle != null) {
            appWindowToken = inputApplicationHandle.appWindowToken;
            if (appWindowToken != null) {
                Slog.i(WindowManagerService.TAG,
                        "Input event dispatching timed out sending to application "
                                + appWindowToken.stringName);
            }
        }

        if (appWindowToken != null && appWindowToken.appToken != null) {
            try {
                // Notify the activity manager about the timeout and let it decide whether
                // to abort dispatching or keep waiting.
                boolean abort = appWindowToken.appToken.keyDispatchingTimedOut();
                if (! abort) {
                    // The activity manager declined to abort dispatching.
                    // Wait a bit longer and timeout again later.
                    return appWindowToken.inputDispatchingTimeoutNanos;
                }
            } catch (RemoteException ex) {
            }
        }
        return 0; // abort dispatching
    }

class AppWindowToken extends WindowToken {
    // Non-null only for application tokens.
    final IApplicationToken appToken;

其实这里的appToken实质上是一个ActivityRecord的“代理”。(需了解binder)

@ActivityRecord.java


final class ActivityRecord {
    final ActivityManagerService service; // owner
    final ActivityStack stack; // owner
    final IApplicationToken.Stub appToken; // window manager token

public boolean keyDispatchingTimedOut() {
        ActivityRecord r;
        ProcessRecord anrApp = null;
        synchronized(service) {
            r = getWaitingHistoryRecordLocked();
            if (r != null && r.app != null) {
                if (r.app.debugging) {
                    return false;
                }
                
                if (service.mDidDexOpt) {
                    // Give more time since we were dexopting.
                    service.mDidDexOpt = false;
                    return false;
                }
                
                if (r.app.instrumentationClass == null) { 
                    anrApp = r.app;
                } else {
                    Bundle info = new Bundle();
                    info.putString("shortMsg", "keyDispatchingTimedOut");
                    info.putString("longMsg", "Timed out while dispatching key event");
                    service.finishInstrumentationLocked(
                            r.app, Activity.RESULT_CANCELED, info);
                }
            }
        }
        
        if (anrApp != null) {
            service.appNotResponding(anrApp, r, this,
                    "keyDispatchingTimedOut");
        }
        
        return true;
    }

接下去就是ActivityManagerService的appNotResponding,我们平常所看到的ANR对话框正出自这里。

<think>我们正在处理关于Android 15中ANR导致界面定屏的问题。根据用户查询,需要提供解决方案或调试方法。结合之前的引用内容,我们可以总结出ANR的一般分析方法和解决思路,并针对Android 15进行说明(注意:Android 15可能引入新的特性或变化,但核心ANR分析机制类似)。 核心思路: 1. ANR产生的原因:主线程被阻塞超过一定时间(如输入事件5秒,广播10秒等)。 2. 分析步骤:收集日志(traces.txt, logcat),定位主线程堆栈,分析阻塞原因。 由于Android 15是最新版本,可能提供更先进的工具(如新的分析工具或API),但基本方法不变。我们可以从以下几个方面展开: 步骤1:确认ANR发生 - 查看系统日志(logcat)中是否有ANR记录,搜索关键字“ANR in”或“ActivityManager: ANR”。 - 检查/data/anr/目录下的traces.txt文件(需要root权限)或者使用adb bugreport命令获取完整的报告。 步骤2:分析ANR原因 - 在日志中找到发生ANR的进程和线程(通常是主线程),查看其堆栈信息。 - 分析堆栈中调用的方法,找出耗时操作(如IO操作、网络请求、数据库操作、锁竞争等)。 步骤3:解决策略 - 将耗时操作移出主线程,使用工作线程(如Thread, HandlerThread, AsyncTask, ExecutorService等)或协程(Kotlin协程)执行。 - 优化主线程中的操作,避免复杂计算和大量循环。 - 检查是否有死锁或锁竞争,特别是主线程中等待子线程锁的情况。 - 使用StrictMode帮助检测主线程中的磁盘读写和网络访问。 步骤4:使用Android 15可能的新特性或工具 - 虽然Android 15的具体特性尚未完全公布(截至2023年),但可以关注Android Studio的最新工具(如Profiler)和系统提供的ANR分析工具(如Android Vitals)的更新。 - Android 15可能提供更详细的ANR报告,例如包含CPU使用率、锁信息等。 步骤5:测试验证 - 使用模拟ANR场景的工具(如adb shell am hang)进行测试。 - 使用性能分析工具(如Systrace)监控应用性能。 根据引用[3]中的经验,不能仅凭堆栈信息完全确定问题,因为可能存在堆栈漂移。因此,需要结合代码逻辑和日志进行综合判断。 下面我们将详细展开这些步骤,并提供一些实用的命令和代码示例。 详细步骤: 1. 获取ANR日志 - 使用adb命令抓取日志: ```bash adb logcat -d > logcat.txt # 获取当前日志 adb bugreport > bugreport.zip # 获取完整的bug报告(包括traces.txt等) ``` 注意:在Android 15中,获取ANR日志的方式可能没有变化,但注意权限问题(如需要用户授权或开启开发者选项)。 2. 分析日志 - 在logcat中搜索ANR关键字,找到发生ANR的包名和原因。 - 在bugreport中,查看/data/anr/目录下的文件,特别是traces.txt,找到主线程的堆栈。 3. 代码检查 - 检查主线程中是否有耗时操作,例如: - 文件读写(特别是大文件) - 网络请求(必须放在后台线程) - 数据库操作(特别是大量数据操作) - 复杂计算(如图像处理、加密解密等) - 使用StrictMode检测主线程中的磁盘和网络操作(在Application或Activity的onCreate中设置): ```java if (BuildConfig.DEBUG) { StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() .detectDiskReads() .detectDiskWrites() .detectNetwork() .penaltyLog() .build()); } ``` 4. 使用性能分析工具 - Android Studio Profiler:可以监控CPU、内存等,查看主线程的耗时方法。 - Systrace:系统级跟踪工具,可以查看线程状态(如运行、休眠、等待锁等): ```bash python systrace.py -o mytrace.html sched gfx view wm am app ``` 然后使用浏览器打开mytrace.html,搜索主线程(通常名为包名+主线程名),查看ANR时间段内的状态。 5. 解决常见问题 - 如果发现主线程中有网络请求,应使用后台线程或协程: ```kotlin // Kotlin协程示例 lifecycleScope.launch(Dispatchers.IO) { // 网络请求 val result = apiService.getData() withContext(Dispatchers.Main) { // 更新UI } } ``` - 如果主线程中有数据库操作,考虑使用Room数据库并配合协程或LiveData进行异步查询。 6. 避免ANR的最佳实践 - 使用IntentService处理长时间后台任务。 - 对于广播接收器,避免在onReceive()中执行耗时操作,使用goAsync()或启动Service处理。 - 优化布局,避免过度绘制和复杂的布局嵌套。 7. Android 15可能的新特性 - 关注Android 15的开发者文档,看是否有新的ANR分析工具或API。 - 使用Android Vitals(在Google Play控制台)分析ANR问题。 注意:由于Android 15尚未广泛使用(截至2023年),上述方法基于之前版本的通用方法。当Android 15发布后,应查阅官方文档以获取最新信息。 相关问题: 1. 如何在没有root权限的情况下获取ANR日志? 2. 如何使用Systrace分析ANR问题? 3. 在Android 15中,ANR超时时间是否有变化? 请根据上述步骤尝试解决ANR问题,如果遇到具体问题,可进一步分析。</think>### Android 15 ANR导致界面定屏的解决方法 当Android 15应用发生ANRApplication Not Responding)导致界面定屏时,可通过以下系统化方法诊断和解决: --- ### **1. 确认ANR触发条件** [^1] - **输入事件超时**:主线程5秒内未响应触摸/按键事件 - **广播超时**:BroadcastReceiver 10秒内未完成执行 - **服务超时**:Service前台20秒/后台200秒未完成启动 ```java // 示例:主线程模拟阻塞(实际需避免) public void onClick(View v) { SystemClock.sleep(6000); // 超过5秒触发ANR } ``` --- ### **2. 关键日志分析** #### (1) 获取ANR日志 ```bash adb pull /data/anr/traces.txt # 直接获取ANR堆栈 adb bugreport # 完整系统报告(含CPU/线程状态) ``` #### (2) 分析重点(logcat搜索)[^2] ```log E/ActivityManager: ANR in com.example.app (主线程堆栈) W/Binder: Blocked thread waiting on lock (锁竞争提示) I/Choreographer: Skipped 60 frames (UI渲染阻塞) ``` --- ### **3. 主线程阻塞定位** #### 典型场景及解决方案: | **阻塞类型** | **解决方案** | **代码示例** | |-------------------|---------------------------------------------|---------------------------------------------| | **文件/网络IO** | 迁移到WorkManager或协程 | `Dispatchers.IO`执行耗时操作 | | **复杂计算** | 使用`ThreadPoolExecutor`异步处理 | `executor.execute(() -> heavyCalculation())`| | **跨进程死锁** | 检查`Binder`调用超时(添加超时机制) | `binderProxy.linkToDeath()` | | **数据库锁竞争** | 优化Room查询/使用异步查询API | `@Query` + `LiveData` | --- ### **4. Android 15新特性适配** #### (1) **后台限制增强** ```xml <!-- AndroidManifest.xml声明前台服务类型 --> <service android:name=".MyService" android:foregroundServiceType="location|camera" /> ``` #### (2) **ANR诊断工具升级** - **`StrictMode`增强检测**: ```java StrictMode.setVmPolicy(new VmPolicy.Builder() .detectLeakedClosableObjects() .penaltyLog() .build()); ``` - **`ANRWatchDog`库**:实时监控主线程阻塞 ```gradle implementation 'com.github.anrwatchdog:anrwatchdog:1.4.0' ``` --- ### **5. 高级调试技巧** #### (1) Systrace性能分析 ```bash python systrace.py --app=com.example.app sched gfx view wm am ``` ![Systrace示例](https://developer.android.com/static/images/tools/performance/systrace-cpu-usage.png) _主线程阻塞段显示红色警告_ #### (2) 模拟ANR测试 ```bash adb shell am hang --allow-restart # 强制触发ANR场景 ``` --- ### **6. 预防机制** 1. **主线程监控**: ```java Handler handler = new Handler(Looper.getMainLooper()); handler.postDelayed(() -> { throw new RuntimeException("MainThread blocked!"); }, 5000); // 5秒阈值 ``` 2. **异步初始化**: ```kotlin class MyApp : Application() { override fun onCreate() { super.onCreate() CoroutineScope(Dispatchers.Default).launch { initSDK() // 异步初始化 } } } ``` > **关键提示**:Android 15强化了[后台服务限制](https://developer.android.com/about/versions/15/behavior-changes-15),ANR分析需结合`adb shell dumpsys activity processes`检查进程状态[^3]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值