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 fd5c1ab..d4adf83 100644
--- a/app/src/main/java/com/fankes/apperrorstracking/bean/AppErrorsInfoBean.kt
+++ b/app/src/main/java/com/fankes/apperrorstracking/bean/AppErrorsInfoBean.kt
@@ -104,6 +104,12 @@ data class AppErrorsInfoBean(
/** 标识当前内容是否为空 */
var isEmpty = false
+ /**
+ * 获取生成的 Json 文件名
+ * @return [String]
+ */
+ val jsonFileName get() = "${packageName}_${pid}_${timestamp}.json"
+
/**
* 获取异常本地化 UTC 时间
* @return [String]
diff --git a/app/src/main/java/com/fankes/apperrorstracking/data/AppErrorsRecordData.kt b/app/src/main/java/com/fankes/apperrorstracking/data/AppErrorsRecordData.kt
new file mode 100644
index 0000000..f8f56e8
--- /dev/null
+++ b/app/src/main/java/com/fankes/apperrorstracking/data/AppErrorsRecordData.kt
@@ -0,0 +1,132 @@
+/*
+ * AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
+ * Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
+ * https://github.com/KitsunePie/AppErrorsTracking
+ *
+ * This software is non-free but opensource software: you can redistribute it
+ * and/or modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation; either
+ * version 3 of the License, or any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * and eula along with this software. If not, see
+ *
+ *
+ * This file is Created by fankes on 2023/1/17.
+ */
+@file:Suppress("StaticFieldLeak")
+
+package com.fankes.apperrorstracking.data
+
+import android.content.Context
+import android.provider.Settings
+import com.fankes.apperrorstracking.bean.AppErrorsInfoBean
+import com.fankes.apperrorstracking.utils.factory.toEntityOrNull
+import com.fankes.apperrorstracking.utils.factory.toJsonOrNull
+import com.highcapable.yukihookapi.hook.log.loggerE
+import java.io.File
+import java.util.concurrent.CopyOnWriteArrayList
+
+/**
+ * [AppErrorsInfoBean] 存储控制类
+ */
+object AppErrorsRecordData {
+
+ /** 异常记录数据文件目录路径 */
+ private const val FOLDER_PATH = "/data/misc/app_errors_records/"
+
+ /** 当前实例 */
+ private var context: Context? = null
+
+ /**
+ * 获取当前异常记录数据目录
+ * @return [File]
+ */
+ private val errorsInfoDataFolder by lazy { File(FOLDER_PATH) }
+
+ /**
+ * 获取当前全部异常记录数据文件
+ * @return [File]
+ */
+ private val errorsInfoDataFiles get() = errorsInfoDataFolder.listFiles() ?: emptyArray()
+
+ /** 已记录的全部 APP 异常信息数组 */
+ var allData = CopyOnWriteArrayList()
+
+ /**
+ * 初始化存储控制类
+ * @param context 实例
+ */
+ fun init(context: Context) {
+ this.context = context
+ initializeDataDirectory()
+ allData = readAllDataFromFiles()
+ }
+
+ /** 初始化异常记录数据目录 */
+ private fun initializeDataDirectory() {
+ runCatching {
+ errorsInfoDataFolder.also { if (it.exists().not() || it.isFile) it.apply { delete(); mkdirs() } }
+ }.onFailure {
+ loggerE(msg = "Can't create directory \"$FOLDER_PATH\", there will be problems with the app errors records function", e = it)
+ }
+ }
+
+ /**
+ * 获取旧版异常记录数据并自动转换到新版
+ * @return [ArrayList]<[AppErrorsInfoBean]> or null
+ */
+ private fun copyOldDataFromResolverString() = context?.let {
+ val keyName = "app_errors_data"
+ runCatching {
+ Settings.Secure.getString(it.contentResolver, keyName)
+ ?.toEntityOrNull>()
+ ?.onEach { e ->
+ e.toJsonOrNull()?.also { json -> File(errorsInfoDataFolder.absolutePath, e.jsonFileName).writeText(json) }
+ }.let { result ->
+ if (result != null) {
+ Settings.Secure.putString(it.contentResolver, keyName, "")
+ result
+ } else null
+ }
+ }.getOrNull()
+ }
+
+ /**
+ * 从文件获取全部异常记录数据
+ * @return [ArrayList]<[AppErrorsInfoBean]>
+ */
+ private fun readAllDataFromFiles() = copyOldDataFromResolverString() ?: CopyOnWriteArrayList().apply {
+ errorsInfoDataFiles.takeIf { it.isNotEmpty() }?.forEach { it.readText().toEntityOrNull()?.let { e -> add(e) } }
+ }
+
+ /**
+ * 添加新的异常记录数据
+ * @param bean [AppErrorsInfoBean] 实例
+ */
+ fun add(bean: AppErrorsInfoBean) {
+ allData.add(0, bean)
+ bean.toJsonOrNull()?.runCatching { File(errorsInfoDataFolder.absolutePath, bean.jsonFileName).writeText(this) }
+ }
+
+ /**
+ * 移除指定的异常记录数据
+ * @param bean [AppErrorsInfoBean] 实例
+ */
+ fun remove(bean: AppErrorsInfoBean) {
+ allData.remove(bean)
+ runCatching { File(errorsInfoDataFolder.absolutePath, bean.jsonFileName).delete() }
+ }
+
+ /** 清除全部异常记录数据 */
+ fun clearAll() {
+ allData.clear()
+ runCatching { errorsInfoDataFolder.deleteRecursively() }
+ initializeDataDirectory()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/fankes/apperrorstracking/data/ConfigData.kt b/app/src/main/java/com/fankes/apperrorstracking/data/ConfigData.kt
index d6526b6..a6b5568 100644
--- a/app/src/main/java/com/fankes/apperrorstracking/data/ConfigData.kt
+++ b/app/src/main/java/com/fankes/apperrorstracking/data/ConfigData.kt
@@ -23,13 +23,10 @@
package com.fankes.apperrorstracking.data
-import android.content.ContentResolver
import android.content.Context
import android.os.Build
-import android.provider.Settings
import android.widget.CompoundButton
import com.highcapable.yukihookapi.hook.factory.modulePrefs
-import com.highcapable.yukihookapi.hook.log.loggerE
import com.highcapable.yukihookapi.hook.log.loggerW
import com.highcapable.yukihookapi.hook.param.PackageParam
import com.highcapable.yukihookapi.hook.xposed.prefs.data.PrefsData
@@ -39,9 +36,6 @@ import com.highcapable.yukihookapi.hook.xposed.prefs.data.PrefsData
*/
object ConfigData {
- /** 存取全部应用异常数据的键值名称 */
- const val APP_ERRORS_DATA = "app_errors_data"
-
/** 显示开发者提示 */
val SHOW_DEVELOPER_NOTICE = PrefsData("_show_developer_notice", true)
@@ -114,27 +108,6 @@ object ConfigData {
}
}
- /**
- * 获取 [ContentResolver] 字符串数据 (仅限 Hook 进程)
- * @param key 键值名称
- * @return [String]
- */
- fun getResolverString(key: String) =
- runCatching { (instance as? PackageParam)?.appContext?.let { Settings.Secure.getString(it.contentResolver, key) } }.getOrNull() ?: ""
-
- /**
- * 存入 [ContentResolver] 字符串数据 (仅限 Hook 进程)
- * @param key 键值名称
- * @param value 键值数据
- */
- fun putResolverString(key: String, value: String) {
- runCatching {
- (instance as? PackageParam)?.appContext?.also { Settings.Secure.putString(it.contentResolver, key, value) }
- }.onFailure {
- loggerE(msg = "Write secure settings failed", e = it)
- }
- }
-
/**
* 是否显示开发者提示
* @return [Boolean]
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 f77c74b..2cf267e 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
@@ -39,6 +39,7 @@ import com.fankes.apperrorstracking.bean.AppErrorsDisplayBean
import com.fankes.apperrorstracking.bean.AppErrorsInfoBean
import com.fankes.apperrorstracking.bean.AppInfoBean
import com.fankes.apperrorstracking.bean.MutedErrorsAppBean
+import com.fankes.apperrorstracking.data.AppErrorsRecordData
import com.fankes.apperrorstracking.data.ConfigData
import com.fankes.apperrorstracking.data.factory.isAppShowErrorsDialog
import com.fankes.apperrorstracking.data.factory.isAppShowErrorsNotify
@@ -87,9 +88,6 @@ object FrameworkHooker : YukiBaseHooker() {
/** 已忽略错误的 APP 数组 - 直到重新启动 */
private var mutedErrorsIfRestartApps = HashSet()
- /** 已记录的 APP 异常信息数组 */
- private var appErrorsRecords = ArrayList()
-
/**
* APP 进程异常数据定义类
* @param errors [AppErrorsClass] 实例
@@ -179,8 +177,8 @@ object FrameworkHooker : YukiBaseHooker() {
registerReceiver(Intent.ACTION_USER_PRESENT) { _, _ -> mutedErrorsIfUnlockApps.clear() }
/** 刷新模块 Resources 缓存 */
registerReceiver(Intent.ACTION_LOCALE_CHANGED) { _, _ -> refreshModuleAppResources() }
- /** 启动时从本地获取异常记录 */
- onCreate { appErrorsRecords = ConfigData.getResolverString(ConfigData.APP_ERRORS_DATA).toEntity() ?: arrayListOf() }
+ /** 启动时从本地获取异常记录数据 */
+ onCreate { AppErrorsRecordData.init(context = this) }
}
FrameworkTool.Host.with(instance = this) {
onOpenAppUsedFramework {
@@ -188,21 +186,19 @@ object FrameworkHooker : YukiBaseHooker() {
loggerI(msg = "Opened \"${it.first}\"${it.second.takeIf { e -> e > 0 }?.let { e -> " --user $e" } ?: ""}")
}
onPushAppErrorInfoData {
- appErrorsRecords.firstOrNull { e -> e.pid == it } ?: run {
+ AppErrorsRecordData.allData.firstOrNull { e -> e.pid == it } ?: run {
loggerW(msg = "Cannot received crash application data --pid $it")
AppErrorsInfoBean.createEmpty()
}
}
- onPushAppErrorsInfoData { appErrorsRecords }
+ onPushAppErrorsInfoData { AppErrorsRecordData.allData.toArrayList() }
onRemoveAppErrorsInfoData {
loggerI(msg = "Removed app errors info data for package \"${it.packageName}\"")
- appErrorsRecords.remove(it)
- saveAllAppErrorsRecords()
+ AppErrorsRecordData.remove(it)
}
onClearAppErrorsInfoData {
- loggerI(msg = "Cleared all app errors info data, size ${appErrorsRecords.size}")
- appErrorsRecords.clear()
- saveAllAppErrorsRecords()
+ loggerI(msg = "Cleared all app errors info data, size ${AppErrorsRecordData.allData.size}")
+ AppErrorsRecordData.clearAll()
}
onMutedErrorsIfUnlock {
mutedErrorsIfUnlockApps.add(it)
@@ -259,9 +255,6 @@ object FrameworkHooker : YukiBaseHooker() {
}
}
- /** 保存异常记录到本地 */
- private fun saveAllAppErrorsRecords() = newThread { ConfigData.putResolverString(ConfigData.APP_ERRORS_DATA, appErrorsRecords.toJson()) }
-
/**
* 处理 APP 进程异常信息展示
* @param context 当前实例
@@ -338,11 +331,8 @@ object FrameworkHooker : YukiBaseHooker() {
* @param info 系统错误报告数据实例
*/
private fun AppErrorsData.handleAppErrorsInfo(info: ApplicationErrorReport.CrashInfo?) {
- /** 添加当前异常信息到第一位 */
- appErrorsRecords.add(0, AppErrorsInfoBean.clone(pid, userId, appInfo?.packageName, info))
+ AppErrorsRecordData.add(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() {