From 7c0c1754e9a4713a65d10073fff531e8c50e3c8a Mon Sep 17 00:00:00 2001 From: fankesyooni Date: Tue, 17 Jan 2023 03:17:37 +0800 Subject: [PATCH] Modify merge to new way to save and read the app errors record data --- .../bean/AppErrorsInfoBean.kt | 6 + .../data/AppErrorsRecordData.kt | 132 ++++++++++++++++++ .../apperrorstracking/data/ConfigData.kt | 27 ---- .../hook/entity/FrameworkHooker.kt | 28 ++-- 4 files changed, 147 insertions(+), 46 deletions(-) create mode 100644 app/src/main/java/com/fankes/apperrorstracking/data/AppErrorsRecordData.kt 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() {