mirror of
https://github.com/KitsunePie/AppErrorsTracking.git
synced 2025-09-01 08:45:16 +08:00
refactor: migrate and update to YukiHookAPI 1.3.0
This commit is contained in:
@@ -84,7 +84,8 @@ androidComponents {
|
||||
|
||||
dependencies {
|
||||
implementation(com.fankes.projectpromote.project.promote)
|
||||
implementation(com.highcapable.yukireflection.api)
|
||||
implementation(com.highcapable.kavaref.kavaref.core)
|
||||
implementation(com.highcapable.kavaref.kavaref.extension)
|
||||
implementation(androidx.core.core.ktx)
|
||||
implementation(androidx.appcompat.appcompat)
|
||||
implementation(com.google.android.material.material)
|
||||
|
@@ -19,21 +19,20 @@
|
||||
*
|
||||
* This file is created by fankes on 2022/5/10.
|
||||
*/
|
||||
@file:Suppress("DEPRECATION")
|
||||
|
||||
package com.fankes.apperrorsdemo.ui.activity.base
|
||||
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import androidx.core.view.WindowCompat
|
||||
import androidx.viewbinding.ViewBinding
|
||||
import com.fankes.apperrorsdemo.R
|
||||
import com.fankes.apperrorsdemo.utils.factory.isNotSystemInDarkMode
|
||||
import com.highcapable.yukireflection.factory.current
|
||||
import com.highcapable.yukireflection.factory.method
|
||||
import com.highcapable.yukireflection.type.android.LayoutInflaterClass
|
||||
import com.highcapable.kavaref.KavaRef.Companion.resolve
|
||||
import com.highcapable.kavaref.extension.genericSuperclassTypeArguments
|
||||
import com.highcapable.kavaref.extension.toClassOrNull
|
||||
|
||||
abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
|
||||
|
||||
@@ -42,10 +41,11 @@ abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = current().generic()?.argument()?.method {
|
||||
val bindingClass = javaClass.genericSuperclassTypeArguments().firstOrNull()?.toClassOrNull()
|
||||
binding = bindingClass?.resolve()?.optional()?.firstMethodOrNull {
|
||||
name = "inflate"
|
||||
param(LayoutInflaterClass)
|
||||
}?.get()?.invoke<VB>(layoutInflater) ?: error("binding failed")
|
||||
parameters(LayoutInflater::class)
|
||||
}?.invoke<VB>(layoutInflater) ?: error("binding failed")
|
||||
if (Build.VERSION.SDK_INT >= 35) binding.root.fitsSystemWindows = true
|
||||
setContentView(binding.root)
|
||||
/** 隐藏系统的标题栏 */
|
||||
@@ -55,6 +55,7 @@ abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
|
||||
isAppearanceLightStatusBars = isNotSystemInDarkMode
|
||||
isAppearanceLightNavigationBars = isNotSystemInDarkMode
|
||||
}
|
||||
@Suppress("DEPRECATION")
|
||||
ResourcesCompat.getColor(resources, R.color.colorThemeBackground, null).also {
|
||||
window?.statusBarColor = it
|
||||
window?.navigationBarColor = it
|
||||
|
@@ -22,7 +22,7 @@ repositories:
|
||||
plugins:
|
||||
com.android.application:
|
||||
alias: android-application
|
||||
version: 8.9.0
|
||||
version: 8.9.3
|
||||
org.jetbrains.kotlin.android:
|
||||
alias: kotlin-android
|
||||
version: 2.1.10
|
||||
@@ -46,12 +46,14 @@ libraries:
|
||||
rovo89-xposed-api
|
||||
com.highcapable.yukihookapi:
|
||||
api:
|
||||
version: 1.2.1
|
||||
version: 1.3.0
|
||||
ksp-xposed:
|
||||
version-ref: <this>::api
|
||||
com.highcapable.yukireflection:
|
||||
api:
|
||||
version: 1.0.3
|
||||
com.highcapable.kavaref:
|
||||
kavaref-core:
|
||||
version: 1.0.0
|
||||
kavaref-extension:
|
||||
version: 1.0.0
|
||||
com.microsoft.appcenter:
|
||||
appcenter-analytics:
|
||||
version: 5.0.6
|
||||
@@ -69,13 +71,13 @@ libraries:
|
||||
version: 2.12.1
|
||||
com.squareup.okhttp3:
|
||||
okhttp:
|
||||
version: 5.0.0-alpha.14
|
||||
version: 5.0.0-alpha.16
|
||||
androidx.core:
|
||||
core-ktx:
|
||||
version: 1.15.0
|
||||
version: 1.16.0
|
||||
androidx.appcompat:
|
||||
appcompat:
|
||||
version: 1.7.0
|
||||
version: 1.7.1
|
||||
com.google.android.material:
|
||||
material:
|
||||
version: 1.12.0
|
||||
|
@@ -82,6 +82,8 @@ dependencies {
|
||||
compileOnly(de.robv.android.xposed.api)
|
||||
implementation(com.highcapable.yukihookapi.api)
|
||||
ksp(com.highcapable.yukihookapi.ksp.xposed)
|
||||
implementation(com.highcapable.kavaref.kavaref.core)
|
||||
implementation(com.highcapable.kavaref.kavaref.extension)
|
||||
implementation(com.fankes.projectpromote.project.promote)
|
||||
implementation(com.microsoft.appcenter.appcenter.analytics)
|
||||
implementation(com.microsoft.appcenter.appcenter.crashes)
|
||||
|
@@ -30,6 +30,7 @@ import android.content.Intent
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.content.pm.PackageInfo
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Message
|
||||
import android.os.SystemClock
|
||||
import android.util.ArrayMap
|
||||
@@ -58,16 +59,10 @@ import com.fankes.apperrorstracking.utils.factory.toArrayList
|
||||
import com.fankes.apperrorstracking.utils.factory.toast
|
||||
import com.fankes.apperrorstracking.utils.tool.FrameworkTool
|
||||
import com.fankes.apperrorstracking.wrapper.BuildConfigWrapper
|
||||
import com.highcapable.yukihookapi.hook.bean.VariousClass
|
||||
import com.highcapable.kavaref.KavaRef.Companion.resolve
|
||||
import com.highcapable.kavaref.extension.VariousClass
|
||||
import com.highcapable.yukihookapi.hook.entity.YukiBaseHooker
|
||||
import com.highcapable.yukihookapi.hook.factory.constructor
|
||||
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.YLog
|
||||
import com.highcapable.yukihookapi.hook.type.android.BundleClass
|
||||
import com.highcapable.yukihookapi.hook.type.android.MessageClass
|
||||
import com.highcapable.yukihookapi.hook.type.java.BooleanType
|
||||
|
||||
object FrameworkHooker : YukiBaseHooker() {
|
||||
|
||||
@@ -110,40 +105,73 @@ object FrameworkHooker : YukiBaseHooker() {
|
||||
* 获取当前包列表实例
|
||||
* @return [Any] or null
|
||||
*/
|
||||
private val pkgList = if (ProcessRecordClass.hasMethod { name = "getPkgList"; emptyParam() })
|
||||
ProcessRecordClass.method { name = "getPkgList"; emptyParam() }.get(proc).call()
|
||||
else ProcessRecordClass.field { name { it.endsWith("pkgList", true) } }.get(proc).any()
|
||||
private val pkgList by lazy {
|
||||
ProcessRecordClass.resolve().optional(silent = true)
|
||||
.firstMethodOrNull {
|
||||
name = "getPkgList"
|
||||
emptyParameters()
|
||||
}?.of(proc)?.invoke()
|
||||
?: ProcessRecordClass.resolve().optional(silent = true)
|
||||
.firstFieldOrNull {
|
||||
name { it.endsWith("pkgList", true) }
|
||||
}?.of(proc)?.get()
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前包列表数组大小
|
||||
* @return [Int]
|
||||
*/
|
||||
private val pkgListSize = PackageListClass?.method { name = "size"; emptyParam() }?.get(pkgList)?.int()
|
||||
?: ProcessRecordClass.field { name = "pkgList" }.get(proc).cast<ArrayMap<*, *>>()?.size ?: -1
|
||||
private val pkgListSize by lazy {
|
||||
PackageListClass?.resolve()?.optional(silent = true)
|
||||
?.firstMethodOrNull {
|
||||
name = "size"
|
||||
emptyParameters()
|
||||
}?.of(pkgList)?.invoke()
|
||||
?: ProcessRecordClass.resolve().optional(silent = true)
|
||||
.firstFieldOrNull { name = "pkgList" }
|
||||
?.of(proc)?.get<ArrayMap<*, *>>()?.size ?: -1
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前 pid 信息
|
||||
* @return [Int]
|
||||
*/
|
||||
val pid = ProcessRecordClass.field { name { it == "mPid" || it == "pid" } }.get(proc).int()
|
||||
val pid by lazy {
|
||||
ProcessRecordClass.resolve().optional()
|
||||
.firstFieldOrNull {
|
||||
name { it == "mPid" || it == "pid" }
|
||||
}?.of(proc)?.get<Int>() ?: 0
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前用户 ID 信息
|
||||
* @return [Int]
|
||||
*/
|
||||
val userId = ProcessRecordClass.field { name = "userId" }.get(proc).int()
|
||||
val userId by lazy {
|
||||
ProcessRecordClass.resolve().optional()
|
||||
.firstFieldOrNull { name = "userId" }
|
||||
?.of(proc)?.get<Int>() ?: 0
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前 APP 信息
|
||||
* @return [ApplicationInfo] or null
|
||||
*/
|
||||
val appInfo = ProcessRecordClass.field { name = "info" }.get(proc).cast<ApplicationInfo>()
|
||||
val appInfo by lazy {
|
||||
ProcessRecordClass.resolve().optional()
|
||||
.firstFieldOrNull { name = "info" }
|
||||
?.of(proc)?.get<ApplicationInfo>()
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前进程名称
|
||||
* @return [String]
|
||||
*/
|
||||
val processName = ProcessRecordClass.field { name = "processName" }.get(proc).string()
|
||||
val processName by lazy {
|
||||
ProcessRecordClass.resolve().optional()
|
||||
.firstFieldOrNull { name = "processName" }
|
||||
?.of(proc)?.get<String>() ?: ""
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前 APP、进程 包名
|
||||
@@ -167,17 +195,25 @@ object FrameworkHooker : YukiBaseHooker() {
|
||||
* 获取当前进程是否为后台进程
|
||||
* @return [Boolean]
|
||||
*/
|
||||
val isBackgroundProcess = UserControllerClass
|
||||
.method { name { it == "getCurrentProfileIds" || it == "getCurrentProfileIdsLocked" } }
|
||||
.get(ActivityManagerServiceClass?.field { name = "mUserController" }
|
||||
?.get(AppErrorsClass.field { name = "mService" }.get(errors).any())?.any())
|
||||
.invoke<IntArray>()?.takeIf { it.isNotEmpty() }?.any { it != userId } ?: false
|
||||
val isBackgroundProcess by lazy {
|
||||
UserControllerClass.resolve().optional()
|
||||
.firstMethodOrNull { name { it == "getCurrentProfileIds" || it == "getCurrentProfileIdsLocked" } }
|
||||
?.of(ActivityManagerServiceClass?.resolve()?.optional()?.firstFieldOrNull { name = "mUserController" }
|
||||
?.of(AppErrorsClass.resolve().optional().firstFieldOrNull { name = "mService" }?.of(errors)?.get())?.getQuietly())
|
||||
?.invokeQuietly<IntArray>()?.takeIf { it.isNotEmpty() }?.any { it != userId } ?: false
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前进程是否短时内重复崩溃
|
||||
* @return [Boolean]
|
||||
*/
|
||||
val isRepeatingCrash = resultData?.let { AppErrorDialog_DataClass.field { name = "repeating" }.get(it).boolean() } ?: false
|
||||
val isRepeatingCrash by lazy {
|
||||
resultData?.let {
|
||||
AppErrorDialog_DataClass.resolve().optional()
|
||||
.firstFieldOrNull { name = "repeating" }
|
||||
?.of(it)?.get<Boolean>() == true
|
||||
} ?: false
|
||||
}
|
||||
}
|
||||
|
||||
/** 注册生命周期 */
|
||||
@@ -376,60 +412,56 @@ object FrameworkHooker : YukiBaseHooker() {
|
||||
/** 注册生命周期 */
|
||||
registerLifecycle()
|
||||
/** 干掉原生错误对话框 - 如果有 */
|
||||
ErrorDialogControllerClass?.apply {
|
||||
if (hasMethod { name = "hasCrashDialogs"; emptyParam() }) {
|
||||
method {
|
||||
name = "hasCrashDialogs"
|
||||
emptyParam()
|
||||
}.hook().replaceToTrue()
|
||||
|
||||
} else {
|
||||
constructor {
|
||||
paramCount = 1
|
||||
}.hook().after {
|
||||
field { name = "mCrashDialogs" }.get(instance).set(emptyList<Any>())
|
||||
ErrorDialogControllerClass?.resolve()?.optional(silent = true)?.apply {
|
||||
val hasCrashDialogs = firstMethodOrNull {
|
||||
name = "hasCrashDialogs"
|
||||
emptyParameters()
|
||||
}?.hook()?.replaceToTrue() != null
|
||||
if (!hasCrashDialogs)
|
||||
firstConstructorOrNull {
|
||||
parameterCount = 1
|
||||
}?.hook()?.after {
|
||||
firstFieldOrNull { name = "mCrashDialogs" }?.of(instance)?.set(emptyList<Any>())
|
||||
}
|
||||
}
|
||||
|
||||
method {
|
||||
firstMethodOrNull {
|
||||
name = "showCrashDialogs"
|
||||
paramCount = 1
|
||||
}.hook().intercept()
|
||||
parameterCount = 1
|
||||
}?.hook()?.intercept()
|
||||
}
|
||||
/** 干掉原生错误对话框 - API 30 以下 */
|
||||
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) {
|
||||
ActivityTaskManagerService_LocalServiceClass?.method {
|
||||
ActivityTaskManagerService_LocalServiceClass?.resolve()?.optional()?.firstMethodOrNull {
|
||||
name = "canShowErrorDialogs"
|
||||
emptyParam()
|
||||
}?.ignored()?.hook()?.replaceToFalse()
|
||||
ActivityManagerServiceClass?.method {
|
||||
emptyParameters()
|
||||
}?.hook()?.replaceToFalse()
|
||||
ActivityManagerServiceClass?.resolve()?.optional()?.firstMethodOrNull {
|
||||
name = "canShowErrorDialogs"
|
||||
emptyParam()
|
||||
}?.ignored()?.hook()?.replaceToFalse()
|
||||
emptyParameters()
|
||||
}?.hook()?.replaceToFalse()
|
||||
}
|
||||
/** 干掉原生错误对话框 - 如果上述方法全部失效则直接结束对话框 */
|
||||
AppErrorDialogClass.apply {
|
||||
method {
|
||||
AppErrorDialogClass.resolve().optional(silent = true).apply {
|
||||
firstMethodOrNull {
|
||||
name = "onCreate"
|
||||
param(BundleClass)
|
||||
}.ignored().hook().after { instance<Dialog>().cancel() }
|
||||
method {
|
||||
parameters(Bundle::class)
|
||||
}?.hook()?.after { instance<Dialog>().cancel() }
|
||||
firstMethodOrNull {
|
||||
name = "onStart"
|
||||
emptyParam()
|
||||
}.ignored().hook().after { instance<Dialog>().cancel() }
|
||||
emptyParameters()
|
||||
}?.hook()?.after { instance<Dialog>().cancel() }
|
||||
}
|
||||
/** 注入自定义错误对话框 */
|
||||
AppErrorsClass.apply {
|
||||
AppErrorsClass.resolve().optional().apply {
|
||||
when {
|
||||
Build.VERSION.SDK_INT > Build.VERSION_CODES.R -> {
|
||||
method {
|
||||
firstMethodOrNull {
|
||||
name = "handleAppCrashLSPB"
|
||||
paramCount = 6
|
||||
}.hook().after {
|
||||
parameterCount = 6
|
||||
}?.hook()?.after {
|
||||
/** 如果为用户终止则不展示异常 */
|
||||
if (args(index = 1).string() == "user-terminated") return@after
|
||||
/** 当前实例 */
|
||||
val context = appContext ?: field { name = "mContext" }.get(instance).cast<Context>() ?: return@after
|
||||
val context = appContext ?: firstFieldOrNull { name = "mContext" }?.of(instance)?.get<Context>() ?: return@after
|
||||
|
||||
/** 当前进程信息 */
|
||||
val proc = args().first().any() ?: return@after YLog.warn("Received but got null ProcessRecord (Show UI failed)")
|
||||
@@ -441,29 +473,29 @@ object FrameworkHooker : YukiBaseHooker() {
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
method {
|
||||
firstMethodOrNull {
|
||||
name = "handleShowAppErrorUi"
|
||||
param(MessageClass)
|
||||
}.hook().after {
|
||||
parameters(Message::class)
|
||||
}?.hook()?.after {
|
||||
/** 当前实例 */
|
||||
val context = appContext ?: field { name = "mContext" }.get(instance).cast<Context>() ?: return@after
|
||||
val context = appContext ?: firstFieldOrNull { name = "mContext" }?.of(instance)?.get<Context>() ?: return@after
|
||||
|
||||
/** 当前错误数据 */
|
||||
val resultData = args().first().cast<Message>()?.obj
|
||||
|
||||
/** 当前进程信息 */
|
||||
val proc = AppErrorDialog_DataClass.field { name = "proc" }.get(resultData).any()
|
||||
val proc = AppErrorDialog_DataClass.resolve().optional().firstFieldOrNull { name = "proc" }?.of(resultData)?.get()
|
||||
/** 创建 APP 进程异常数据类 */
|
||||
AppErrorsProcessData(instance, proc, resultData).handleShowAppErrorUi(context)
|
||||
}
|
||||
}
|
||||
}
|
||||
method {
|
||||
firstMethodOrNull {
|
||||
name = "handleAppCrashInActivityController"
|
||||
returnType = BooleanType
|
||||
}.hook().after {
|
||||
returnType = Boolean::class
|
||||
}?.hook()?.after {
|
||||
/** 当前实例 */
|
||||
val context = appContext ?: field { name = "mContext" }.get(instance).cast<Context>() ?: return@after
|
||||
val context = appContext ?: firstFieldOrNull { name = "mContext" }?.of(instance)?.get<Context>() ?: return@after
|
||||
|
||||
/** 当前进程信息 */
|
||||
val proc = args().first().any() ?: return@after YLog.warn("Received but got null ProcessRecord")
|
||||
|
@@ -19,14 +19,13 @@
|
||||
*
|
||||
* This file is created by fankes on 2022/5/7.
|
||||
*/
|
||||
@file:Suppress("DEPRECATION")
|
||||
|
||||
package com.fankes.apperrorstracking.ui.activity.base
|
||||
|
||||
import android.app.ActivityManager
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import androidx.core.view.WindowCompat
|
||||
@@ -34,9 +33,9 @@ import androidx.viewbinding.ViewBinding
|
||||
import com.fankes.apperrorstracking.R
|
||||
import com.fankes.apperrorstracking.utils.factory.isNotSystemInDarkMode
|
||||
import com.fankes.apperrorstracking.utils.factory.toast
|
||||
import com.highcapable.yukihookapi.hook.factory.current
|
||||
import com.highcapable.yukihookapi.hook.factory.method
|
||||
import com.highcapable.yukihookapi.hook.type.android.LayoutInflaterClass
|
||||
import com.highcapable.kavaref.KavaRef.Companion.resolve
|
||||
import com.highcapable.kavaref.extension.genericSuperclassTypeArguments
|
||||
import com.highcapable.kavaref.extension.toClassOrNull
|
||||
|
||||
abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
|
||||
|
||||
@@ -45,10 +44,11 @@ abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = current().generic()?.argument()?.method {
|
||||
val bindingClass = javaClass.genericSuperclassTypeArguments().firstOrNull()?.toClassOrNull()
|
||||
binding = bindingClass?.resolve()?.optional()?.firstMethodOrNull {
|
||||
name = "inflate"
|
||||
param(LayoutInflaterClass)
|
||||
}?.get()?.invoke<VB>(layoutInflater) ?: error("binding failed")
|
||||
parameters(LayoutInflater::class)
|
||||
}?.invoke<VB>(layoutInflater) ?: error("binding failed")
|
||||
if (Build.VERSION.SDK_INT >= 35) binding.root.fitsSystemWindows = true
|
||||
setContentView(binding.root)
|
||||
/** 隐藏系统的标题栏 */
|
||||
@@ -58,6 +58,7 @@ abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
|
||||
isAppearanceLightStatusBars = isNotSystemInDarkMode
|
||||
isAppearanceLightNavigationBars = isNotSystemInDarkMode
|
||||
}
|
||||
@Suppress("DEPRECATION")
|
||||
ResourcesCompat.getColor(resources, R.color.colorThemeBackground, null).also {
|
||||
window?.statusBarColor = it
|
||||
window?.navigationBarColor = it
|
||||
|
@@ -19,6 +19,8 @@
|
||||
*
|
||||
* This file is created by fankes on 2022/6/3.
|
||||
*/
|
||||
@file:Suppress("DEPRECATION")
|
||||
|
||||
package com.fankes.apperrorstracking.utils.factory
|
||||
|
||||
import android.content.Context
|
||||
|
@@ -19,7 +19,7 @@
|
||||
*
|
||||
* This file is created by fankes on 2022/5/12.
|
||||
*/
|
||||
@file:Suppress("unused")
|
||||
@file:Suppress("unused", "DEPRECATION")
|
||||
|
||||
package com.fankes.apperrorstracking.utils.factory
|
||||
|
||||
|
@@ -19,7 +19,7 @@
|
||||
*
|
||||
* This file is created by fankes on 2022/5/7.
|
||||
*/
|
||||
@file:Suppress("unused", "NotificationPermission")
|
||||
@file:Suppress("unused", "NotificationPermission", "DEPRECATION")
|
||||
|
||||
package com.fankes.apperrorstracking.utils.factory
|
||||
|
||||
|
@@ -776,6 +776,35 @@
|
||||
android:textColor="@color/colorTextGray"
|
||||
android:textSize="11sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="15dp"
|
||||
android:layout_marginRight="15dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:background="@drawable/bg_permotion_round"
|
||||
android:gravity="center|start"
|
||||
android:orientation="horizontal"
|
||||
android:padding="10dp">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="35dp"
|
||||
android:layout_height="35dp"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:src="@mipmap/ic_kavaref" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:autoLink="web"
|
||||
android:ellipsize="end"
|
||||
android:lineSpacingExtra="6dp"
|
||||
android:maxLines="2"
|
||||
android:text="@string/about_module_extension"
|
||||
android:textColor="@color/colorTextGray"
|
||||
android:textSize="11sp" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
</LinearLayout>
|
||||
|
BIN
module-app/src/main/res/mipmap-xxhdpi/ic_kavaref.png
Normal file
BIN
module-app/src/main/res/mipmap-xxhdpi/ic_kavaref.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 17 KiB |
@@ -157,4 +157,5 @@
|
||||
<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>
|
||||
<string name="about_module_extension">このモジュールは KavaRef を搭載しています。\n詳細はこちら https://github.com/HighCapable/KavaRef</string>
|
||||
</resources>
|
||||
|
@@ -159,4 +159,5 @@
|
||||
<string name="stack_trace_share_other">其它必要信息</string>
|
||||
<string name="share_with_file">以文件方式分享异常堆栈</string>
|
||||
<string name="share_with_file_tip">使用文件的方式代替文本分享异常堆栈</string>
|
||||
<string name="about_module_extension">此模块使用 KavaRef 强力驱动。\n了解更多 https://github.com/HighCapable/KavaRef</string>
|
||||
</resources>
|
@@ -155,4 +155,5 @@
|
||||
<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>
|
||||
<string name="about_module_extension">此模組使用 KavaRef 強力驅動。\n了解更多 https://github.com/HighCapable/KavaRef</string>
|
||||
</resources>
|
@@ -155,4 +155,5 @@
|
||||
<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>
|
||||
<string name="about_module_extension">此模組使用 KavaRef 強力驅動。\n了解更多 https://github.com/HighCapable/KavaRef</string>
|
||||
</resources>
|
@@ -155,4 +155,5 @@
|
||||
<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>
|
||||
<string name="about_module_extension">此模組使用 KavaRef 強力驅動。\n瞭解更多 https://github.com/HighCapable/KavaRef</string>
|
||||
</resources>
|
@@ -162,4 +162,5 @@
|
||||
<string name="stack_trace_share_other">Other Requirement</string>
|
||||
<string name="share_with_file">Share errors stacktrace with file</string>
|
||||
<string name="share_with_file_tip">Share errors stacktrace using files instead of text</string>
|
||||
<string name="about_module_extension">This Xposed Module is powered by KavaRef.\nLearn more https://github.com/HighCapable/KavaRef</string>
|
||||
</resources>
|
||||
|
Reference in New Issue
Block a user