feat: add stack trace share optional dialog

This commit is contained in:
2023-11-03 18:46:07 +08:00
parent 2d42581e36
commit a64ad86e64
11 changed files with 257 additions and 39 deletions

View File

@@ -183,43 +183,67 @@ data class AppErrorsInfoBean(
/**
* 获取异常堆栈分享模板
* @param sDeviceBrand
* @param sDeviceModel
* @param sDisplay
* @param sPackageName
* @return [String]
*/
val stackOutputShareContent
get() = """
fun stackOutputShareContent(
sDeviceBrand: Boolean = true,
sDeviceModel: Boolean = true,
sDisplay: Boolean = true,
sPackageName: Boolean = true
) = """
Generated by AppErrorsTracking $ModuleVersion
Project URL: https://github.com/KitsunePie/AppErrorsTracking
===============
""".trimIndent() + "\n$environmentInfo"
""".trimIndent() + "\n${environmentInfo(sDeviceBrand, sDeviceModel, sDisplay, sPackageName)}"
/**
* 获取异常堆栈文件模板
* @param sDeviceBrand
* @param sDeviceModel
* @param sDisplay
* @param sPackageName
* @return [String]
*/
val stackOutputFileContent
get() = """
fun stackOutputFileContent(
sDeviceBrand: Boolean = true,
sDeviceModel: Boolean = true,
sDisplay: Boolean = true,
sPackageName: Boolean = true
) = """
================================================================
Generated by AppErrorsTracking $ModuleVersion
Project URL: https://github.com/KitsunePie/AppErrorsTracking
================================================================
""".trimIndent() + "\n$environmentInfo"
""".trimIndent() + "\n${environmentInfo(sDeviceBrand, sDeviceModel, sDisplay, sPackageName)}"
/**
* 获取运行环境信息
* @param sDeviceBrand
* @param sDeviceModel
* @param sDisplay
* @param sPackageName
* @return [String]
*/
private val environmentInfo
get() = """
[Device Brand]: ${Build.BRAND}
[Device Model]: ${Build.MODEL}
[Display]: ${Build.DISPLAY}
private fun environmentInfo(
sDeviceBrand: Boolean,
sDeviceModel: Boolean,
sDisplay: Boolean,
sPackageName: Boolean
) = """
[Device Brand]: ${Build.BRAND.by(sDeviceBrand)}
[Device Model]: ${Build.MODEL.by(sDeviceModel)}
[Display]: ${Build.DISPLAY.by(sDisplay)}
[Android Version]: ${Build.VERSION.RELEASE}
[Android API Level]: ${Build.VERSION.SDK_INT}
[System Locale]: ${Locale.getDefault()}
[Process ID]: $pid
[User ID]: $userId
[CPU ABI]: ${cpuAbi.ifBlank { "none" }}
[Package Name]: $packageName
[Package Name]: ${packageName.by(sPackageName)}
[Version Name]: ${versionName.ifBlank { "unknown" }}
[Version Code]: ${versionCode.takeIf { it != -1L } ?: "unknown"}
[Target SDK]: ${targetSdk.takeIf { it != -1 } ?: "unknown"}
@@ -228,4 +252,11 @@ data class AppErrorsInfoBean(
[Crash Time]: $utcTime
[Stack Trace]:
""".trimIndent() + "\n$stackTrace"
/**
* 判断字符串是否需要显示
* @param isDisplay 是否需要显示
* @return [String]
*/
private fun String.by(isDisplay: Boolean) = if (isDisplay) this else "***"
}

View File

@@ -45,6 +45,7 @@ import com.fankes.apperrorstracking.utils.factory.navigate
import com.fankes.apperrorstracking.utils.factory.openSelfSetting
import com.fankes.apperrorstracking.utils.factory.showDialog
import com.fankes.apperrorstracking.utils.factory.toast
import com.fankes.apperrorstracking.utils.tool.StackTraceShareHelper
import com.highcapable.yukihookapi.hook.log.loggerE
class AppErrorsDetailActivity : BaseActivity<ActivityAppErrorsDetailBinding>() {
@@ -121,23 +122,32 @@ class AppErrorsDetailActivity : BaseActivity<ActivityAppErrorsDetailBinding>() {
loggerE(msg = appErrorsInfo.stackTrace)
toast(locale.printToLogcatSuccess)
}
binding.copyIcon.setOnClickListener { copyToClipboard(appErrorsInfo.stackOutputShareContent) }
binding.copyIcon.setOnClickListener {
StackTraceShareHelper.showChoose(context = this, locale.copyErrorStack) { sDeviceBrand, sDeviceModel, sDisplay, sPackageName ->
copyToClipboard(appErrorsInfo.stackOutputShareContent(sDeviceBrand, sDeviceModel, sDisplay, sPackageName))
}
}
binding.exportIcon.setOnClickListener {
stackTrace = appErrorsInfo.stackOutputFileContent
StackTraceShareHelper.showChoose(context = this, locale.exportToFile) { sDeviceBrand, sDeviceModel, sDisplay, sPackageName ->
stackTrace = appErrorsInfo.stackOutputFileContent(sDeviceBrand, sDeviceModel, sDisplay, sPackageName)
runCatching {
startActivityForResult(Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "*/application"
putExtra(Intent.EXTRA_TITLE, "${appErrorsInfo.packageName}_${appErrorsInfo.utcTime}.log")
val packageName = if (sPackageName) appErrorsInfo.packageName else "anonymous"
putExtra(Intent.EXTRA_TITLE, "${packageName}_${appErrorsInfo.utcTime}.log")
}, WRITE_REQUEST_CODE)
}.onFailure { toast(msg = "Start Android SAF failed") }
}
}
binding.shareIcon.setOnClickListener {
StackTraceShareHelper.showChoose(context = this, locale.shareErrorStack) { sDeviceBrand, sDeviceModel, sDisplay, sPackageName ->
startActivity(Intent.createChooser(Intent(Intent.ACTION_SEND).apply {
type = "text/plain"
putExtra(Intent.EXTRA_TEXT, appErrorsInfo.stackOutputShareContent)
putExtra(Intent.EXTRA_TEXT, appErrorsInfo.stackOutputShareContent(sDeviceBrand, sDeviceModel, sDisplay, sPackageName))
}, locale.shareErrorStack))
}
}
binding.appIcon.setImageDrawable(appIconOf(appErrorsInfo.packageName))
binding.appNameText.text = appNameOf(appErrorsInfo.packageName).ifBlank { appErrorsInfo.packageName }
binding.appVersionText.text = appErrorsInfo.versionBrand

View File

@@ -50,6 +50,7 @@ import com.fankes.apperrorstracking.utils.factory.showDialog
import com.fankes.apperrorstracking.utils.factory.toUtcTime
import com.fankes.apperrorstracking.utils.factory.toast
import com.fankes.apperrorstracking.utils.tool.FrameworkTool
import com.fankes.apperrorstracking.utils.tool.StackTraceShareHelper
import com.fankes.apperrorstracking.utils.tool.ZipFileTool
import com.fankes.apperrorstracking.wrapper.BuildConfigWrapper
import java.io.File
@@ -176,10 +177,13 @@ class AppErrorsRecordActivity : BaseActivity<ActivityAppErrorsRecordBinding>() {
/** 打包导出全部 */
private fun exportAll() {
clearAllExportTemp()
StackTraceShareHelper.showChoose(context = this, locale.exportAll) { sDeviceBrand, sDeviceModel, sDisplay, sPackageName ->
("${cacheDir.absolutePath}/temp").also { path ->
File(path).mkdirs()
listData.takeIf { it.isNotEmpty() }?.forEach {
File("$path/${it.packageName}_${it.utcTime}.log").writeText(it.stackOutputFileContent)
listData.takeIf { it.isNotEmpty() }?.forEachIndexed { index, bean ->
val packageName = if (sPackageName) bean.packageName else "anonymous_$index"
File("$path/${packageName}_${bean.utcTime}.log")
.writeText(bean.stackOutputFileContent(sDeviceBrand, sDeviceModel, sDisplay, sPackageName))
}
outPutFilePath = "${cacheDir.absolutePath}/temp_${System.currentTimeMillis()}.zip"
ZipFileTool.zipMultiFile(path, outPutFilePath)
@@ -192,6 +196,7 @@ class AppErrorsRecordActivity : BaseActivity<ActivityAppErrorsRecordBinding>() {
}.onFailure { toast(msg = "Start Android SAF failed") }
}
}
}
/** 清空导出的临时文件 */
private fun clearAllExportTemp() {

View File

@@ -0,0 +1,57 @@
/*
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
* Copyright (C) 2017-2023 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/11/3.
*/
package com.fankes.apperrorstracking.utils.tool
import android.content.Context
import com.fankes.apperrorstracking.databinding.DiaStackTraceShareBinding
import com.fankes.apperrorstracking.utils.factory.showDialog
/**
* 异常堆栈分享工具类
*/
object StackTraceShareHelper {
/**
* 显示分享选择器
* @param context 当前实例
* @param title 对话框标题
* @param onChoose 回调选择的结果
*/
fun showChoose(
context: Context,
title: String,
onChoose: (sDeviceBrand: Boolean, sDeviceModel: Boolean, sDisplay: Boolean, sPackageName: Boolean) -> Unit
) {
context.showDialog<DiaStackTraceShareBinding> {
this.title = title
confirmButton {
val sDeviceBrand = binding.configCheck0.isChecked
val sDeviceModel = binding.configCheck1.isChecked
val sDisplay = binding.configCheck2.isChecked
val sPackageName = binding.configCheck3.isChecked
onChoose(sDeviceBrand, sDeviceModel, sDisplay, sPackageName)
cancel()
}
cancelButton()
}
}
}

View File

@@ -0,0 +1,73 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingLeft="15dp"
android:paddingTop="15dp"
android:paddingRight="15dp"
tools:ignore="HardcodedText">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="8.5dp"
android:layout_marginRight="8.5dp"
android:layout_marginBottom="15dp"
android:lineSpacingExtra="6dp"
android:text="@string/stack_trace_share_top_tip"
android:textSize="13sp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="8.5dp"
android:layout_marginRight="8.5dp"
android:layout_marginBottom="10dp"
android:lineSpacingExtra="6dp"
android:text="@string/stack_trace_share_bottom_tip"
android:textColor="#FFFF5722"
android:textSize="13sp" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/config_check_0"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:checked="true"
android:text="@string/stack_trace_share_device_brand"
app:buttonTint="@color/colorPrimaryAccent" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/config_check_1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:checked="true"
android:text="@string/stack_trace_share_device_model"
app:buttonTint="@color/colorPrimaryAccent" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/config_check_2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:checked="true"
android:text="@string/stack_trace_share_system_build_id"
app:buttonTint="@color/colorPrimaryAccent" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/config_check_3"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:checked="true"
android:text="@string/stack_trace_share_package_name"
app:buttonTint="@color/colorPrimaryAccent" />
<com.google.android.material.checkbox.MaterialCheckBox
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:checked="true"
android:enabled="false"
android:text="@string/stack_trace_share_other"
app:buttonTint="@color/colorPrimaryAccent" />
</LinearLayout>

View File

@@ -148,4 +148,11 @@
<string name="record_count">%1$s の合計紀錄</string>
<string name="ci_notice_dialog_title">CI 自動ビルド手順</string>
<string name="ci_notice_dialog_content">コミット ID %1$s の CI 自動ビルドを使用しています。\n\nコードが送信された後、自動的にトリガーされてビルドされ、自動的にコンパイルされてリリースされますが、安定性についてはテストされていません。ご自身の責任で使用してください。</string>
<string name="stack_trace_share_top_tip">共有したいコンテンツを選択すると、表示したくないコンテンツの一部を非表示にすることができます。</string>
<string name="stack_trace_share_bottom_tip">システム言語地域、バージョン、アプリケーションのバージョン、およびエラー ログは、非表示にしてはいけないコンテンツとして共有する必要があります。これらのコンテンツは、開発者が問題を特定するのに役立ちます。</string>
<string name="stack_trace_share_device_brand">デバイスのブランド</string>
<string name="stack_trace_share_device_model">デバイスモデル</string>
<string name="stack_trace_share_system_build_id">システムビルドID</string>
<string name="stack_trace_share_package_name">アプリのパッケージ名</string>
<string name="stack_trace_share_other">その他の要件</string>
</resources>

View File

@@ -148,4 +148,11 @@
<string name="record_count">共 %1$s 条记录</string>
<string name="ci_notice_dialog_title">CI 自动构建说明</string>
<string name="ci_notice_dialog_content">你正在使用的是 CI 自动构建版本Commit ID 为 %1$s。\n\n它是由代码提交后自动触发并构建、自动编译发布的并未经任何稳定性测试使用风险自负。</string>
<string name="stack_trace_share_top_tip">选择你想要分享的内容,你可以隐藏部分你不想展示的内容。</string>
<string name="stack_trace_share_bottom_tip">系统语言区域、版本、应用版本和错误日志等作为必须分享的内容不能被隐藏,这些内容都有助于帮助开发者定位问题。</string>
<string name="stack_trace_share_device_brand">设备制造商</string>
<string name="stack_trace_share_device_model">设备型号</string>
<string name="stack_trace_share_system_build_id">系统构建 ID</string>
<string name="stack_trace_share_package_name">APP 包名</string>
<string name="stack_trace_share_other">其它必要信息</string>
</resources>

View File

@@ -148,4 +148,11 @@
<string name="record_count">共 %1$s 條紀錄</string>
<string name="ci_notice_dialog_title">CI 自動建置說明</string>
<string name="ci_notice_dialog_content">你正在使用的是 CI 自動建置版本Commit ID 為 %1$s。\n\n它是由程式碼提交後自動觸發並建置、自動編譯發布的並未經任何穩定性測試使用風險自負。</string>
<string name="stack_trace_share_top_tip">選擇您要分享的內容,您可以隱藏一些您不想顯示的內容。</string>
<string name="stack_trace_share_bottom_tip">系統語言區域、版本、應用程式版本和錯誤日誌必須作為內容共享,且不得隱藏,這些內容將幫助開發人員定位問題。</string>
<string name="stack_trace_share_device_brand">設備品牌</string>
<string name="stack_trace_share_device_model">設備型號</string>
<string name="stack_trace_share_system_build_id">系統建置 ID</string>
<string name="stack_trace_share_package_name">應用程式包名稱</string>
<string name="stack_trace_share_other">其它必要資訊</string>
</resources>

View File

@@ -148,4 +148,11 @@
<string name="record_count">共 %1$s 條紀錄</string>
<string name="ci_notice_dialog_title">CI 自動建置說明</string>
<string name="ci_notice_dialog_content">你正在使用的是 CI 自動建置版本Commit ID 為 %1$s。\n\n它是由程式碼提交後自動觸發並建置、自動編譯發布的並未經任何穩定性測試使用風險自負。</string>
<string name="stack_trace_share_top_tip">選擇您要分享的內容,您可以隱藏一些您不想顯示的內容。</string>
<string name="stack_trace_share_bottom_tip">系統語言區域、版本、應用程式版本和錯誤日誌必須作為內容共享,且不得隱藏,這些內容將幫助開發人員定位問題。</string>
<string name="stack_trace_share_device_brand">設備品牌</string>
<string name="stack_trace_share_device_model">設備型號</string>
<string name="stack_trace_share_system_build_id">系統建置 ID</string>
<string name="stack_trace_share_package_name">應用程式包名稱</string>
<string name="stack_trace_share_other">其它必要資訊</string>
</resources>

View File

@@ -148,4 +148,11 @@
<string name="record_count">共 %1$s 條紀錄</string>
<string name="ci_notice_dialog_title">CI 自動建置說明</string>
<string name="ci_notice_dialog_content">你正在使用的是 CI 自動建置版本Commit ID 為 %1$s。\n\n它是由程式碼提交後自動觸發並建置、自動編譯發布的並未經任何穩定性測試使用風險自負。</string>
<string name="stack_trace_share_top_tip">選擇您要分享的內容,您可以隱藏一些您不想顯示的內容。</string>
<string name="stack_trace_share_bottom_tip">系統語言區域、版本、應用程式版本和錯誤日誌必須作為內容共享,且不得隱藏,這些內容將幫助開發人員定位問題。</string>
<string name="stack_trace_share_device_brand">設備品牌</string>
<string name="stack_trace_share_device_model">設備型號</string>
<string name="stack_trace_share_system_build_id">系統建置 ID</string>
<string name="stack_trace_share_package_name">應用程式包名稱</string>
<string name="stack_trace_share_other">其它必要資訊</string>
</resources>

View File

@@ -151,4 +151,11 @@
<string name="ci_notice_dialog_content">You are using a CI automated build with Commit ID %1$s.\n\nIt is automatically triggered and built after the code is committed, automatically compiled and released, and has not been tested for stability, use it at your own risk.</string>
<string name="app_target_sdk" translatable="false">Target %1$s</string>
<string name="app_min_sdk" translatable="false">Min %1$s</string>
<string name="stack_trace_share_top_tip">Select the content you want to share, and you can hide some of the content you don\'t want to show.</string>
<string name="stack_trace_share_bottom_tip">System language region, version, application version and error logs must be shared as content that must not be hidden, these content will help developers locate problems.</string>
<string name="stack_trace_share_device_brand">Device Brand</string>
<string name="stack_trace_share_device_model">Device Model</string>
<string name="stack_trace_share_system_build_id">System Build ID</string>
<string name="stack_trace_share_package_name">App Package Name</string>
<string name="stack_trace_share_other">Other Requirement</string>
</resources>