mirror of
https://github.com/KitsunePie/AppErrorsTracking.git
synced 2026-02-04 12:17:22 +08:00
Compare commits
6 Commits
master
...
b89a4f3550
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b89a4f3550 | ||
|
|
c4108069a9 | ||
|
|
3cfb1dc950 | ||
|
|
3cc37a68ee | ||
|
|
94bc3dc066 | ||
|
|
fd2c563089 |
@@ -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()})"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user