mirror of
https://github.com/KitsunePie/AppErrorsTracking.git
synced 2025-09-04 02:05:16 +08:00
Modify merge app errors functions implementation code to AppErrorsData in FrameworkHooker
This commit is contained in:
@@ -23,6 +23,7 @@
|
|||||||
|
|
||||||
package com.fankes.apperrorstracking.hook.entity
|
package com.fankes.apperrorstracking.hook.entity
|
||||||
|
|
||||||
|
import android.app.ApplicationErrorReport
|
||||||
import android.app.Dialog
|
import android.app.Dialog
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
@@ -39,6 +40,7 @@ import com.fankes.apperrorstracking.bean.AppErrorsInfoBean
|
|||||||
import com.fankes.apperrorstracking.bean.AppInfoBean
|
import com.fankes.apperrorstracking.bean.AppInfoBean
|
||||||
import com.fankes.apperrorstracking.bean.MutedErrorsAppBean
|
import com.fankes.apperrorstracking.bean.MutedErrorsAppBean
|
||||||
import com.fankes.apperrorstracking.data.ConfigData
|
import com.fankes.apperrorstracking.data.ConfigData
|
||||||
|
import com.fankes.apperrorstracking.data.factory.isAppShowErrorsDialog
|
||||||
import com.fankes.apperrorstracking.data.factory.isAppShowErrorsNotify
|
import com.fankes.apperrorstracking.data.factory.isAppShowErrorsNotify
|
||||||
import com.fankes.apperrorstracking.data.factory.isAppShowErrorsToast
|
import com.fankes.apperrorstracking.data.factory.isAppShowErrorsToast
|
||||||
import com.fankes.apperrorstracking.data.factory.isAppShowNothing
|
import com.fankes.apperrorstracking.data.factory.isAppShowNothing
|
||||||
@@ -88,6 +90,88 @@ object FrameworkHooker : YukiBaseHooker() {
|
|||||||
/** 已记录的 APP 异常信息数组 */
|
/** 已记录的 APP 异常信息数组 */
|
||||||
private var appErrorsRecords = ArrayList<AppErrorsInfoBean>()
|
private var appErrorsRecords = ArrayList<AppErrorsInfoBean>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* APP 进程异常数据定义类
|
||||||
|
* @param errors [AppErrorsClass] 实例
|
||||||
|
* @param proc [ProcessRecordClass] 实例
|
||||||
|
* @param resultData [AppErrorDialog_DataClass] 实例 - 默认空
|
||||||
|
*/
|
||||||
|
private class AppErrorsData(errors: Any?, proc: Any?, resultData: Any? = null) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前包列表实例
|
||||||
|
* @return [Any] or null
|
||||||
|
*/
|
||||||
|
private val pkgList = if (ProcessRecordClass.toClass().hasMethod { name = "getPkgList"; emptyParam() })
|
||||||
|
ProcessRecordClass.toClass().method { name = "getPkgList"; emptyParam() }.get(proc).call()
|
||||||
|
else ProcessRecordClass.toClass().field { name = "pkgList" }.get(proc).any()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前包列表数组大小
|
||||||
|
* @return [Int]
|
||||||
|
*/
|
||||||
|
private val pkgListSize = PackageListClass.toClassOrNull()?.method { name = "size"; emptyParam() }?.get(pkgList)?.int()
|
||||||
|
?: ProcessRecordClass.toClass().field { name = "pkgList" }.get(proc).cast<ArrayMap<*, *>>()?.size ?: -1
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前 pid 信息
|
||||||
|
* @return [Int]
|
||||||
|
*/
|
||||||
|
val pid = ProcessRecordClass.toClass().field { name { it == "mPid" || it == "pid" } }.get(proc).int()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前用户 ID 信息
|
||||||
|
* @return [Int]
|
||||||
|
*/
|
||||||
|
val userId = ProcessRecordClass.toClass().field { name = "userId" }.get(proc).int()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前 APP 信息
|
||||||
|
* @return [ApplicationInfo] or null
|
||||||
|
*/
|
||||||
|
val appInfo = ProcessRecordClass.toClass().field { name = "info" }.get(proc).cast<ApplicationInfo>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前进程名称
|
||||||
|
* @return [String]
|
||||||
|
*/
|
||||||
|
val processName = ProcessRecordClass.toClass().field { name = "processName" }.get(proc).string()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前 APP、进程 包名
|
||||||
|
* @return [String]
|
||||||
|
*/
|
||||||
|
val packageName = appInfo?.packageName ?: processName
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前进程是否为可被启动的 APP - 非框架 APP
|
||||||
|
* @return [Boolean]
|
||||||
|
*/
|
||||||
|
val isActualApp = pkgListSize == 1 && appInfo != null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前进程是否为主进程
|
||||||
|
* @return [Boolean]
|
||||||
|
*/
|
||||||
|
val isMainProcess = packageName == processName
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前进程是否为后台进程
|
||||||
|
* @return [Boolean]
|
||||||
|
*/
|
||||||
|
val isBackgroundProcess = UserControllerClass.toClass()
|
||||||
|
.method { name { it == "getCurrentProfileIds" || it == "getCurrentProfileIdsLocked" } }
|
||||||
|
.get(ActivityManagerServiceClass.toClass().field { name = "mUserController" }
|
||||||
|
.get(AppErrorsClass.toClass().field { name = "mService" }.get(errors).any()).any())
|
||||||
|
.invoke<IntArray>()?.takeIf { it.isNotEmpty() }?.any { it != userId } ?: false
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前进程是否短时内重复崩溃
|
||||||
|
* @return [Boolean]
|
||||||
|
*/
|
||||||
|
val isRepeatingCrash = resultData?.let { AppErrorDialog_DataClass.toClass().field { name = "repeating" }.get(it).boolean() } ?: false
|
||||||
|
}
|
||||||
|
|
||||||
/** 注册生命周期 */
|
/** 注册生命周期 */
|
||||||
private fun registerLifecycle() {
|
private fun registerLifecycle() {
|
||||||
onAppLifecycle {
|
onAppLifecycle {
|
||||||
@@ -178,6 +262,89 @@ object FrameworkHooker : YukiBaseHooker() {
|
|||||||
/** 保存异常记录到本地 */
|
/** 保存异常记录到本地 */
|
||||||
private fun saveAllAppErrorsRecords() = newThread { ConfigData.putResolverString(ConfigData.APP_ERRORS_DATA, appErrorsRecords.toJson()) }
|
private fun saveAllAppErrorsRecords() = newThread { ConfigData.putResolverString(ConfigData.APP_ERRORS_DATA, appErrorsRecords.toJson()) }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理 APP 进程异常信息展示
|
||||||
|
* @param context 当前实例
|
||||||
|
*/
|
||||||
|
private fun AppErrorsData.handleShowAppErrorUi(context: Context) {
|
||||||
|
/** 当前 APP 名称 */
|
||||||
|
val appName = appInfo?.let { context.appNameOf(it.packageName) } ?: packageName
|
||||||
|
|
||||||
|
/** 崩溃标题 */
|
||||||
|
val errorTitle = if (isRepeatingCrash) LocaleString.aerrRepeatedTitle(appName) else LocaleString.aerrTitle(appName)
|
||||||
|
|
||||||
|
/** 使用通知推送异常信息 */
|
||||||
|
fun showAppErrorsWithNotify() =
|
||||||
|
context.pushNotify(
|
||||||
|
channelId = "APPS_ERRORS",
|
||||||
|
channelName = LocaleString.appName,
|
||||||
|
title = errorTitle,
|
||||||
|
content = LocaleString.appErrorsTip,
|
||||||
|
icon = IconCompat.createWithBitmap(moduleAppResources.drawableOf(R.drawable.ic_notify).toBitmap()),
|
||||||
|
color = 0xFFFF6200.toInt(),
|
||||||
|
intent = AppErrorsRecordActivity.intent()
|
||||||
|
)
|
||||||
|
|
||||||
|
/** 使用 Toast 展示异常信息 */
|
||||||
|
fun showAppErrorsWithToast() = context.toast(errorTitle)
|
||||||
|
|
||||||
|
/** 使用对话框展示异常信息 */
|
||||||
|
fun showAppErrorsWithDialog() =
|
||||||
|
AppErrorsDisplayActivity.start(
|
||||||
|
context, AppErrorsDisplayBean(
|
||||||
|
pid = pid,
|
||||||
|
userId = userId,
|
||||||
|
packageName = packageName,
|
||||||
|
processName = processName,
|
||||||
|
appName = appName,
|
||||||
|
title = errorTitle,
|
||||||
|
isShowAppInfoButton = isActualApp,
|
||||||
|
isShowReopenButton = isActualApp &&
|
||||||
|
(isRepeatingCrash.not() || ConfigData.isEnableAlwaysShowsReopenAppOptions) &&
|
||||||
|
context.isAppCanOpened(packageName) &&
|
||||||
|
isMainProcess,
|
||||||
|
isShowCloseAppButton = isActualApp
|
||||||
|
)
|
||||||
|
)
|
||||||
|
/** 判断是否为已忽略的 APP */
|
||||||
|
if (mutedErrorsIfUnlockApps.contains(packageName) || mutedErrorsIfRestartApps.contains(packageName)) return
|
||||||
|
/** 判断是否为后台进程 */
|
||||||
|
if ((isBackgroundProcess || context.isAppCanOpened(packageName).not()) && ConfigData.isEnableOnlyShowErrorsInFront) return
|
||||||
|
/** 判断是否为主进程 */
|
||||||
|
if (isMainProcess.not() && ConfigData.isEnableOnlyShowErrorsInMain) return
|
||||||
|
when {
|
||||||
|
packageName == BuildConfig.APPLICATION_ID -> {
|
||||||
|
context.toast(msg = "AppErrorsTracking has crashed, please see the log in console")
|
||||||
|
loggerE(msg = "AppErrorsTracking has crashed itself, please see the Android Runtime Exception in console")
|
||||||
|
}
|
||||||
|
ConfigData.isEnableAppConfigTemplate -> when {
|
||||||
|
isAppShowNothing(packageName) -> {}
|
||||||
|
isAppShowErrorsNotify(packageName) -> showAppErrorsWithNotify()
|
||||||
|
isAppShowErrorsToast(packageName) -> showAppErrorsWithToast()
|
||||||
|
isAppShowErrorsDialog(packageName) -> showAppErrorsWithDialog()
|
||||||
|
}
|
||||||
|
else -> showAppErrorsWithDialog()
|
||||||
|
}
|
||||||
|
/** 打印错误日志 */
|
||||||
|
if (isActualApp) loggerE(
|
||||||
|
msg = "Application \"$packageName\" ${if (isRepeatingCrash) "keeps stopping" else "has stopped"}" +
|
||||||
|
(if (packageName != processName) " --process \"$processName\"" else "") +
|
||||||
|
"${if (userId != 0) " --user $userId" else ""} --pid $pid"
|
||||||
|
) else loggerE(msg = "Process \"$processName\" ${if (isRepeatingCrash) "keeps stopping" else "has stopped"} --pid $pid")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理 APP 进程异常数据
|
||||||
|
* @param info 系统错误报告数据实例
|
||||||
|
*/
|
||||||
|
private fun AppErrorsData.handleAppErrorsInfo(info: ApplicationErrorReport.CrashInfo?) {
|
||||||
|
/** 添加当前异常信息到第一位 */
|
||||||
|
appErrorsRecords.add(0, AppErrorsInfoBean.clone(pid, userId, appInfo?.packageName, info))
|
||||||
|
loggerI(msg = "Received crash application data${if (userId != 0) " --user $userId" else ""} --pid $pid")
|
||||||
|
/** 保存异常记录到本地 */
|
||||||
|
saveAllAppErrorsRecords()
|
||||||
|
}
|
||||||
|
|
||||||
override fun onHook() {
|
override fun onHook() {
|
||||||
/** 注册生命周期 */
|
/** 注册生命周期 */
|
||||||
registerLifecycle()
|
registerLifecycle()
|
||||||
@@ -247,111 +414,13 @@ object FrameworkHooker : YukiBaseHooker() {
|
|||||||
/** 当前实例 */
|
/** 当前实例 */
|
||||||
val context = appContext ?: field { name = "mContext" }.get(instance).cast<Context>() ?: return@afterHook
|
val context = appContext ?: field { name = "mContext" }.get(instance).cast<Context>() ?: return@afterHook
|
||||||
|
|
||||||
/** 错误数据 */
|
/** 当前错误数据 */
|
||||||
val errData = args().first().cast<Message>()?.obj
|
val resultData = args().first().cast<Message>()?.obj
|
||||||
|
|
||||||
/** 当前进程信息 */
|
/** 当前进程信息 */
|
||||||
val proc = AppErrorDialog_DataClass.toClass().field { name = "proc" }.get(errData).any()
|
val proc = AppErrorDialog_DataClass.toClass().field { name = "proc" }.get(resultData).any()
|
||||||
|
/** 创建 APP 进程异常数据类 */
|
||||||
/** 当前 pid 信息 */
|
AppErrorsData(instance, proc, resultData).handleShowAppErrorUi(context)
|
||||||
val pid = ProcessRecordClass.toClass().field { name { it == "mPid" || it == "pid" } }.get(proc).int()
|
|
||||||
|
|
||||||
/** 当前用户 ID 信息 */
|
|
||||||
val userId = ProcessRecordClass.toClass().field { name = "userId" }.get(proc).int()
|
|
||||||
|
|
||||||
/** 当前 APP 信息 */
|
|
||||||
val appInfo = ProcessRecordClass.toClass().field { name = "info" }.get(proc).cast<ApplicationInfo>()
|
|
||||||
|
|
||||||
/** 当前进程名称 */
|
|
||||||
val processName = ProcessRecordClass.toClass().field { name = "processName" }.get(proc).string()
|
|
||||||
|
|
||||||
/** 当前 APP、进程 包名 */
|
|
||||||
val packageName = appInfo?.packageName ?: processName
|
|
||||||
|
|
||||||
/** 当前 APP 名称 */
|
|
||||||
val appName = appInfo?.let { context.appNameOf(it.packageName) } ?: packageName
|
|
||||||
|
|
||||||
/** 当前包列表实例 */
|
|
||||||
val pkgList = (if (ProcessRecordClass.toClass().hasMethod { name = "getPkgList"; emptyParam() })
|
|
||||||
ProcessRecordClass.toClass().method { name = "getPkgList"; emptyParam() }.get(proc).call()
|
|
||||||
else ProcessRecordClass.toClass().field { name = "pkgList" }.get(proc).any())
|
|
||||||
|
|
||||||
/** 当前包列表数组大小 */
|
|
||||||
val pkgListSize = PackageListClass.toClassOrNull()?.method { name = "size"; emptyParam() }?.get(pkgList)?.int()
|
|
||||||
?: ProcessRecordClass.toClass().field { name = "pkgList" }.get(proc).cast<ArrayMap<*, *>>()?.size ?: -1
|
|
||||||
|
|
||||||
/** 是否为 APP */
|
|
||||||
val isApp = pkgListSize == 1 && appInfo != null
|
|
||||||
|
|
||||||
/** 是否为主进程 */
|
|
||||||
val isMainProcess = packageName == processName
|
|
||||||
|
|
||||||
/** 是否为后台进程 */
|
|
||||||
val isBackgroundProcess = UserControllerClass.toClass()
|
|
||||||
.method { name { it == "getCurrentProfileIds" || it == "getCurrentProfileIdsLocked" } }
|
|
||||||
.get(ActivityManagerServiceClass.toClass().field { name = "mUserController" }
|
|
||||||
.get(field { name = "mService" }.get(instance).any()).any())
|
|
||||||
.invoke<IntArray>()?.takeIf { it.isNotEmpty() }?.any { it != userId } ?: false
|
|
||||||
|
|
||||||
/** 是否短时内重复错误 */
|
|
||||||
val isRepeating = AppErrorDialog_DataClass.toClass().field { name = "repeating" }.get(errData).boolean()
|
|
||||||
|
|
||||||
/** 崩溃标题 */
|
|
||||||
val errorTitle = if (isRepeating) LocaleString.aerrRepeatedTitle(appName) else LocaleString.aerrTitle(appName)
|
|
||||||
/** 打印错误日志 */
|
|
||||||
if (isApp) loggerE(
|
|
||||||
msg = "Application \"$packageName\" ${if (isRepeating) "keeps stopping" else "has stopped"}" +
|
|
||||||
(if (packageName != processName) " --process \"$processName\"" else "") +
|
|
||||||
"${if (userId != 0) " --user $userId" else ""} --pid $pid"
|
|
||||||
) else loggerE(msg = "Process \"$processName\" ${if (isRepeating) "keeps stopping" else "has stopped"} --pid $pid")
|
|
||||||
/** 判断是否为模块自身崩溃 */
|
|
||||||
if (packageName == BuildConfig.APPLICATION_ID) {
|
|
||||||
context.toast(msg = "AppErrorsTracking has crashed, please see the log in console")
|
|
||||||
loggerE(msg = "AppErrorsTracking has crashed itself, please see the Android Runtime Exception in console")
|
|
||||||
return@afterHook
|
|
||||||
}
|
|
||||||
/** 判断是否为已忽略的 APP */
|
|
||||||
if (mutedErrorsIfUnlockApps.contains(packageName) || mutedErrorsIfRestartApps.contains(packageName)) return@afterHook
|
|
||||||
/** 判断配置模块启用状态 */
|
|
||||||
if (ConfigData.isEnableAppConfigTemplate) {
|
|
||||||
if (isAppShowNothing(packageName)) return@afterHook
|
|
||||||
if (isAppShowErrorsNotify(packageName)) {
|
|
||||||
context.pushNotify(
|
|
||||||
channelId = "APPS_ERRORS",
|
|
||||||
channelName = LocaleString.appName,
|
|
||||||
title = errorTitle,
|
|
||||||
content = LocaleString.appErrorsTip,
|
|
||||||
icon = IconCompat.createWithBitmap(moduleAppResources.drawableOf(R.drawable.ic_notify).toBitmap()),
|
|
||||||
color = 0xFFFF6200.toInt(),
|
|
||||||
intent = AppErrorsRecordActivity.intent()
|
|
||||||
)
|
|
||||||
return@afterHook
|
|
||||||
}
|
|
||||||
if (isAppShowErrorsToast(packageName)) {
|
|
||||||
context.toast(errorTitle)
|
|
||||||
return@afterHook
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/** 判断是否为后台进程 */
|
|
||||||
if ((isBackgroundProcess || context.isAppCanOpened(packageName).not()) && ConfigData.isEnableOnlyShowErrorsInFront)
|
|
||||||
return@afterHook
|
|
||||||
/** 判断是否为主进程 */
|
|
||||||
if (isMainProcess.not() && ConfigData.isEnableOnlyShowErrorsInMain) return@afterHook
|
|
||||||
/** 启动错误对话框显示窗口 */
|
|
||||||
AppErrorsDisplayActivity.start(
|
|
||||||
context, AppErrorsDisplayBean(
|
|
||||||
pid = pid,
|
|
||||||
userId = userId,
|
|
||||||
packageName = packageName,
|
|
||||||
processName = processName,
|
|
||||||
appName = appName,
|
|
||||||
title = errorTitle,
|
|
||||||
isShowAppInfoButton = isApp,
|
|
||||||
isShowReopenButton = isApp && (isRepeating.not() || ConfigData.isEnableAlwaysShowsReopenAppOptions)
|
|
||||||
&& context.isAppCanOpened(packageName) && isMainProcess,
|
|
||||||
isShowCloseAppButton = isApp
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
injectMember {
|
injectMember {
|
||||||
@@ -362,20 +431,8 @@ object FrameworkHooker : YukiBaseHooker() {
|
|||||||
afterHook {
|
afterHook {
|
||||||
/** 当前进程信息 */
|
/** 当前进程信息 */
|
||||||
val proc = args().first().any() ?: return@afterHook loggerW(msg = "Received but got null ProcessRecord")
|
val proc = args().first().any() ?: return@afterHook loggerW(msg = "Received but got null ProcessRecord")
|
||||||
|
/** 创建 APP 进程异常数据类 */
|
||||||
/** 当前 pid 信息 */
|
AppErrorsData(instance, proc).handleAppErrorsInfo(args(index = 1).cast())
|
||||||
val pid = ProcessRecordClass.toClass().field { name { it == "mPid" || it == "pid" } }.get(proc).int()
|
|
||||||
|
|
||||||
/** 当前用户 ID 信息 */
|
|
||||||
val userId = ProcessRecordClass.toClass().field { name = "userId" }.get(proc).int()
|
|
||||||
|
|
||||||
/** 当前 APP 信息 */
|
|
||||||
val appInfo = ProcessRecordClass.toClass().field { name = "info" }.get(proc).cast<ApplicationInfo>()
|
|
||||||
/** 添加当前异常信息到第一位 */
|
|
||||||
appErrorsRecords.add(0, AppErrorsInfoBean.clone(pid, userId, appInfo?.packageName, args(index = 1).cast()))
|
|
||||||
loggerI(msg = "Received crash application data${if (userId != 0) " --user $userId" else ""} --pid $pid")
|
|
||||||
/** 保存异常记录到本地 */
|
|
||||||
saveAllAppErrorsRecords()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user