From 94bc3dc066f6627a0d2ac9f5451813027541199b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 15 Jan 2026 19:05:03 +0000 Subject: [PATCH] Add ANR (Application Not Responding) tracking support - Add isAnr flag to AppErrorsInfoBean data model - Add cloneAnr() method to create ANR error records from ApplicationErrorReport.AnrInfo - Hook appNotResponding() and handleAnrInActivityController() methods in framework - Add ANR-specific error titles in all supported languages (EN, ZH-CN, ZH-HK, ZH-MO, ZH-TW, JA) - Update error type display to show "ANR" for ANR errors - Add handleAppAnrInfo() method to record ANR data Co-authored-by: NextAlone <12210746+NextAlone@users.noreply.github.com> --- .../bean/AppErrorsInfoBean.kt | 40 ++++++++++++++++- .../hook/entity/FrameworkHooker.kt | 45 ++++++++++++++++++- module-app/src/main/res/values-ja/strings.xml | 2 + .../src/main/res/values-zh-rCN/strings.xml | 2 + .../src/main/res/values-zh-rHK/strings.xml | 2 + .../src/main/res/values-zh-rMO/strings.xml | 2 + .../src/main/res/values-zh-rTW/strings.xml | 2 + module-app/src/main/res/values/strings.xml | 2 + 8 files changed, 94 insertions(+), 3 deletions(-) diff --git a/module-app/src/main/java/com/fankes/apperrorstracking/bean/AppErrorsInfoBean.kt b/module-app/src/main/java/com/fankes/apperrorstracking/bean/AppErrorsInfoBean.kt index 46233d0..a673d47 100644 --- a/module-app/src/main/java/com/fankes/apperrorstracking/bean/AppErrorsInfoBean.kt +++ b/module-app/src/main/java/com/fankes/apperrorstracking/bean/AppErrorsInfoBean.kt @@ -50,6 +50,7 @@ import java.util.Locale * @param targetSdk 目标 SDK 版本 * @param minSdk 最低 SDK 版本 * @param isNativeCrash 是否为原生层异常 + * @param isAnr 是否为 ANR (Application Not Responding) * @param exceptionClassName 异常类名 * @param exceptionMessage 异常信息 * @param throwClassName 抛出异常的类名 @@ -78,6 +79,8 @@ data class AppErrorsInfoBean( var minSdk: Int = -1, @SerializedName("isNativeCrash") var isNativeCrash: Boolean = false, + @SerializedName("isAnr") + var isAnr: Boolean = false, @SerializedName("exceptionClassName") var exceptionClassName: String = "", @SerializedName("exceptionMessage") @@ -134,6 +137,37 @@ data class AppErrorsInfoBean( timestamp = System.currentTimeMillis() ) } + + /** + * 从 [ApplicationErrorReport.AnrInfo] 克隆 + * @param context 当前实例 + * @param pid APP 进程 ID + * @param userId APP 用户 ID + * @param packageName APP 包名 + * @param anrInfo [ApplicationErrorReport.AnrInfo] + * @return [AppErrorsInfoBean] + */ + fun cloneAnr(context: Context, pid: Int, userId: Int, packageName: String?, anrInfo: ApplicationErrorReport.AnrInfo?) = + AppErrorsInfoBean( + pid = pid, + userId = userId, + cpuAbi = packageName?.let { context.appCpuAbiOf(it) } ?: "", + packageName = packageName ?: "unknown", + versionName = packageName?.let { context.appVersionNameOf(it).ifBlank { "unknown" } } ?: "", + versionCode = packageName?.let { context.appVersionCodeOf(it) } ?: -1L, + targetSdk = packageName?.let { context.appTargetSdkOf(it) } ?: -1, + minSdk = packageName?.let { context.appMinSdkOf(it) } ?: -1, + isNativeCrash = false, + isAnr = true, + exceptionClassName = "ANR", + exceptionMessage = anrInfo?.cause ?: "Application Not Responding", + throwFileName = anrInfo?.activity?.className ?: "unknown", + throwClassName = anrInfo?.activity?.packageName ?: packageName ?: "unknown", + throwMethodName = "unknown", + throwLineNumber = -1, + stackTrace = anrInfo?.info?.trim() ?: "unknown", + timestamp = System.currentTimeMillis() + ) } /** @@ -248,7 +282,11 @@ data class AppErrorsInfoBean( [Version Code]: ${versionCode.takeIf { it != -1L } ?: "unknown"} [Target SDK]: ${targetSdk.takeIf { it != -1 } ?: "unknown"} [Min SDK]: ${minSdk.takeIf { it != -1 } ?: "unknown"} - [Error Type]: ${if (isNativeCrash) "Native" else "JVM"} + [Error Type]: ${when { + isAnr -> "ANR" + isNativeCrash -> "Native" + else -> "JVM" + }} [Crash Time]: $utcTime [Stack Trace]: """.trimIndent() + "\n$stackTrace" diff --git a/module-app/src/main/java/com/fankes/apperrorstracking/hook/entity/FrameworkHooker.kt b/module-app/src/main/java/com/fankes/apperrorstracking/hook/entity/FrameworkHooker.kt index fb7b0f7..f19b6ad 100644 --- a/module-app/src/main/java/com/fankes/apperrorstracking/hook/entity/FrameworkHooker.kt +++ b/module-app/src/main/java/com/fankes/apperrorstracking/hook/entity/FrameworkHooker.kt @@ -98,8 +98,9 @@ object FrameworkHooker : YukiBaseHooker() { * @param errors [AppErrorsClass] 实例 * @param proc [ProcessRecordClass] 实例 * @param resultData [AppErrorDialog_DataClass] 实例 - 默认空 + * @param isAnr 是否为 ANR - 默认 false */ - private class AppErrorsProcessData(errors: Any?, proc: Any?, resultData: Any? = null) { + private class AppErrorsProcessData(errors: Any?, proc: Any?, resultData: Any? = null, val isAnr: Boolean = false) { /** * 获取当前包列表实例 @@ -330,7 +331,12 @@ object FrameworkHooker : YukiBaseHooker() { val appNameWithUserId = if (userId != 0) "$appName (${locale.userId(userId)})" else appName /** 崩溃标题 */ - val errorTitle = if (isRepeatingCrash) locale.aerrRepeatedTitle(appNameWithUserId) else locale.aerrTitle(appNameWithUserId) + val errorTitle = when { + isAnr && isRepeatingCrash -> locale.anrRepeatedTitle(appNameWithUserId) + isAnr -> locale.anrTitle(appNameWithUserId) + isRepeatingCrash -> locale.aerrRepeatedTitle(appNameWithUserId) + else -> locale.aerrTitle(appNameWithUserId) + } /** 使用通知推送异常信息 */ fun showAppErrorsWithNotify() = @@ -408,6 +414,16 @@ object FrameworkHooker : YukiBaseHooker() { YLog.info("Received crash application data${if (userId != 0) " --user $userId" else ""} --pid $pid") } + /** + * 处理 APP 进程 ANR 数据 + * @param context 当前实例 + * @param info ANR 错误报告数据实例 + */ + private fun AppErrorsProcessData.handleAppAnrInfo(context: Context, info: ApplicationErrorReport.AnrInfo?) { + AppErrorsRecordData.add(AppErrorsInfoBean.cloneAnr(context, pid, userId, appInfo?.packageName, info)) + YLog.info("Received ANR application data${if (userId != 0) " --user $userId" else ""} --pid $pid") + } + override fun onHook() { /** 注册生命周期 */ registerLifecycle() @@ -502,6 +518,31 @@ object FrameworkHooker : YukiBaseHooker() { /** 创建 APP 进程异常数据类 */ AppErrorsProcessData(instance, proc).handleAppErrorsInfo(context, args(index = 1).cast()) } + /** Hook ANR handling methods */ + firstMethodOrNull { + name = "appNotResponding" + }?.hook()?.after { + /** 当前实例 */ + val context = appContext ?: firstFieldOrNull { name = "mContext" }?.of(instance)?.get() ?: return@after + + /** 当前进程信息 - 第一个参数是 ProcessRecord */ + val proc = args().first().any() ?: return@after YLog.warn("Received ANR but got null ProcessRecord") + + /** 创建 APP 进程异常数据类并展示 ANR UI */ + AppErrorsProcessData(instance, proc, isAnr = true).handleShowAppErrorUi(context) + } + firstMethodOrNull { + name = "handleAnrInActivityController" + returnType = Boolean::class + }?.hook()?.after { + /** 当前实例 */ + val context = appContext ?: firstFieldOrNull { name = "mContext" }?.of(instance)?.get() ?: return@after + + /** 当前进程信息 */ + val proc = args().first().any() ?: return@after YLog.warn("Received ANR but got null ProcessRecord") + /** 创建 ANR 数据 - args(1) 应该包含 AnrInfo */ + AppErrorsProcessData(instance, proc, isAnr = true).handleAppAnrInfo(context, args(index = 1).cast()) + } } } } \ No newline at end of file diff --git a/module-app/src/main/res/values-ja/strings.xml b/module-app/src/main/res/values-ja/strings.xml index 627a136..ceb693b 100644 --- a/module-app/src/main/res/values-ja/strings.xml +++ b/module-app/src/main/res/values-ja/strings.xml @@ -10,6 +10,8 @@ アプリを閉じる %1$s が停止しました %1$s が繰り返し停止しています + %1$s は応答していません + %1$s は繰り返し応答していません デバイスのロックが解除されるまで「%1$s」のエラーをミュートにします デバイスが再起動されるまで「%1$s」のエラーをミュートにします 戻る diff --git a/module-app/src/main/res/values-zh-rCN/strings.xml b/module-app/src/main/res/values-zh-rCN/strings.xml index ba2f9f8..9410ee9 100644 --- a/module-app/src/main/res/values-zh-rCN/strings.xml +++ b/module-app/src/main/res/values-zh-rCN/strings.xml @@ -10,6 +10,8 @@ 关闭应用 %1$s 已停止运行 %1$s 屡次停止运行 + %1$s 无响应 + %1$s 屡次无响应 忽略“%1$s”的错误直到设备重新解锁 忽略“%1$s”的错误直到设备重新启动 返回 diff --git a/module-app/src/main/res/values-zh-rHK/strings.xml b/module-app/src/main/res/values-zh-rHK/strings.xml index b4ec46e..6ae232b 100644 --- a/module-app/src/main/res/values-zh-rHK/strings.xml +++ b/module-app/src/main/res/values-zh-rHK/strings.xml @@ -10,6 +10,8 @@ 結束程式 %1$s 已停止運作 %1$s 屢次停止運作 + %1$s 无响应 + %1$s 屡次无响应 忽略“%1$s”的錯誤直到設備重新開屏 忽略“%1$s”的錯誤直到設備重新開機 回退 diff --git a/module-app/src/main/res/values-zh-rMO/strings.xml b/module-app/src/main/res/values-zh-rMO/strings.xml index 650c468..44a11f5 100644 --- a/module-app/src/main/res/values-zh-rMO/strings.xml +++ b/module-app/src/main/res/values-zh-rMO/strings.xml @@ -10,6 +10,8 @@ 結束程式 %1$s 已停止運作 %1$s 屢次停止運作 + %1$s 无响应 + %1$s 屡次无响应 忽略“%1$s”的錯誤直到設備重新開屏 忽略“%1$s”的錯誤直到設備重新開機 回退 diff --git a/module-app/src/main/res/values-zh-rTW/strings.xml b/module-app/src/main/res/values-zh-rTW/strings.xml index 549a57a..33353dd 100644 --- a/module-app/src/main/res/values-zh-rTW/strings.xml +++ b/module-app/src/main/res/values-zh-rTW/strings.xml @@ -10,6 +10,8 @@ 結束程式 %1$s 已停止運作 %1$s 屢次停止運作 + %1$s 无响应 + %1$s 屡次无响应 忽略“%1$s”的錯誤直到裝置重新展示 忽略“%1$s”的錯誤直到裝置重新開機 回退 diff --git a/module-app/src/main/res/values/strings.xml b/module-app/src/main/res/values/strings.xml index 9ae8ace..0efb028 100644 --- a/module-app/src/main/res/values/strings.xml +++ b/module-app/src/main/res/values/strings.xml @@ -10,6 +10,8 @@ Close App %1$s has stopped %1$s keeps stopping + %1$s isn\'t responding + %1$s keeps not responding Muted errors for \'%1$s\' until device is re-unlocked Muted errors for \'%1$s\' until device reboots Back