mirror of
https://github.com/KitsunePie/AppErrorsTracking.git
synced 2025-09-01 16:55:18 +08:00
Added export all errors record function
This commit is contained in:
@@ -202,4 +202,16 @@ object LocaleString {
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun areYouSureExportAllErrors(vararg objArrs: Any) = R.string.are_you_sure_export_all_errors.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val exportAllErrorsSuccess get() = exportAllErrorsSuccess()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun exportAllErrorsSuccess(vararg objArrs: Any) = R.string.export_all_errors_success.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val exportAllErrorsFail get() = exportAllErrorsFail()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun exportAllErrorsFail(vararg objArrs: Any) = R.string.export_all_errors_fail.bind(*objArrs)
|
||||
}
|
@@ -19,8 +19,12 @@
|
||||
*
|
||||
* This file is Created by fankes on 2022/5/11.
|
||||
*/
|
||||
@file:Suppress("DEPRECATION", "OVERRIDE_DEPRECATION")
|
||||
|
||||
package com.fankes.apperrorstracking.ui.activity
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.view.*
|
||||
import android.widget.AdapterView.AdapterContextMenuInfo
|
||||
import android.widget.BaseAdapter
|
||||
@@ -33,11 +37,21 @@ import com.fankes.apperrorstracking.locale.LocaleString
|
||||
import com.fankes.apperrorstracking.ui.activity.base.BaseActivity
|
||||
import com.fankes.apperrorstracking.utils.factory.*
|
||||
import com.fankes.apperrorstracking.utils.tool.FrameworkTool
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import com.fankes.apperrorstracking.utils.tool.ZipFileTool
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
|
||||
class AppErrorsRecordActivity : BaseActivity<ActivityAppErrorsRecordBinding>() {
|
||||
|
||||
companion object {
|
||||
|
||||
/** 请求保存文件回调标识 */
|
||||
private const val WRITE_REQUEST_CODE = 0
|
||||
}
|
||||
|
||||
/** 当前导出文件的路径 */
|
||||
private var outPutFilePath = ""
|
||||
|
||||
/** 回调适配器改变 */
|
||||
private var onChanged: (() -> Unit)? = null
|
||||
|
||||
@@ -60,8 +74,12 @@ class AppErrorsRecordActivity : BaseActivity<ActivityAppErrorsRecordBinding>() {
|
||||
}
|
||||
}
|
||||
binding.exportAllIcon.setOnClickListener {
|
||||
// TODO 待实现
|
||||
toast(msg = "Coming soon")
|
||||
showDialog {
|
||||
title = LocaleString.notice
|
||||
msg = LocaleString.areYouSureExportAllErrors
|
||||
confirmButton { exportAll() }
|
||||
cancelButton()
|
||||
}
|
||||
}
|
||||
/** 设置列表元素和 Adapter */
|
||||
binding.listView.apply {
|
||||
@@ -84,7 +102,7 @@ class AppErrorsRecordActivity : BaseActivity<ActivityAppErrorsRecordBinding>() {
|
||||
getItem(position).also {
|
||||
holder.appIcon.setImageDrawable(appIcon(it.packageName))
|
||||
holder.appNameText.text = appName(it.packageName)
|
||||
holder.errorsTimeText.text = SimpleDateFormat.getDateTimeInstance().format(Date(it.timestamp))
|
||||
holder.errorsTimeText.text = it.time
|
||||
holder.errorTypeIcon.setImageResource(if (it.isNativeCrash) R.drawable.ic_cpp else R.drawable.ic_java)
|
||||
holder.errorTypeText.text = if (it.isNativeCrash) "Native crash" else it.exceptionClassName.let { text ->
|
||||
if (text.contains(other = ".")) text.split(".").let { e -> e[e.lastIndex] } else text
|
||||
@@ -112,6 +130,32 @@ class AppErrorsRecordActivity : BaseActivity<ActivityAppErrorsRecordBinding>() {
|
||||
}
|
||||
}
|
||||
|
||||
/** 打包导出全部 */
|
||||
private fun exportAll() {
|
||||
clearAllExportTemp()
|
||||
("${cacheDir.absolutePath}/temp").also { path ->
|
||||
File(path).mkdirs()
|
||||
listData.takeIf { it.isNotEmpty() }?.forEach {
|
||||
File("$path/${it.packageName}_${it.timestamp}.log").writeText(it.stackOutputContent)
|
||||
}
|
||||
outPutFilePath = "${cacheDir.absolutePath}/temp_${System.currentTimeMillis()}.zip"
|
||||
ZipFileTool.zipMultiFile(path, outPutFilePath)
|
||||
runCatching {
|
||||
startActivityForResult(Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
|
||||
addCategory(Intent.CATEGORY_OPENABLE)
|
||||
type = "*/application"
|
||||
putExtra(Intent.EXTRA_TITLE, "app_errors_info_${System.currentTimeMillis()}.zip")
|
||||
}, WRITE_REQUEST_CODE)
|
||||
}.onFailure { toast(msg = "Start Android SAF failed") }
|
||||
}
|
||||
}
|
||||
|
||||
/** 清空导出的临时文件 */
|
||||
private fun clearAllExportTemp() {
|
||||
cacheDir.deleteRecursively()
|
||||
cacheDir.mkdirs()
|
||||
}
|
||||
|
||||
override fun onCreateContextMenu(menu: ContextMenu?, v: View?, menuInfo: ContextMenu.ContextMenuInfo?) {
|
||||
menuInflater.inflate(R.menu.menu_list_detail_action, menu)
|
||||
super.onCreateContextMenu(menu, v, menuInfo)
|
||||
@@ -128,6 +172,17 @@ class AppErrorsRecordActivity : BaseActivity<ActivityAppErrorsRecordBinding>() {
|
||||
return super.onContextItemSelected(item)
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
if (requestCode == WRITE_REQUEST_CODE && resultCode == Activity.RESULT_OK) runCatching {
|
||||
data?.data?.let {
|
||||
contentResolver?.openOutputStream(it)?.apply { write(FileInputStream(outPutFilePath).readBytes()) }?.close()
|
||||
clearAllExportTemp()
|
||||
toast(LocaleString.exportAllErrorsSuccess)
|
||||
} ?: toast(LocaleString.exportAllErrorsFail)
|
||||
}.onFailure { toast(LocaleString.exportAllErrorsFail) }
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
/** 执行更新 */
|
||||
|
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
* 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 2022/5/13.
|
||||
*/
|
||||
@file:Suppress("unused")
|
||||
|
||||
package com.fankes.apperrorstracking.utils.tool
|
||||
|
||||
import java.io.*
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipInputStream
|
||||
import java.util.zip.ZipOutputStream
|
||||
|
||||
/**
|
||||
* 处理压缩文件
|
||||
*/
|
||||
object ZipFileTool {
|
||||
|
||||
private const val BUFF_SIZE = 2048
|
||||
|
||||
/**
|
||||
* 压缩整个文件夹中的所有文件 - 生成指定名称的 Zip 压缩包
|
||||
* @param filePath 文件所在目录
|
||||
* @param zipPath 压缩后 Zip 文件名称
|
||||
* @param isDirFlag Zip 文件中第一层是否包含一级目录 - true 包含 false没有
|
||||
*/
|
||||
fun zipMultiFile(filePath: String, zipPath: String, isDirFlag: Boolean = false) {
|
||||
runCatching {
|
||||
val file = File(filePath)
|
||||
val zipFile = File(zipPath)
|
||||
val zipOut = ZipOutputStream(FileOutputStream(zipFile))
|
||||
if (file.isDirectory) {
|
||||
val files = file.listFiles() ?: emptyArray()
|
||||
for (fileSec in files)
|
||||
if (isDirFlag) recursionZip(zipOut, fileSec, file.name + File.separator)
|
||||
else recursionZip(zipOut, fileSec, "")
|
||||
}
|
||||
zipOut.close()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解压文件
|
||||
* @param unZipPath 解压后的目录
|
||||
* @param zipPath 压缩文件目录
|
||||
*/
|
||||
fun unZipFile(unZipPath: String, zipPath: String) {
|
||||
runCatching {
|
||||
unZipFileByInput(unZipPath, FileInputStream(zipPath))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解压文件
|
||||
* @param unZipPath 解压后的目录
|
||||
* @param zips 压缩文件流
|
||||
*/
|
||||
private fun unZipFileByInput(unZipPath: String, zips: FileInputStream) {
|
||||
val path = createSeparator(unZipPath)
|
||||
var bos: BufferedOutputStream? = null
|
||||
var zis: ZipInputStream? = null
|
||||
try {
|
||||
var filename: String
|
||||
zis = ZipInputStream(BufferedInputStream(zips))
|
||||
var ze: ZipEntry
|
||||
val buffer = ByteArray(BUFF_SIZE)
|
||||
var count: Int
|
||||
while (zis.nextEntry.also { ze = it } != null) {
|
||||
filename = ze.name
|
||||
createSubFolders(filename, path)
|
||||
if (ze.isDirectory) {
|
||||
val fmd = File(path + filename)
|
||||
fmd.mkdirs()
|
||||
continue
|
||||
}
|
||||
bos = BufferedOutputStream(FileOutputStream(path + filename))
|
||||
while (zis.read(buffer).also { count = it } != -1) bos.write(buffer, 0, count)
|
||||
bos.flush()
|
||||
bos.close()
|
||||
}
|
||||
} catch (_: IOException) {
|
||||
} finally {
|
||||
runCatching {
|
||||
if (zis != null) {
|
||||
zis.closeEntry()
|
||||
zis.close()
|
||||
}
|
||||
bos?.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @base code */
|
||||
private fun recursionZip(zipOut: ZipOutputStream, file: File, baseDir: String) {
|
||||
if (file.isDirectory) {
|
||||
val files = file.listFiles() ?: emptyArray()
|
||||
for (fileSec in files) recursionZip(zipOut, fileSec, baseDir + file.name + File.separator)
|
||||
} else {
|
||||
val buf = ByteArray(1024)
|
||||
val input: InputStream = FileInputStream(file)
|
||||
zipOut.putNextEntry(ZipEntry(baseDir + file.name))
|
||||
var len: Int
|
||||
while (input.read(buf).also { len = it } != -1) {
|
||||
zipOut.write(buf, 0, len)
|
||||
}
|
||||
input.close()
|
||||
}
|
||||
}
|
||||
|
||||
/** @base code */
|
||||
private fun createSubFolders(filename: String, path: String) {
|
||||
val subFolders = filename.split("/").toTypedArray()
|
||||
if (subFolders.size <= 1) return
|
||||
var pathNow = path
|
||||
for (i in 0 until subFolders.size - 1) {
|
||||
pathNow = pathNow + subFolders[i] + "/"
|
||||
val fmd = File(pathNow)
|
||||
if (fmd.exists()) continue
|
||||
fmd.mkdirs()
|
||||
}
|
||||
}
|
||||
|
||||
/** @base code */
|
||||
private fun createSeparator(path: String): String {
|
||||
val dir = File(path)
|
||||
if (!dir.exists()) dir.mkdirs()
|
||||
return if (path.endsWith("/")) path else "$path/"
|
||||
}
|
||||
}
|
@@ -40,4 +40,6 @@
|
||||
<string name="all_errors_clear_success">すべてのアラーレコードがクリアされました</string>
|
||||
<string name="are_you_sure_export_all_errors">すべてのログファイルをエクスポートしてもよろしいですか? 梱包プロセスには時間がかかる場合があります。</string>
|
||||
<string name="view_detail">詳細を見る</string>
|
||||
<string name="export_all_errors_success">すべてのエラーレコードがエクスポートされました</string>
|
||||
<string name="export_all_errors_fail">すべてのエラーレコードのエクスポートに失敗しました</string>
|
||||
</resources>
|
@@ -40,4 +40,6 @@
|
||||
<string name="all_errors_clear_success">全部异常记录已清空</string>
|
||||
<string name="are_you_sure_export_all_errors">你确定要导出全部日志文件吗?打包过程可能会花费一点时间。</string>
|
||||
<string name="view_detail">查看详情</string>
|
||||
<string name="export_all_errors_success">已导出全部异常记录</string>
|
||||
<string name="export_all_errors_fail">导出全部异常记录失败</string>
|
||||
</resources>
|
@@ -40,4 +40,6 @@
|
||||
<string name="all_errors_clear_success">全部異常紀錄已清空</string>
|
||||
<string name="are_you_sure_export_all_errors">你確認要導出全部日誌文件嗎?打包過程可能會花費一點時間。</string>
|
||||
<string name="view_detail">查看詳情</string>
|
||||
<string name="export_all_errors_success">已導出全部異常紀錄</string>
|
||||
<string name="export_all_errors_fail">導出全部異常紀錄失敗</string>
|
||||
</resources>
|
@@ -40,4 +40,6 @@
|
||||
<string name="all_errors_clear_success">全部異常紀錄已清空</string>
|
||||
<string name="are_you_sure_export_all_errors">你確認要導出全部日誌文件嗎?打包過程可能會花費一點時間。</string>
|
||||
<string name="view_detail">查看詳情</string>
|
||||
<string name="export_all_errors_success">已導出全部異常紀錄</string>
|
||||
<string name="export_all_errors_fail">導出全部異常紀錄失敗</string>
|
||||
</resources>
|
@@ -40,4 +40,6 @@
|
||||
<string name="all_errors_clear_success">全部異常紀錄已清空</string>
|
||||
<string name="are_you_sure_export_all_errors">你確認要導出全部日誌文件嗎?打包過程可能會花費一點時間。</string>
|
||||
<string name="view_detail">查看詳情</string>
|
||||
<string name="export_all_errors_success">已導出全部異常紀錄</string>
|
||||
<string name="export_all_errors_fail">導出全部異常紀錄失敗</string>
|
||||
</resources>
|
@@ -39,4 +39,6 @@
|
||||
<string name="all_errors_clear_success">All errors records have been cleared</string>
|
||||
<string name="are_you_sure_export_all_errors">Are you sure you want to export all log files? The packaging process may take a while.</string>
|
||||
<string name="view_detail">View detail</string>
|
||||
<string name="export_all_errors_success">All errors record exported</string>
|
||||
<string name="export_all_errors_fail">Failed to exported all errors record</string>
|
||||
</resources>
|
Reference in New Issue
Block a user