mirror of
https://mirror.skon.top/github.com/gkd-kit/gkd
synced 2026-04-20 21:00:12 +08:00
feat: CrashReportPage
This commit is contained in:
@@ -15,6 +15,7 @@ import android.database.ContentObserver
|
||||
import android.net.Uri
|
||||
import android.os.PowerManager
|
||||
import android.provider.Settings
|
||||
import android.util.Log
|
||||
import android.view.WindowManager
|
||||
import android.view.accessibility.AccessibilityManager
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
@@ -22,9 +23,9 @@ import androidx.core.content.ContextCompat
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.MainScope
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.serialization.Serializable
|
||||
import li.songe.gkd.a11y.initA11yFeat
|
||||
import li.songe.gkd.data.CrashData
|
||||
import li.songe.gkd.data.selfAppInfo
|
||||
import li.songe.gkd.notif.initChannel
|
||||
import li.songe.gkd.service.clearHttpSubs
|
||||
@@ -34,9 +35,11 @@ import li.songe.gkd.store.initStore
|
||||
import li.songe.gkd.util.AndroidTarget
|
||||
import li.songe.gkd.util.LogUtils
|
||||
import li.songe.gkd.util.PKG_FLAGS
|
||||
import li.songe.gkd.util.deviceInfoDesc
|
||||
import li.songe.gkd.util.initAppState
|
||||
import li.songe.gkd.util.initSubsState
|
||||
import li.songe.gkd.util.initToast
|
||||
import li.songe.gkd.util.launchTry
|
||||
import li.songe.gkd.util.toast
|
||||
import org.lsposed.hiddenapibypass.HiddenApiBypass
|
||||
import kotlin.system.exitProcess
|
||||
@@ -196,7 +199,21 @@ class App : Application() {
|
||||
Thread.setDefaultUncaughtExceptionHandler { t, e ->
|
||||
toast(e.message ?: e.toString())
|
||||
LogUtils.d("UncaughtExceptionHandler", t, e)
|
||||
appScope.launch(Dispatchers.IO) {
|
||||
val mtime = System.currentTimeMillis()
|
||||
appScope.launchTry(Dispatchers.IO) {
|
||||
CrashData(
|
||||
id = mtime,
|
||||
mtime = mtime,
|
||||
device = deviceInfoDesc,
|
||||
androidVersionCode = android.os.Build.VERSION.SDK_INT,
|
||||
androidVersionName = android.os.Build.VERSION.RELEASE,
|
||||
versionCode = META.versionCode,
|
||||
versionName = META.versionName,
|
||||
name = e::class.java.name,
|
||||
message = e.message,
|
||||
thread = t.name,
|
||||
stackTrace = Log.getStackTraceString(e),
|
||||
).save()
|
||||
delay(1500)
|
||||
if (isActivityVisible) {
|
||||
startLaunchActivity()
|
||||
|
||||
@@ -94,6 +94,8 @@ import li.songe.gkd.ui.AuthA11yPage
|
||||
import li.songe.gkd.ui.AuthA11yRoute
|
||||
import li.songe.gkd.ui.BlockA11yAppListPage
|
||||
import li.songe.gkd.ui.BlockA11yAppListRoute
|
||||
import li.songe.gkd.ui.CrashReportPage
|
||||
import li.songe.gkd.ui.CrashReportRoute
|
||||
import li.songe.gkd.ui.EditBlockAppListPage
|
||||
import li.songe.gkd.ui.EditBlockAppListRoute
|
||||
import li.songe.gkd.ui.ImagePreviewPage
|
||||
@@ -119,6 +121,7 @@ import li.songe.gkd.ui.WebViewRoute
|
||||
import li.songe.gkd.ui.component.BuildDialog
|
||||
import li.songe.gkd.ui.component.PerfIcon
|
||||
import li.songe.gkd.ui.component.ShareDataDialog
|
||||
import li.songe.gkd.ui.component.ShareLogDlg
|
||||
import li.songe.gkd.ui.component.SubsSheet
|
||||
import li.songe.gkd.ui.component.TermsAcceptDialog
|
||||
import li.songe.gkd.ui.component.TextDialog
|
||||
@@ -278,6 +281,7 @@ class MainActivity : ComponentActivity() {
|
||||
entry<UpsertRuleGroupRoute> { UpsertRuleGroupPage(it) }
|
||||
entry<SubsAppGroupListRoute> { SubsAppGroupListPage(it) }
|
||||
entry<AppConfigRoute> { AppConfigPage(it) }
|
||||
entry<CrashReportRoute> { CrashReportPage() }
|
||||
},
|
||||
transitionSpec = {
|
||||
slideInHorizontally(initialOffsetX = { it }) togetherWith
|
||||
@@ -308,6 +312,7 @@ class MainActivity : ComponentActivity() {
|
||||
mainVm.inputSubsLinkOption.ContentDialog()
|
||||
mainVm.ruleGroupState.Render()
|
||||
TextDialog(mainVm.textFlow)
|
||||
ShareLogDlg(mainVm.showShareLogDlgFlow)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import li.songe.gkd.a11y.useA11yServiceEnabledFlow
|
||||
import li.songe.gkd.a11y.useEnabledA11yServicesFlow
|
||||
import li.songe.gkd.data.CrashData
|
||||
import li.songe.gkd.data.RawSubscription
|
||||
import li.songe.gkd.data.SubsItem
|
||||
import li.songe.gkd.data.importData
|
||||
@@ -33,6 +34,7 @@ import li.songe.gkd.store.createTextFlow
|
||||
import li.songe.gkd.store.storeFlow
|
||||
import li.songe.gkd.ui.AdvancedPageRoute
|
||||
import li.songe.gkd.ui.AppOpsAllowRoute
|
||||
import li.songe.gkd.ui.CrashReportRoute
|
||||
import li.songe.gkd.ui.SnapshotPageRoute
|
||||
import li.songe.gkd.ui.WebViewRoute
|
||||
import li.songe.gkd.ui.component.AlertDialogOptions
|
||||
@@ -51,7 +53,10 @@ import li.songe.gkd.util.UpdateStatus
|
||||
import li.songe.gkd.util.appIconMapFlow
|
||||
import li.songe.gkd.util.clearCache
|
||||
import li.songe.gkd.util.client
|
||||
import li.songe.gkd.util.crashFolder
|
||||
import li.songe.gkd.util.crashTempFolder
|
||||
import li.songe.gkd.util.findOption
|
||||
import li.songe.gkd.util.json
|
||||
import li.songe.gkd.util.launchTry
|
||||
import li.songe.gkd.util.openUri
|
||||
import li.songe.gkd.util.openWeChatScaner
|
||||
@@ -63,7 +68,9 @@ import li.songe.gkd.util.toast
|
||||
import li.songe.gkd.util.updateSubsMutex
|
||||
import li.songe.gkd.util.updateSubscription
|
||||
import rikka.shizuku.Shizuku
|
||||
import java.nio.file.Files
|
||||
import kotlin.reflect.jvm.jvmName
|
||||
import kotlin.time.Duration.Companion.days
|
||||
|
||||
class MainViewModel : BaseViewModel(), OnSimpleLife {
|
||||
companion object {
|
||||
@@ -323,6 +330,10 @@ class MainViewModel : BaseViewModel(), OnSimpleLife {
|
||||
uiAutomationFlow.value?.shutdown()
|
||||
}
|
||||
|
||||
val showShareLogDlgFlow = MutableStateFlow(false)
|
||||
|
||||
var tempCrashDataList = emptyList<CrashData>()
|
||||
|
||||
init {
|
||||
// preload
|
||||
appIconMapFlow.value
|
||||
@@ -360,6 +371,31 @@ class MainViewModel : BaseViewModel(), OnSimpleLife {
|
||||
// preload
|
||||
githubCookieFlow.value
|
||||
}
|
||||
viewModelScope.launchTry(Dispatchers.IO) {
|
||||
val list = (crashTempFolder.listFiles() ?: emptyArray()).mapNotNull {
|
||||
try {
|
||||
json.decodeFromString<CrashData>(it.readText())
|
||||
} catch (e: Exception) {
|
||||
LogUtils.d("解析崩溃日志失败: ${it.name}", e)
|
||||
null
|
||||
}
|
||||
}.sortedBy { -it.mtime }
|
||||
crashTempFolder.deleteRecursively()
|
||||
val t = System.currentTimeMillis()
|
||||
crashFolder.listFiles()?.filter {
|
||||
val name = it.name
|
||||
!list.any { f -> name == f.filename }
|
||||
}?.forEach {
|
||||
val mtime = Files.getLastModifiedTime(it.toPath()).toMillis()
|
||||
if (t - mtime > 30.days.inWholeMilliseconds) {
|
||||
it.delete()
|
||||
}
|
||||
}
|
||||
tempCrashDataList = list
|
||||
if (list.isNotEmpty()) {
|
||||
navigatePage(CrashReportRoute)
|
||||
}
|
||||
}
|
||||
|
||||
// for OnSimpleLife
|
||||
onCreated()
|
||||
|
||||
30
app/src/main/kotlin/li/songe/gkd/data/CrashData.kt
Normal file
30
app/src/main/kotlin/li/songe/gkd/data/CrashData.kt
Normal file
@@ -0,0 +1,30 @@
|
||||
package li.songe.gkd.data
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import li.songe.gkd.util.crashFolder
|
||||
import li.songe.gkd.util.crashTempFolder
|
||||
import li.songe.gkd.util.format
|
||||
import li.songe.gkd.util.json
|
||||
|
||||
@Serializable
|
||||
data class CrashData(
|
||||
val id: Long,
|
||||
val mtime: Long,
|
||||
val device: String,
|
||||
val androidVersionCode: Int,
|
||||
val androidVersionName: String,
|
||||
val versionCode: Int,
|
||||
val versionName: String,
|
||||
val name: String,
|
||||
val message: String?,
|
||||
val thread: String,
|
||||
val stackTrace: String,
|
||||
) {
|
||||
val filename get() = "gkd_crash-" + mtime.format("yyyyMMdd_HHmmss") + ".json"
|
||||
fun save() {
|
||||
val text = json.encodeToString(this)
|
||||
crashFolder.resolve(filename).writeText(text)
|
||||
crashTempFolder.resolve(filename).writeText(text)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -79,7 +79,6 @@ import li.songe.gkd.util.PLAY_STORE_URL
|
||||
import li.songe.gkd.util.REPOSITORY_URL
|
||||
import li.songe.gkd.util.ShortUrlSet
|
||||
import li.songe.gkd.util.UpdateChannelOption
|
||||
import li.songe.gkd.util.buildLogFile
|
||||
import li.songe.gkd.util.findOption
|
||||
import li.songe.gkd.util.format
|
||||
import li.songe.gkd.util.getShareApkFile
|
||||
@@ -146,7 +145,6 @@ fun AboutPage() {
|
||||
},
|
||||
)
|
||||
}
|
||||
var showShareLogDlg by vm.showShareLogDlgFlow.asMutableState()
|
||||
var showShareAppDlg by vm.showShareAppDlgFlow.asMutableState()
|
||||
val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior()
|
||||
Scaffold(
|
||||
@@ -287,7 +285,7 @@ fun AboutPage() {
|
||||
title = "导出日志",
|
||||
imageVector = PerfIcon.Share,
|
||||
onClick = {
|
||||
showShareLogDlg = true
|
||||
mainVm.showShareLogDlgFlow.value = true
|
||||
}
|
||||
)
|
||||
if (mainVm.updateStatus != null) {
|
||||
@@ -337,54 +335,6 @@ fun AboutPage() {
|
||||
}
|
||||
}
|
||||
|
||||
if (showShareLogDlg) {
|
||||
Dialog(onDismissRequest = { showShareLogDlg = false }) {
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp),
|
||||
shape = RoundedCornerShape(16.dp),
|
||||
) {
|
||||
val modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp)
|
||||
Text(
|
||||
text = "分享到其他应用", modifier = Modifier
|
||||
.clickable(onClick = throttle {
|
||||
showShareLogDlg = false
|
||||
mainVm.viewModelScope.launchTry(Dispatchers.IO) {
|
||||
val logZipFile = buildLogFile()
|
||||
context.shareFile(logZipFile, "分享日志文件")
|
||||
}
|
||||
})
|
||||
.then(modifier)
|
||||
)
|
||||
Text(
|
||||
text = "保存到下载", modifier = Modifier
|
||||
.clickable(onClick = throttle {
|
||||
showShareLogDlg = false
|
||||
mainVm.viewModelScope.launchTry(Dispatchers.IO) {
|
||||
val logZipFile = buildLogFile()
|
||||
context.saveFileToDownloads(logZipFile)
|
||||
}
|
||||
})
|
||||
.then(modifier)
|
||||
)
|
||||
Text(
|
||||
text = "生成链接(需科学上网)",
|
||||
modifier = Modifier
|
||||
.clickable(onClick = throttle {
|
||||
showShareLogDlg = false
|
||||
mainVm.uploadOptions.startTask(
|
||||
getFile = { buildLogFile() }
|
||||
)
|
||||
})
|
||||
.then(modifier)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (showShareAppDlg) {
|
||||
Dialog(onDismissRequest = { showShareAppDlg = false }) {
|
||||
Card(
|
||||
|
||||
@@ -5,6 +5,5 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
||||
|
||||
class AboutVm : ViewModel() {
|
||||
val showInfoDlgFlow = MutableStateFlow(false)
|
||||
val showShareLogDlgFlow = MutableStateFlow(false)
|
||||
val showShareAppDlgFlow = MutableStateFlow(false)
|
||||
}
|
||||
108
app/src/main/kotlin/li/songe/gkd/ui/CrashReportPage.kt
Normal file
108
app/src/main/kotlin/li/songe/gkd/ui/CrashReportPage.kt
Normal file
@@ -0,0 +1,108 @@
|
||||
package li.songe.gkd.ui
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.BottomAppBar
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.navigation3.runtime.NavKey
|
||||
import kotlinx.serialization.Serializable
|
||||
import li.songe.gkd.ui.component.CopyTextCard
|
||||
import li.songe.gkd.ui.component.EmptyText
|
||||
import li.songe.gkd.ui.component.PerfIcon
|
||||
import li.songe.gkd.ui.component.PerfIconButton
|
||||
import li.songe.gkd.ui.component.PerfTopAppBar
|
||||
import li.songe.gkd.ui.component.useScrollBehaviorState
|
||||
import li.songe.gkd.ui.share.LocalMainViewModel
|
||||
import li.songe.gkd.ui.share.noRippleClickable
|
||||
import li.songe.gkd.ui.style.EmptyHeight
|
||||
import li.songe.gkd.ui.style.itemHorizontalPadding
|
||||
import li.songe.gkd.ui.style.itemVerticalPadding
|
||||
import li.songe.gkd.util.ISSUES_URL
|
||||
import li.songe.gkd.util.throttle
|
||||
|
||||
|
||||
@Serializable
|
||||
data object CrashReportRoute : NavKey
|
||||
|
||||
@Composable
|
||||
fun CrashReportPage() {
|
||||
val mainVm = LocalMainViewModel.current
|
||||
val vm = viewModel<CrashReportVm>()
|
||||
val scrollKey = rememberSaveable { mutableIntStateOf(0) }
|
||||
val (scrollBehavior, scrollState) = useScrollBehaviorState(scrollKey)
|
||||
Scaffold(
|
||||
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||
topBar = {
|
||||
PerfTopAppBar(
|
||||
scrollBehavior = scrollBehavior,
|
||||
navigationIcon = {
|
||||
PerfIconButton(
|
||||
imageVector = PerfIcon.ArrowBack,
|
||||
onClick = mainVm::popPage,
|
||||
)
|
||||
},
|
||||
title = {
|
||||
Text(
|
||||
text = "崩溃记录",
|
||||
modifier = Modifier.noRippleClickable(onClick = throttle { scrollKey.intValue++ })
|
||||
)
|
||||
},
|
||||
)
|
||||
},
|
||||
bottomBar = {
|
||||
if (vm.crashDataList.isNotEmpty()) {
|
||||
BottomAppBar {
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
TextButton(
|
||||
onClick = throttle { mainVm.openUrl(ISSUES_URL) },
|
||||
) {
|
||||
Text(text = "问题反馈")
|
||||
}
|
||||
Spacer(modifier = Modifier.width(itemHorizontalPadding))
|
||||
TextButton(
|
||||
onClick = { mainVm.showShareLogDlgFlow.value = true },
|
||||
) {
|
||||
Text(text = "导出日志")
|
||||
}
|
||||
Spacer(modifier = Modifier.width(itemHorizontalPadding))
|
||||
}
|
||||
}
|
||||
},
|
||||
) { contentPadding ->
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.verticalScroll(scrollState)
|
||||
.fillMaxSize()
|
||||
.padding(contentPadding),
|
||||
verticalArrangement = Arrangement.spacedBy(itemVerticalPadding)
|
||||
) {
|
||||
if (vm.crashDataList.isNotEmpty()) {
|
||||
vm.crashDataList.forEach { crashData ->
|
||||
CopyTextCard(
|
||||
text = crashData.stackTrace,
|
||||
modifier = Modifier.padding(horizontal = 8.dp),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
Spacer(modifier = Modifier.height(EmptyHeight))
|
||||
EmptyText()
|
||||
}
|
||||
Spacer(modifier = Modifier.height(EmptyHeight))
|
||||
}
|
||||
}
|
||||
}
|
||||
12
app/src/main/kotlin/li/songe/gkd/ui/CrashReportVm.kt
Normal file
12
app/src/main/kotlin/li/songe/gkd/ui/CrashReportVm.kt
Normal file
@@ -0,0 +1,12 @@
|
||||
package li.songe.gkd.ui
|
||||
|
||||
import li.songe.gkd.MainViewModel
|
||||
import li.songe.gkd.ui.share.BaseViewModel
|
||||
|
||||
class CrashReportVm : BaseViewModel() {
|
||||
val crashDataList = MainViewModel.instance.run {
|
||||
val v = tempCrashDataList
|
||||
tempCrashDataList = emptyList()
|
||||
v
|
||||
}
|
||||
}
|
||||
80
app/src/main/kotlin/li/songe/gkd/ui/component/ShareLogDlg.kt
Normal file
80
app/src/main/kotlin/li/songe/gkd/ui/component/ShareLogDlg.kt
Normal file
@@ -0,0 +1,80 @@
|
||||
package li.songe.gkd.ui.component
|
||||
|
||||
import androidx.activity.compose.LocalActivity
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import li.songe.gkd.MainActivity
|
||||
import li.songe.gkd.ui.share.LocalMainViewModel
|
||||
import li.songe.gkd.ui.share.asMutableState
|
||||
import li.songe.gkd.util.buildLogFile
|
||||
import li.songe.gkd.util.launchTry
|
||||
import li.songe.gkd.util.saveFileToDownloads
|
||||
import li.songe.gkd.util.shareFile
|
||||
import li.songe.gkd.util.throttle
|
||||
|
||||
@Composable
|
||||
fun ShareLogDlg(showShareLogDlgFlow: MutableStateFlow<Boolean>) {
|
||||
var visible by showShareLogDlgFlow.asMutableState()
|
||||
if (visible) {
|
||||
val mainVm = LocalMainViewModel.current
|
||||
val context = LocalActivity.current as MainActivity
|
||||
Dialog(onDismissRequest = { visible = false }) {
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp),
|
||||
shape = RoundedCornerShape(16.dp),
|
||||
) {
|
||||
val modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp)
|
||||
Text(
|
||||
text = "分享到其他应用", modifier = Modifier
|
||||
.clickable(onClick = throttle {
|
||||
visible = false
|
||||
mainVm.viewModelScope.launchTry(Dispatchers.IO) {
|
||||
val logZipFile = buildLogFile()
|
||||
context.shareFile(logZipFile, "分享日志文件")
|
||||
}
|
||||
})
|
||||
.then(modifier)
|
||||
)
|
||||
Text(
|
||||
text = "保存到下载", modifier = Modifier
|
||||
.clickable(onClick = throttle {
|
||||
visible = false
|
||||
mainVm.viewModelScope.launchTry(Dispatchers.IO) {
|
||||
val logZipFile = buildLogFile()
|
||||
context.saveFileToDownloads(logZipFile)
|
||||
}
|
||||
})
|
||||
.then(modifier)
|
||||
)
|
||||
Text(
|
||||
text = "生成链接(需科学上网)",
|
||||
modifier = Modifier
|
||||
.clickable(onClick = throttle {
|
||||
visible = false
|
||||
mainVm.uploadOptions.startTask(
|
||||
getFile = { buildLogFile() }
|
||||
)
|
||||
})
|
||||
.then(modifier)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -36,6 +36,10 @@ val snapshotFolder: File
|
||||
get() = filesDir.resolve("snapshot").autoMk()
|
||||
val logFolder: File
|
||||
get() = filesDir.resolve("log").autoMk()
|
||||
val crashFolder: File
|
||||
get() = filesDir.resolve("crash").autoMk()
|
||||
val crashTempFolder: File
|
||||
get() = filesDir.resolve("crash/temp").autoMk()
|
||||
|
||||
val privateStoreFolder: File
|
||||
get() = app.filesDir.resolve("store").autoMk()
|
||||
@@ -82,7 +86,7 @@ private data class AppJsonData(
|
||||
@WorkerThread
|
||||
fun buildLogFile(): File {
|
||||
val tempDir = createGkdTempDir()
|
||||
val files = mutableListOf(dbFolder, storeFolder, subsFolder, logFolder)
|
||||
val files = mutableListOf(dbFolder, storeFolder, subsFolder, logFolder, crashFolder)
|
||||
tempDir.resolve("meta.json").also {
|
||||
it.writeText(toJson5String(META))
|
||||
files.add(it)
|
||||
|
||||
@@ -46,17 +46,19 @@ object LogUtils {
|
||||
|
||||
private val logFileExecutor = Executors.newSingleThreadExecutor()
|
||||
private const val MAX_LOG_KEEP_DAYS = 7
|
||||
private val deviceInfoText by lazy {
|
||||
val deviceInfos = listOf(
|
||||
val deviceInfoDesc by lazy {
|
||||
listOf(
|
||||
android.os.Build.MANUFACTURER,
|
||||
android.os.Build.MODEL,
|
||||
DeviceBrand.getBrandName(),
|
||||
DeviceOs.getOsName() + DeviceOs.getOsVersionName() + DeviceOs.getOsBigVersionCode(),
|
||||
DeviceMarketName.getMarketName(app)
|
||||
)
|
||||
).joinToString("/")
|
||||
}
|
||||
private val deviceInfoText by lazy {
|
||||
buildString {
|
||||
append("Android: ${android.os.Build.VERSION.RELEASE} (${android.os.Build.VERSION.SDK_INT})\n")
|
||||
append("Device: ${deviceInfos.joinToString("/")}\n")
|
||||
append("Device: ${deviceInfoDesc}\n")
|
||||
append("App: ${META.versionName} (${META.versionCode})\n")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user