mirror of
https://github.com/KitsunePie/AppErrorsTracking.git
synced 2025-09-01 16:55:18 +08:00
Added function AppErrorsRecordActivity and support native crash message
This commit is contained in:
@@ -44,13 +44,15 @@ Added more features to app's crash dialog, fixed custom rom deleted dialog, the
|
||||
|
||||
- 打印异常堆栈到控制台功能
|
||||
|
||||
- 异常历史记录功能,可通过通知栏磁贴“异常历史记录”进入和模块主界面进入(正在开发)
|
||||
|
||||
- 支持 Android 10 及以上系统的深色模式
|
||||
|
||||
## Future
|
||||
|
||||
此项目依然在开发中,现在未解决的问题和包含的问题如下
|
||||
|
||||
- 异常历史记录和排除列表正在开发
|
||||
- 排除列表和异常历史记录导出、清空功能正在开发
|
||||
|
||||
- 后台进程可能依然会弹出崩溃对话框且开发者选项里的设置无效,还在排查
|
||||
|
||||
|
@@ -31,6 +31,28 @@
|
||||
<activity
|
||||
android:name=".ui.activity.AppErrorsDetailActivity"
|
||||
android:exported="true"
|
||||
android:screenOrientation="behind" />
|
||||
android:launchMode="singleTask"
|
||||
android:screenOrientation="behind"
|
||||
android:taskAffinity=":detail" />
|
||||
|
||||
<activity
|
||||
android:name=".ui.activity.AppErrorsRecordActivity"
|
||||
android:exported="true"
|
||||
android:label="@string/errors_record"
|
||||
android:launchMode="singleTask"
|
||||
android:screenOrientation="behind"
|
||||
android:taskAffinity=":history" />
|
||||
|
||||
<service
|
||||
android:name=".service.QuickStartTileService"
|
||||
android:exported="true"
|
||||
android:icon="@drawable/ic_debug"
|
||||
android:label="@string/errors_record"
|
||||
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.service.quicksettings.action.QS_TILE" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
</application>
|
||||
</manifest>
|
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* 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/12.
|
||||
*/
|
||||
package com.fankes.apperrorstracking.const
|
||||
|
||||
import com.fankes.apperrorstracking.bean.AppErrorsInfoBean
|
||||
|
||||
/**
|
||||
* 全局常量
|
||||
*/
|
||||
object Const {
|
||||
|
||||
/** [AppErrorsInfoBean] 传值 */
|
||||
const val EXTRA_APP_ERRORS_INFO = "app_errors_info_extra"
|
||||
|
||||
/** 模块接收广播 */
|
||||
const val ACTION_MODULE_HANDLER_RECEIVER = "module_handler_action"
|
||||
|
||||
/** 宿主接收广播 */
|
||||
const val ACTION_HOST_HANDLER_RECEIVER = "host_handler_action"
|
||||
|
||||
/** [AppErrorsInfoBean] 控制数据 */
|
||||
const val TYPE_APP_ERRORS_DATA_CONTROL = "app_errors_data_control_type"
|
||||
|
||||
/** [AppErrorsInfoBean] 获取数据 */
|
||||
const val TYPE_APP_ERRORS_DATA_CONTROL_GET_DATA = "app_errors_data_control_get_data_type"
|
||||
|
||||
/** [AppErrorsInfoBean] 获取到的数据内容 */
|
||||
const val TAG_APP_ERRORS_DATA_CONTENT = "app_errors_data_content_tag"
|
||||
}
|
@@ -41,6 +41,7 @@ import android.widget.TextView
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import com.fankes.apperrorstracking.R
|
||||
import com.fankes.apperrorstracking.bean.AppErrorsInfoBean
|
||||
import com.fankes.apperrorstracking.const.Const
|
||||
import com.fankes.apperrorstracking.locale.LocaleString
|
||||
import com.fankes.apperrorstracking.ui.activity.AppErrorsDetailActivity
|
||||
import com.fankes.apperrorstracking.utils.drawable.drawabletoolbox.DrawableBuilder
|
||||
@@ -51,12 +52,11 @@ import com.highcapable.yukihookapi.hook.factory.field
|
||||
import com.highcapable.yukihookapi.hook.factory.hasMethod
|
||||
import com.highcapable.yukihookapi.hook.factory.method
|
||||
import com.highcapable.yukihookapi.hook.log.loggerE
|
||||
import com.highcapable.yukihookapi.hook.type.android.ActivityThreadClass
|
||||
import com.highcapable.yukihookapi.hook.type.android.MessageClass
|
||||
|
||||
object FrameworkHooker : YukiBaseHooker() {
|
||||
|
||||
const val APP_ERRORS_INFO = "app_errors_info"
|
||||
|
||||
private const val AppErrorsClass = "com.android.server.am.AppErrors"
|
||||
|
||||
private const val AppErrorResultClass = "com.android.server.am.AppErrorResult"
|
||||
@@ -110,6 +110,23 @@ object FrameworkHooker : YukiBaseHooker() {
|
||||
}
|
||||
}
|
||||
|
||||
/** 宿主广播接收器 */
|
||||
private val hostHandlerReceiver by lazy {
|
||||
object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context?, intent: Intent?) {
|
||||
if (intent == null) return
|
||||
when (intent.getStringExtra(Const.TYPE_APP_ERRORS_DATA_CONTROL)) {
|
||||
Const.TYPE_APP_ERRORS_DATA_CONTROL_GET_DATA ->
|
||||
context?.sendBroadcast(Intent().apply {
|
||||
action = Const.ACTION_MODULE_HANDLER_RECEIVER
|
||||
putExtra(Const.TYPE_APP_ERRORS_DATA_CONTROL, Const.TYPE_APP_ERRORS_DATA_CONTROL_GET_DATA)
|
||||
putExtra(Const.TAG_APP_ERRORS_DATA_CONTENT, appErrorsRecords)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册广播接收器
|
||||
* @param context 实例
|
||||
@@ -118,6 +135,7 @@ object FrameworkHooker : YukiBaseHooker() {
|
||||
if (isRegisterReceiver) return
|
||||
context.registerReceiver(userPresentReceiver, IntentFilter().apply { addAction(Intent.ACTION_USER_PRESENT) })
|
||||
context.registerReceiver(localeChangedReceiver, IntentFilter().apply { addAction(Intent.ACTION_LOCALE_CHANGED) })
|
||||
context.registerReceiver(hostHandlerReceiver, IntentFilter().apply { addAction(Const.ACTION_HOST_HANDLER_RECEIVER) })
|
||||
isRegisterReceiver = true
|
||||
}
|
||||
|
||||
@@ -161,6 +179,16 @@ object FrameworkHooker : YukiBaseHooker() {
|
||||
}
|
||||
|
||||
override fun onHook() {
|
||||
/** 注入全局监听 */
|
||||
ActivityThreadClass.hook {
|
||||
injectMember {
|
||||
method {
|
||||
name = "currentApplication"
|
||||
emptyParam()
|
||||
}
|
||||
afterHook { result<Context>()?.let { registerReceiver(it) } }
|
||||
}
|
||||
}
|
||||
/** 干掉原生错误对话框 - 如果有 */
|
||||
ErrorDialogControllerClass.hook {
|
||||
injectMember {
|
||||
@@ -229,8 +257,6 @@ object FrameworkHooker : YukiBaseHooker() {
|
||||
|
||||
/** 是否短时内重复错误 */
|
||||
val isRepeating = AppErrorDialog_DataClass.clazz.field { name = "repeating" }.get(errData).boolean()
|
||||
/** 注册广播 */
|
||||
registerReceiver(context)
|
||||
/** 打印错误日志 */
|
||||
loggerE(msg = "Process \"$packageName\" has crashed${if (isRepeating) " again" else ""}")
|
||||
/** 判断是否被忽略 - 在后台就不显示对话框 */
|
||||
@@ -317,20 +343,26 @@ object FrameworkHooker : YukiBaseHooker() {
|
||||
/** 当前异常信息 */
|
||||
args().last().cast<ApplicationErrorReport.CrashInfo>()?.also { crashInfo ->
|
||||
/** 添加到第一位 */
|
||||
appErrorsRecords.add(
|
||||
0, AppErrorsInfoBean(
|
||||
packageName = appInfo?.packageName ?: "",
|
||||
isNativeCrash = crashInfo.exceptionClassName.lowercase() == "native crash",
|
||||
exceptionClassName = crashInfo.exceptionClassName ?: "",
|
||||
exceptionMessage = crashInfo.exceptionMessage ?: "",
|
||||
throwFileName = crashInfo.throwFileName ?: "",
|
||||
throwClassName = crashInfo.throwClassName ?: "",
|
||||
throwMethodName = crashInfo.throwMethodName ?: "",
|
||||
throwLineNumber = crashInfo.throwLineNumber,
|
||||
stackTrace = crashInfo.stackTrace?.trim() ?: "",
|
||||
timestamp = System.currentTimeMillis()
|
||||
(crashInfo.exceptionClassName.lowercase() == "native crash").also { isNativeCrash ->
|
||||
appErrorsRecords.add(
|
||||
0, AppErrorsInfoBean(
|
||||
packageName = appInfo?.packageName ?: "",
|
||||
isNativeCrash = isNativeCrash,
|
||||
exceptionClassName = crashInfo.exceptionClassName ?: "",
|
||||
exceptionMessage = if (isNativeCrash) crashInfo.stackTrace.let {
|
||||
if (it.contains(other = "Abort message: '"))
|
||||
runCatching { it.split("Abort message: '")[1].split("'")[0] }.getOrNull()
|
||||
?: crashInfo.exceptionMessage ?: "" else crashInfo.exceptionMessage ?: ""
|
||||
} else crashInfo.exceptionMessage ?: "",
|
||||
throwFileName = crashInfo.throwFileName ?: "",
|
||||
throwClassName = crashInfo.throwClassName ?: "",
|
||||
throwMethodName = crashInfo.throwMethodName ?: "",
|
||||
throwLineNumber = crashInfo.throwLineNumber,
|
||||
stackTrace = crashInfo.stackTrace?.trim() ?: "",
|
||||
timestamp = System.currentTimeMillis()
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* 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/11.
|
||||
*/
|
||||
package com.fankes.apperrorstracking.service
|
||||
|
||||
import android.service.quicksettings.TileService
|
||||
import com.fankes.apperrorstracking.ui.activity.AppErrorsRecordActivity
|
||||
import com.fankes.apperrorstracking.utils.factory.navigate
|
||||
|
||||
class QuickStartTileService : TileService() {
|
||||
|
||||
override fun onClick() {
|
||||
super.onClick()
|
||||
/** 启动异常历史记录窗口 */
|
||||
navigate<AppErrorsRecordActivity>()
|
||||
}
|
||||
}
|
@@ -29,8 +29,8 @@ import android.content.Intent
|
||||
import androidx.core.view.isGone
|
||||
import com.fankes.apperrorstracking.R
|
||||
import com.fankes.apperrorstracking.bean.AppErrorsInfoBean
|
||||
import com.fankes.apperrorstracking.const.Const
|
||||
import com.fankes.apperrorstracking.databinding.ActivityAppErrorsDetailBinding
|
||||
import com.fankes.apperrorstracking.hook.entity.FrameworkHooker
|
||||
import com.fankes.apperrorstracking.locale.LocaleString
|
||||
import com.fankes.apperrorstracking.ui.activity.base.BaseActivity
|
||||
import com.fankes.apperrorstracking.utils.factory.*
|
||||
@@ -52,7 +52,7 @@ class AppErrorsDetailActivity : BaseActivity<ActivityAppErrorsDetailBinding>() {
|
||||
* @param isOutSide 是否从外部启动
|
||||
*/
|
||||
fun start(context: Context, appErrorsInfo: AppErrorsInfoBean, isOutSide: Boolean = false) =
|
||||
context.navigate<AppErrorsDetailActivity>(isOutSide) { putExtra(FrameworkHooker.APP_ERRORS_INFO, appErrorsInfo) }
|
||||
context.navigate<AppErrorsDetailActivity>(isOutSide) { putExtra(Const.EXTRA_APP_ERRORS_INFO, appErrorsInfo) }
|
||||
}
|
||||
|
||||
/** 预导出的异常堆栈 */
|
||||
@@ -60,7 +60,7 @@ class AppErrorsDetailActivity : BaseActivity<ActivityAppErrorsDetailBinding>() {
|
||||
|
||||
override fun onCreate() {
|
||||
val appErrorsInfo =
|
||||
intent?.getSerializableExtra(FrameworkHooker.APP_ERRORS_INFO) as? AppErrorsInfoBean ?: return toastAndFinish()
|
||||
intent?.getSerializableExtra(Const.EXTRA_APP_ERRORS_INFO) as? AppErrorsInfoBean ?: return toastAndFinish()
|
||||
|
||||
/** 创建异常堆栈模板 */
|
||||
fun createStack() =
|
||||
@@ -113,7 +113,7 @@ class AppErrorsDetailActivity : BaseActivity<ActivityAppErrorsDetailBinding>() {
|
||||
}
|
||||
|
||||
override fun onBackPressed() {
|
||||
intent?.removeExtra(FrameworkHooker.APP_ERRORS_INFO)
|
||||
intent?.removeExtra(Const.EXTRA_APP_ERRORS_INFO)
|
||||
finish()
|
||||
}
|
||||
}
|
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
* 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/11.
|
||||
*/
|
||||
@file:Suppress("UNCHECKED_CAST")
|
||||
|
||||
package com.fankes.apperrorstracking.ui.activity
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.BaseAdapter
|
||||
import androidx.core.view.isVisible
|
||||
import com.fankes.apperrorstracking.R
|
||||
import com.fankes.apperrorstracking.bean.AppErrorsInfoBean
|
||||
import com.fankes.apperrorstracking.const.Const
|
||||
import com.fankes.apperrorstracking.databinding.ActivityAppErrorsRecordBinding
|
||||
import com.fankes.apperrorstracking.databinding.AdapterAppErrorsRecordBinding
|
||||
import com.fankes.apperrorstracking.ui.activity.base.BaseActivity
|
||||
import com.fankes.apperrorstracking.utils.factory.appIcon
|
||||
import com.fankes.apperrorstracking.utils.factory.appName
|
||||
import com.fankes.apperrorstracking.utils.factory.toast
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
class AppErrorsRecordActivity : BaseActivity<ActivityAppErrorsRecordBinding>() {
|
||||
|
||||
/** 回调适配器改变 */
|
||||
private var onChanged: (() -> Unit)? = null
|
||||
|
||||
/** 全部的 APP 异常数据 */
|
||||
private val listData = ArrayList<AppErrorsInfoBean>()
|
||||
|
||||
override fun onCreate() {
|
||||
binding.titleBackIcon.setOnClickListener { onBackPressed() }
|
||||
binding.clearAllIcon.setOnClickListener {
|
||||
// TODO 待实现
|
||||
toast(msg = "Coming soon")
|
||||
}
|
||||
binding.exportAllIcon.setOnClickListener {
|
||||
// TODO 待实现
|
||||
toast(msg = "Coming soon")
|
||||
}
|
||||
/** 设置列表元素和 Adapter */
|
||||
binding.listView.apply {
|
||||
adapter = object : BaseAdapter() {
|
||||
|
||||
override fun getCount() = listData.size
|
||||
|
||||
override fun getItem(position: Int) = listData[position]
|
||||
|
||||
override fun getItemId(position: Int) = position.toLong()
|
||||
|
||||
override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
|
||||
var cView = convertView
|
||||
val holder: AdapterAppErrorsRecordBinding
|
||||
if (convertView == null) {
|
||||
holder = AdapterAppErrorsRecordBinding.inflate(LayoutInflater.from(context))
|
||||
cView = holder.root
|
||||
cView.tag = holder
|
||||
} else holder = convertView.tag as AdapterAppErrorsRecordBinding
|
||||
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.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
|
||||
}
|
||||
holder.errorMsgText.text = it.exceptionMessage
|
||||
}
|
||||
return cView!!
|
||||
}
|
||||
}.apply {
|
||||
setOnItemClickListener { _, _, p, _ -> AppErrorsDetailActivity.start(context, listData[p]) }
|
||||
onChanged = { notifyDataSetChanged() }
|
||||
}
|
||||
}
|
||||
/** 注册广播 */
|
||||
registerReceiver(moduleHandlerReceiver, IntentFilter().apply { addAction(Const.ACTION_MODULE_HANDLER_RECEIVER) })
|
||||
}
|
||||
|
||||
/** 更新列表数据 */
|
||||
private fun refreshData() {
|
||||
sendBroadcast(Intent().apply {
|
||||
action = Const.ACTION_HOST_HANDLER_RECEIVER
|
||||
putExtra(Const.TYPE_APP_ERRORS_DATA_CONTROL, Const.TYPE_APP_ERRORS_DATA_CONTROL_GET_DATA)
|
||||
})
|
||||
}
|
||||
|
||||
/** 模块广播接收器 */
|
||||
private val moduleHandlerReceiver by lazy {
|
||||
object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context?, intent: Intent?) {
|
||||
if (intent == null) return
|
||||
when (intent.getStringExtra(Const.TYPE_APP_ERRORS_DATA_CONTROL)) {
|
||||
Const.TYPE_APP_ERRORS_DATA_CONTROL_GET_DATA ->
|
||||
(intent.getSerializableExtra(Const.TAG_APP_ERRORS_DATA_CONTENT) as? ArrayList<AppErrorsInfoBean>)?.also {
|
||||
listData.clear()
|
||||
it.takeIf { e -> e.isNotEmpty() }?.forEach { e -> listData.add(e) }
|
||||
onChanged?.invoke()
|
||||
binding.listView.isVisible = listData.isNotEmpty()
|
||||
binding.listNoDataView.isVisible = listData.isEmpty()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
/** 执行更新 */
|
||||
refreshData()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
/** 取消注册 */
|
||||
unregisterReceiver(moduleHandlerReceiver)
|
||||
}
|
||||
}
|
10
app/src/main/res/drawable-night/bg_permotion_ripple.xml
Normal file
10
app/src/main/res/drawable-night/bg_permotion_ripple.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:color="#777777">
|
||||
<item>
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="#666E6E6E" />
|
||||
<corners android:radius="15dp" />
|
||||
</shape>
|
||||
</item>
|
||||
</ripple>
|
10
app/src/main/res/drawable/bg_permotion_ripple.xml
Normal file
10
app/src/main/res/drawable/bg_permotion_ripple.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:color="#777777">
|
||||
<item>
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="#66E4E4E4" />
|
||||
<corners android:radius="15dp" />
|
||||
</shape>
|
||||
</item>
|
||||
</ripple>
|
6
app/src/main/res/drawable/bg_red_round.xml
Normal file
6
app/src/main/res/drawable/bg_red_round.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="#EF5350" />
|
||||
<corners android:radius="5dp" />
|
||||
</shape>
|
9
app/src/main/res/drawable/ic_clear.xml
Normal file
9
app/src/main/res/drawable/ic_clear.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="150dp"
|
||||
android:height="150dp"
|
||||
android:viewportWidth="1024"
|
||||
android:viewportHeight="1024">
|
||||
<path
|
||||
android:pathData="M785.8,927.4h-64.9c-9.7,0 -16.9,-9.7 -14.3,-19.5 0,-0.6 0,-0.6 0.6,-1.3 4.5,-16.9 -18.1,-26.6 -27.2,-11.7 -7.2,11 -13,20.2 -16.9,26a14.8,14.8 0,0 1,-12.3 6.5h-56.5c-9.7,0 -16.9,-9.7 -14.3,-19.5 0,-0.6 0,-0.6 0.6,-1.3 4.5,-16.9 -18.2,-26.6 -27.3,-11.7 -7.1,11 -13,20.2 -16.9,26a14.8,14.8 0,0 1,-12.3 6.5h-55.8c-9.7,0 -16.9,-9.7 -14.3,-19.5 0,-0.6 0,-0.6 0.6,-1.3 4.5,-16.9 -18.2,-26.6 -27.3,-11.7 -7.1,11 -13,20.2 -16.9,26a14.8,14.8 0,0 1,-12.3 6.5H360c-9.7,0 -16.9,-9.7 -14.3,-19.5 0,-0.6 0,-0.6 0.6,-1.3 4.5,-16.9 -18.2,-26.6 -27.3,-11.7 -7.1,11 -13,20.2 -16.9,26a14.8,14.8 0,0 1,-12.4 6.5H207.5a13.1,13.1 0,0 1,-7.1 -1.9c-43.5,-25.3 -62.9,-84.4 0.6,-138.9 42.2,-36.4 46.7,-94.8 45.4,-147.3 0,-8.4 6.5,-15.6 14.9,-15.6h526.4c7.1,0 13,4.5 14.3,11.7 14.9,64.9 20.1,116.2 32.4,205.8 8.4,65.5 -48.7,86.3 -48.7,86.3zM215.3,570.4c-20.1,0 -29.2,-14.9 -20.8,-32.5l70.8,-150.6c8.4,-18.2 31.8,-32.5 51.3,-32.5h113.6a14.7,14.7 0,0 0,14.9 -14.9V147.8c0.6,-28.6 23.4,-51.3 51.3,-51.3h42.2c27.9,0 51.3,22.7 51.3,51.3v190.8c0,8.4 6.5,14.9 14.9,14.9h109.7c20.1,0 42.8,14.9 51.3,32.4l70.7,150.6c8.5,18.2 -0.6,32.4 -20.7,32.4l-600.4,1.3z"
|
||||
android:fillColor="#ffffff"/>
|
||||
</vector>
|
11
app/src/main/res/drawable/ic_debug.xml
Normal file
11
app/src/main/res/drawable/ic_debug.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:width="250dp"
|
||||
android:height="250dp"
|
||||
android:viewportWidth="1024"
|
||||
android:viewportHeight="1024"
|
||||
tools:ignore="VectorRaster">
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M510.9,1024c123.9,0 231.8,-67.1 289.8,-168.2h194.3v-111.4L841.6,744.4c2.3,-18.2 4.5,-36.4 4.5,-55.7v-55.7h148.9v-111.4h-147.7L847.3,466c0,-19.3 -2.3,-37.5 -4.5,-55.7h154.6v-111.4L801.8,298.9c-25,-43.2 -60.2,-80.7 -102.3,-109.1l113.7,-109.1L734.8,0 590.4,138.7c-26.1,-5.7 -52.3,-9.1 -79.6,-9.1 -27.3,0 -53.4,3.4 -78.4,9.1L291.5,0l-78.4,78.4 110.2,109.1c-40.9,28.4 -76.1,65.9 -101.1,109.1L26.7,296.6L26.7,409.1h154.6c-2.3,18.2 -4.5,37.5 -4.5,55.7v55.7L26.7,520.5v111.4h148.9v55.7c0,19.3 2.3,37.5 4.5,55.7L26.7,743.3v111.4h194.3c58,101.1 165.9,169.3 289.8,169.3zM399.5,409.1h223.9v111.4L399.5,520.5L399.5,409.1zM399.5,631.9h223.9v111.4L399.5,743.3v-111.4z" />
|
||||
</vector>
|
93
app/src/main/res/layout/activity_app_errors_record.xml
Normal file
93
app/src/main/res/layout/activity_app_errors_record.xml
Normal file
@@ -0,0 +1,93 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/colorThemeBackground"
|
||||
android:orientation="vertical"
|
||||
tools:context=".ui.activity.AppErrorsRecordActivity"
|
||||
tools:ignore="ContentDescription,UseCompoundDrawables">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:elevation="0dp"
|
||||
android:gravity="center|start"
|
||||
android:paddingLeft="15dp"
|
||||
android:paddingTop="15dp"
|
||||
android:paddingRight="15dp"
|
||||
android:paddingBottom="15dp">
|
||||
|
||||
<androidx.constraintlayout.utils.widget.ImageFilterView
|
||||
android:id="@+id/title_back_icon"
|
||||
style="?android:attr/selectableItemBackgroundBorderless"
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
android:layout_marginStart="10dp"
|
||||
android:layout_marginEnd="20dp"
|
||||
android:src="@mipmap/ic_back"
|
||||
android:tint="@color/colorTextGray"
|
||||
android:tooltipText="@string/back" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="2.5dp"
|
||||
android:layout_weight="1"
|
||||
android:singleLine="true"
|
||||
android:text="@string/errors_record"
|
||||
android:textColor="@color/colorTextGray"
|
||||
android:textSize="19sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<androidx.constraintlayout.utils.widget.ImageFilterView
|
||||
android:id="@+id/clear_all_icon"
|
||||
android:layout_width="26dp"
|
||||
android:layout_height="26dp"
|
||||
android:layout_marginEnd="15dp"
|
||||
android:src="@drawable/ic_clear"
|
||||
android:tint="@color/colorTextGray"
|
||||
android:tooltipText="@string/clear_all" />
|
||||
|
||||
<androidx.constraintlayout.utils.widget.ImageFilterView
|
||||
android:id="@+id/export_all_icon"
|
||||
android:layout_width="25dp"
|
||||
android:layout_height="25dp"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:src="@drawable/ic_export"
|
||||
android:tint="@color/colorTextGray"
|
||||
android:tooltipText="@string/export_all" />
|
||||
</LinearLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginTop="5dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/list_no_data_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center"
|
||||
android:lineSpacingExtra="6dp"
|
||||
android:text="@string/no_errors_data"
|
||||
android:textColor="@color/colorTextDark"
|
||||
android:textSize="17sp" />
|
||||
|
||||
<ListView
|
||||
android:id="@+id/list_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:divider="@color/trans"
|
||||
android:dividerHeight="15dp"
|
||||
android:fadingEdgeLength="10dp"
|
||||
android:listSelector="@color/trans"
|
||||
android:paddingLeft="15dp"
|
||||
android:paddingRight="15dp"
|
||||
android:paddingBottom="15dp"
|
||||
android:requiresFadingEdge="vertical"
|
||||
android:scrollbars="none"
|
||||
android:visibility="gone" />
|
||||
</FrameLayout>
|
||||
</LinearLayout>
|
104
app/src/main/res/layout/adapter_app_errors_record.xml
Normal file
104
app/src/main/res/layout/adapter_app_errors_record.xml
Normal file
@@ -0,0 +1,104 @@
|
||||
<?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:background="@drawable/bg_permotion_ripple"
|
||||
android:gravity="center|start"
|
||||
android:orientation="horizontal"
|
||||
android:padding="10dp"
|
||||
tools:ignore="ContentDescription,UseCompoundDrawables,SmallSp">
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:layout_width="45dp"
|
||||
android:layout_height="45dp"
|
||||
app:cardBackgroundColor="@color/trans"
|
||||
app:cardCornerRadius="10dp"
|
||||
app:cardElevation="0dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/app_icon"
|
||||
android:layout_width="45dp"
|
||||
android:layout_height="45dp" />
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="10dp"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center|start"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:gravity="center|start"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/app_name_text"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="5dp"
|
||||
android:layout_weight="1"
|
||||
android:ellipsize="end"
|
||||
android:singleLine="true"
|
||||
android:textColor="@color/colorTextGray"
|
||||
android:textSize="15sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/errors_time_text"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="5dp"
|
||||
android:layout_weight="1"
|
||||
android:alpha="0.85"
|
||||
android:ellipsize="start"
|
||||
android:gravity="end"
|
||||
android:singleLine="true"
|
||||
android:textColor="@color/colorTextDark"
|
||||
android:textSize="11sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center|start"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/error_type_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="5dp"
|
||||
android:background="@drawable/bg_red_round"
|
||||
android:ellipsize="end"
|
||||
android:paddingLeft="3dp"
|
||||
android:paddingTop="0.5dp"
|
||||
android:paddingRight="3dp"
|
||||
android:paddingBottom="0.5dp"
|
||||
android:singleLine="true"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="9sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/error_msg_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:singleLine="true"
|
||||
android:textColor="@color/colorTextDark"
|
||||
android:textSize="11sp" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/error_type_icon"
|
||||
android:layout_width="25dp"
|
||||
android:layout_height="25dp"
|
||||
android:layout_marginEnd="10dp" />
|
||||
</LinearLayout>
|
@@ -28,4 +28,8 @@
|
||||
<string name="print_to_logcat_success">コンソールに印刷されていました</string>
|
||||
<string name="output_stack_success">エクスポートされたエラースタックされていました</string>
|
||||
<string name="output_stack_fail">エラースタックのエクスポートに失敗しました</string>
|
||||
<string name="export_all">すべてエクスポート</string>
|
||||
<string name="clear_all">すべてクリア</string>
|
||||
<string name="errors_record">エラー履歴記錄</string>
|
||||
<string name="no_errors_data">一時的にエラーなデータレコードはありません</string>
|
||||
</resources>
|
@@ -28,4 +28,8 @@
|
||||
<string name="print_to_logcat_success">已打印到控制台</string>
|
||||
<string name="output_stack_success">已导出异常堆栈</string>
|
||||
<string name="output_stack_fail">导出异常堆栈失败</string>
|
||||
<string name="export_all">导出全部</string>
|
||||
<string name="clear_all">清空全部</string>
|
||||
<string name="errors_record">异常历史记录</string>
|
||||
<string name="no_errors_data">暂时没有异常数据记录</string>
|
||||
</resources>
|
@@ -28,4 +28,8 @@
|
||||
<string name="print_to_logcat_success">已打印到控制台</string>
|
||||
<string name="output_stack_success">已導出異常堆棧</string>
|
||||
<string name="output_stack_fail">導出異常堆棧失敗</string>
|
||||
<string name="export_all">導出全部</string>
|
||||
<string name="clear_all">清空全部</string>
|
||||
<string name="errors_record">異常歷史記錄</string>
|
||||
<string name="no_errors_data">暫時沒有異常數據紀錄</string>
|
||||
</resources>
|
@@ -28,4 +28,8 @@
|
||||
<string name="print_to_logcat_success">已打印到控制台</string>
|
||||
<string name="output_stack_success">已導出異常堆棧</string>
|
||||
<string name="output_stack_fail">導出異常堆棧失敗</string>
|
||||
<string name="export_all">導出全部</string>
|
||||
<string name="clear_all">清空全部</string>
|
||||
<string name="errors_record">異常歷史記錄</string>
|
||||
<string name="no_errors_data">暫時沒有異常數據紀錄</string>
|
||||
</resources>
|
@@ -28,4 +28,8 @@
|
||||
<string name="print_to_logcat_success">已打印到控制台</string>
|
||||
<string name="output_stack_success">已導出異常堆棧</string>
|
||||
<string name="output_stack_fail">導出異常堆棧失敗</string>
|
||||
<string name="export_all">導出全部</string>
|
||||
<string name="clear_all">清空全部</string>
|
||||
<string name="errors_record">異常歷史記錄</string>
|
||||
<string name="no_errors_data">暫時沒有異常數據紀錄</string>
|
||||
</resources>
|
@@ -27,4 +27,8 @@
|
||||
<string name="print_to_logcat_success">Printed to logcat</string>
|
||||
<string name="output_stack_success">Export exception stack succeeded</string>
|
||||
<string name="output_stack_fail">Failed to export exception stack</string>
|
||||
<string name="export_all">Export all</string>
|
||||
<string name="clear_all">Clear all</string>
|
||||
<string name="errors_record">AppErrorsRecord</string>
|
||||
<string name="no_errors_data">No errors data for now</string>
|
||||
</resources>
|
Reference in New Issue
Block a user