mirror of
https://github.com/KitsunePie/AppErrorsTracking.git
synced 2025-09-01 08:45:16 +08:00
Modify merge to new way to save and read the app errors record data
This commit is contained in:
@@ -104,6 +104,12 @@ data class AppErrorsInfoBean(
|
||||
/** 标识当前内容是否为空 */
|
||||
var isEmpty = false
|
||||
|
||||
/**
|
||||
* 获取生成的 Json 文件名
|
||||
* @return [String]
|
||||
*/
|
||||
val jsonFileName get() = "${packageName}_${pid}_${timestamp}.json"
|
||||
|
||||
/**
|
||||
* 获取异常本地化 UTC 时间
|
||||
* @return [String]
|
||||
|
@@ -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
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* 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<AppErrorsInfoBean>()
|
||||
|
||||
/**
|
||||
* 初始化存储控制类
|
||||
* @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<CopyOnWriteArrayList<AppErrorsInfoBean>>()
|
||||
?.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<AppErrorsInfoBean>().apply {
|
||||
errorsInfoDataFiles.takeIf { it.isNotEmpty() }?.forEach { it.readText().toEntityOrNull<AppErrorsInfoBean>()?.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()
|
||||
}
|
||||
}
|
@@ -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]
|
||||
|
@@ -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<String>()
|
||||
|
||||
/** 已记录的 APP 异常信息数组 */
|
||||
private var appErrorsRecords = ArrayList<AppErrorsInfoBean>()
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
|
Reference in New Issue
Block a user