6 Commits

Author SHA1 Message Date
copilot-swe-agent[bot]
b89a4f3550 Fix ComponentName property access in cloneAnr method
Changed from accessing non-existent className/packageName properties
to using flattenToShortString() method which returns the component
name in a standard format. This fixes the Kotlin compilation errors:
- Unresolved reference 'className'
- Unresolved reference 'packageName'

Co-authored-by: fankes <37344460+fankes@users.noreply.github.com>
2026-01-16 03:43:39 +00:00
copilot-swe-agent[bot]
c4108069a9 Fix build failure: add explicit String type to suffix parameter
The build was failing because the Gradle Kotlin DSL couldn't infer
the type of the 'suffix' parameter in the lambda, causing
'isNotBlank()' method to be unresolved.

Fixed by explicitly typing the lambda parameter as String:
`{ suffix: String -> ... }` instead of `{ suffix -> ... }`

Co-authored-by: fankes <37344460+fankes@users.noreply.github.com>
2026-01-16 03:33:32 +00:00
copilot-swe-agent[bot]
3cfb1dc950 Fix indentation in locale strings files
Ensure ANR string entries use consistent 2-space indentation
matching the rest of the file in all locale variants.

Co-authored-by: NextAlone <12210746+NextAlone@users.noreply.github.com>
2026-01-15 19:09:04 +00:00
copilot-swe-agent[bot]
3cc37a68ee Fix Traditional Chinese translations for ANR strings
Use proper Traditional Chinese characters:
- 无响应 → 無回應
- 屡次 → 屢次

Applied to zh-rHK, zh-rMO, and zh-rTW locales.

Co-authored-by: NextAlone <12210746+NextAlone@users.noreply.github.com>
2026-01-15 19:07:40 +00:00
copilot-swe-agent[bot]
94bc3dc066 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>
2026-01-15 19:05:03 +00:00
copilot-swe-agent[bot]
fd2c563089 Initial plan 2026-01-15 18:57:04 +00:00
9 changed files with 95 additions and 4 deletions

View File

@@ -59,7 +59,7 @@ androidComponents {
// Workaround for GitHub Actions.
// Strongly transfer type to [String].
@Suppress("UNNECESSARY_SAFE_CALL")
val currentSuffix = gropify.github.ci.commit.id?.let { suffix ->
val currentSuffix = gropify.github.ci.commit.id?.let { suffix: String ->
if (suffix.isNotBlank()) "-$suffix" else ""
}
val currentVersion = "${output.versionName.get()}$currentSuffix(${output.versionCode.get()})"

View File

@@ -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?.flattenToShortString() ?: "unknown",
throwClassName = 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"

View File

@@ -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<Context>() ?: 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<Context>() ?: 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())
}
}
}
}

View File

@@ -10,6 +10,8 @@
<string name="close_app">アプリを閉じる</string>
<string name="aerr_title">%1$s が停止しました</string>
<string name="aerr_repeated_title">%1$s が繰り返し停止しています</string>
<string name="anr_title">%1$s は応答していません</string>
<string name="anr_repeated_title">%1$s は繰り返し応答していません</string>
<string name="mute_if_unlock_tip">デバイスのロックが解除されるまで「%1$s」のエラーをミュートにします</string>
<string name="mute_if_restart_tip">デバイスが再起動されるまで「%1$s」のエラーをミュートにします</string>
<string name="back">戻る</string>

View File

@@ -10,6 +10,8 @@
<string name="close_app">关闭应用</string>
<string name="aerr_title">%1$s 已停止运行</string>
<string name="aerr_repeated_title">%1$s 屡次停止运行</string>
<string name="anr_title">%1$s 无响应</string>
<string name="anr_repeated_title">%1$s 屡次无响应</string>
<string name="mute_if_unlock_tip">忽略“%1$s”的错误直到设备重新解锁</string>
<string name="mute_if_restart_tip">忽略“%1$s”的错误直到设备重新启动</string>
<string name="back">返回</string>

View File

@@ -10,6 +10,8 @@
<string name="close_app">結束程式</string>
<string name="aerr_title">%1$s 已停止運作</string>
<string name="aerr_repeated_title">%1$s 屢次停止運作</string>
<string name="anr_title">%1$s 無回應</string>
<string name="anr_repeated_title">%1$s 屢次無回應</string>
<string name="mute_if_unlock_tip">忽略“%1$s”的錯誤直到設備重新開屏</string>
<string name="mute_if_restart_tip">忽略“%1$s”的錯誤直到設備重新開機</string>
<string name="back">回退</string>

View File

@@ -10,6 +10,8 @@
<string name="close_app">結束程式</string>
<string name="aerr_title">%1$s 已停止運作</string>
<string name="aerr_repeated_title">%1$s 屢次停止運作</string>
<string name="anr_title">%1$s 無回應</string>
<string name="anr_repeated_title">%1$s 屢次無回應</string>
<string name="mute_if_unlock_tip">忽略“%1$s”的錯誤直到設備重新開屏</string>
<string name="mute_if_restart_tip">忽略“%1$s”的錯誤直到設備重新開機</string>
<string name="back">回退</string>

View File

@@ -10,6 +10,8 @@
<string name="close_app">結束程式</string>
<string name="aerr_title">%1$s 已停止運作</string>
<string name="aerr_repeated_title">%1$s 屢次停止運作</string>
<string name="anr_title">%1$s 無回應</string>
<string name="anr_repeated_title">%1$s 屢次無回應</string>
<string name="mute_if_unlock_tip">忽略“%1$s”的錯誤直到裝置重新展示</string>
<string name="mute_if_restart_tip">忽略“%1$s”的錯誤直到裝置重新開機</string>
<string name="back">回退</string>

View File

@@ -10,6 +10,8 @@
<string name="close_app">Close App</string>
<string name="aerr_title">%1$s has stopped</string>
<string name="aerr_repeated_title">%1$s keeps stopping</string>
<string name="anr_title">%1$s isn\'t responding</string>
<string name="anr_repeated_title">%1$s keeps not responding</string>
<string name="mute_if_unlock_tip">Muted errors for \'%1$s\' until device is re-unlocked</string>
<string name="mute_if_restart_tip">Muted errors for \'%1$s\' until device reboots</string>
<string name="back">Back</string>