diff --git a/app/src/main/java/com/fankes/apperrorstracking/bean/AppErrorsInfoBean.kt b/app/src/main/java/com/fankes/apperrorstracking/bean/AppErrorsInfoBean.kt index 86cc4b8..fe2e7e5 100644 --- a/app/src/main/java/com/fankes/apperrorstracking/bean/AppErrorsInfoBean.kt +++ b/app/src/main/java/com/fankes/apperrorstracking/bean/AppErrorsInfoBean.kt @@ -32,6 +32,7 @@ import java.util.* /** * 应用异常信息 bean + * @param userId 用户 ID * @param packageName 包名 * @param isNativeCrash 是否为原生层异常 * @param exceptionClassName 异常类名 @@ -44,6 +45,7 @@ import java.util.* * @param timestamp 记录时间戳 */ data class AppErrorsInfoBean( + @Keep var userId: Int, @Keep var packageName: String, @Keep var isNativeCrash: Boolean, @Keep var exceptionClassName: String, @@ -61,12 +63,14 @@ data class AppErrorsInfoBean( /** * 从 [ApplicationErrorReport.CrashInfo] 克隆 * @param packageName APP 包名 + * @param userId APP 用户 ID * @param crashInfo [ApplicationErrorReport.CrashInfo] * @return [AppErrorsInfoBean] */ - fun clone(packageName: String?, crashInfo: ApplicationErrorReport.CrashInfo?) = + fun clone(packageName: String?, userId: Int?, crashInfo: ApplicationErrorReport.CrashInfo?) = (crashInfo?.exceptionClassName?.lowercase() == "native crash").let { isNativeCrash -> AppErrorsInfoBean( + userId = userId ?: 0, packageName = packageName ?: "unknown", isNativeCrash = isNativeCrash, exceptionClassName = crashInfo?.exceptionClassName ?: "unknown", @@ -122,6 +126,7 @@ data class AppErrorsInfoBean( "[API Version]: ${Build.VERSION.SDK_INT}\n" + "[System Locale]: ${Locale.getDefault()}\n" + "[Package Name]: $packageName\n" + + (if (userId > 0) "[User Id]: $userId\n" else "") + "[Error Type]: ${if (isNativeCrash) "Native" else "Jvm"}\n" + "[Crash Time]: $dateTime\n" + "[Stack Trace]:\n" + stackTrace @@ -142,6 +147,7 @@ data class AppErrorsInfoBean( "[API Version]: ${Build.VERSION.SDK_INT}\n" + "[System Locale]: ${Locale.getDefault()}\n" + "[Package Name]: $packageName\n" + + (if (userId > 0) "[User Id]: $userId\n" else "") + "[Error Type]: ${if (isNativeCrash) "Native" else "Jvm"}\n" + "[Crash Time]: $dateTime\n" + "[Stack Trace]:\n" + stackTrace diff --git a/app/src/main/java/com/fankes/apperrorstracking/hook/entity/FrameworkHooker.kt b/app/src/main/java/com/fankes/apperrorstracking/hook/entity/FrameworkHooker.kt index c02940a..132ef3e 100644 --- a/app/src/main/java/com/fankes/apperrorstracking/hook/entity/FrameworkHooker.kt +++ b/app/src/main/java/com/fankes/apperrorstracking/hook/entity/FrameworkHooker.kt @@ -75,6 +75,9 @@ object FrameworkHooker : YukiBaseHooker() { "com.android.server.am.ErrorDialogController" ) + /** 已记录的 APP 用户 ID */ + private var appUserIdRecords = HashMap() + /** 已忽略错误的 APP 数组 - 直到重新解锁 */ private var mutedErrorsIfUnlockApps = HashSet() @@ -250,10 +253,12 @@ object FrameworkHooker : YukiBaseHooker() { /** 崩溃标题 */ val errorTitle = if (isRepeating) LocaleString.aerrRepeatedTitle(appName) else LocaleString.aerrTitle(appName) + /** 写入到用户 ID 记录 */ + appUserIdRecords[proc.toString()] = userId /** 打印错误日志 */ if (isApp) loggerE( msg = "App \"$packageName\"${if (packageName != processName) " --process \"$processName\"" else ""}" + - " has crashed${if (isRepeating) " again" else ""}" + "${if (userId != 0) " --user $userId" else ""} has crashed${if (isRepeating) " again" else ""}" ) else loggerE(msg = "Process \"$processName\" has crashed${if (isRepeating) " again" else ""}") /** 判断是否为模块自身崩溃 */ if (packageName == BuildConfig.APPLICATION_ID) { @@ -308,10 +313,15 @@ object FrameworkHooker : YukiBaseHooker() { paramCount = 2 } afterHook { + /** 当前进程信息 */ + val proc = args().first().any() + /** 当前 APP 信息 */ - val appInfo = ProcessRecordClass.toClass().field { name = "info" }.get(args().first().any()).cast() + val appInfo = ProcessRecordClass.toClass().field { name = "info" }.get(proc).cast() /** 添加当前异常信息到第一位 */ - appErrorsRecords.add(0, AppErrorsInfoBean.clone(appInfo?.packageName, args().last().cast())) + appErrorsRecords.add( + 0, AppErrorsInfoBean.clone(appInfo?.packageName, appUserIdRecords[proc.toString()], args().last().cast()) + ) /** 保存异常记录到本地 */ saveAllAppErrorsRecords() } diff --git a/app/src/main/java/com/fankes/apperrorstracking/ui/activity/errors/AppErrorsDetailActivity.kt b/app/src/main/java/com/fankes/apperrorstracking/ui/activity/errors/AppErrorsDetailActivity.kt index 17f52ef..a4d4f57 100644 --- a/app/src/main/java/com/fankes/apperrorstracking/ui/activity/errors/AppErrorsDetailActivity.kt +++ b/app/src/main/java/com/fankes/apperrorstracking/ui/activity/errors/AppErrorsDetailActivity.kt @@ -27,6 +27,7 @@ import android.app.Activity import android.content.Context import android.content.Intent import androidx.core.view.isGone +import androidx.core.view.isVisible import com.fankes.apperrorstracking.R import com.fankes.apperrorstracking.bean.AppErrorsInfoBean import com.fankes.apperrorstracking.databinding.ActivityAppErrorsDetailBinding @@ -86,6 +87,8 @@ class AppErrorsDetailActivity : BaseActivity() { binding.appIcon.setImageDrawable(appIconOf(appErrorsInfo.packageName)) binding.appNameText.text = appNameOf(appErrorsInfo.packageName) binding.appVersionText.text = appVersionBrandOf(appErrorsInfo.packageName) + binding.appUserIdText.isVisible = appErrorsInfo.userId > 0 + binding.appUserIdText.text = LocaleString.userId(appErrorsInfo.userId) binding.appAbiText.text = appCpuAbiOf(appErrorsInfo.packageName).ifBlank { LocaleString.noCpuAbi } binding.jvmErrorPanel.isGone = appErrorsInfo.isNativeCrash binding.errorTypeIcon.setImageResource(if (appErrorsInfo.isNativeCrash) R.drawable.ic_cpp else R.drawable.ic_java) diff --git a/app/src/main/java/com/fankes/apperrorstracking/ui/activity/errors/AppErrorsRecordActivity.kt b/app/src/main/java/com/fankes/apperrorstracking/ui/activity/errors/AppErrorsRecordActivity.kt index beb5257..8cb1a24 100644 --- a/app/src/main/java/com/fankes/apperrorstracking/ui/activity/errors/AppErrorsRecordActivity.kt +++ b/app/src/main/java/com/fankes/apperrorstracking/ui/activity/errors/AppErrorsRecordActivity.kt @@ -134,6 +134,8 @@ class AppErrorsRecordActivity : BaseActivity() { listData[position].also { bean -> binding.appIcon.setImageDrawable(appIconOf(bean.packageName)) binding.appNameText.text = appNameOf(bean.packageName) + binding.appUserIdText.isVisible = bean.userId > 0 + binding.appUserIdText.text = LocaleString.userId(bean.userId) binding.errorsTimeText.text = bean.crossTime binding.errorTypeIcon.setImageResource(if (bean.isNativeCrash) R.drawable.ic_cpp else R.drawable.ic_java) binding.errorTypeText.text = if (bean.isNativeCrash) "Native crash" else bean.exceptionClassName.simpleThwName() diff --git a/app/src/main/res/drawable/bg_blue_round.xml b/app/src/main/res/drawable/bg_blue_round.xml new file mode 100644 index 0000000..893f5d8 --- /dev/null +++ b/app/src/main/res/drawable/bg_blue_round.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_app_errors_detail.xml b/app/src/main/res/layout/activity_app_errors_detail.xml index 69b666d..1e9fa1e 100644 --- a/app/src/main/res/layout/activity_app_errors_detail.xml +++ b/app/src/main/res/layout/activity_app_errors_detail.xml @@ -151,6 +151,23 @@ android:textColor="@color/colorTextGray" android:textSize="15sp" /> + + - + android:gravity="center|start"> + + + + +