Skip to content

Commit 30c9015

Browse files
committed
Add send crash
1 parent c997650 commit 30c9015

File tree

11 files changed

+411
-9
lines changed

11 files changed

+411
-9
lines changed

app/src/main/AndroidManifest.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
android:label="@string/app_name"
1212
android:roundIcon="@mipmap/ic_launcher_round"
1313
android:supportsRtl="true"
14+
android:name=".SampleApp"
1415
android:theme="@style/AppTheme">
1516
<activity android:name="com.omega_r.base.simple.MainActivity">
1617
<intent-filter>
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.omega_r.base.simple
2+
3+
import android.app.Application
4+
5+
class SampleApp : Application() {
6+
7+
override fun onCreate() {
8+
super.onCreate()
9+
}
10+
11+
}

core/src/main/java/com/omega_r/base/components/OmegaActivity.kt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ import com.omega_r.base.mvp.model.Action
2222
import com.omega_r.base.mvp.views.findAnnotation
2323
import com.omega_r.base.dialogs.DialogManager
2424
import com.omega_r.base.dialogs.WaitingController
25+
import com.omega_r.base.crash.CrashSender
26+
import com.omega_r.base.crash.EmailSenderCrashWay
2527
import com.omega_r.base.mvp.presenters.OmegaPresenter
2628
import com.omega_r.bind.delegates.IdHolder
2729
import com.omega_r.bind.delegates.managers.BindersManager
@@ -63,6 +65,7 @@ abstract class OmegaActivity : MvpAppCompatActivity, OmegaComponent {
6365
override fun onCreate(savedInstanceState: Bundle?) {
6466
OmegaPresenter.isDebuggable.ifNull {
6567
OmegaPresenter.isDebuggable = 0 != applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE
68+
setupCrashSender()
6669
}
6770

6871
this::class.findAnnotation<OmegaWindowFlags>()?.let {
@@ -95,6 +98,16 @@ abstract class OmegaActivity : MvpAppCompatActivity, OmegaComponent {
9598
}
9699
}
97100

101+
protected open fun setupCrashSender() {
102+
if (OmegaPresenter.isDebuggable == true) {
103+
CrashSender.setup(application, *getSenderCrashWays())
104+
}
105+
}
106+
107+
protected open fun getSenderCrashWays(): Array<CrashSender.SenderCrashWay> {
108+
return arrayOf(EmailSenderCrashWay())
109+
}
110+
98111
override fun getViewForSnackbar(): View {
99112
return findViewById(android.R.id.content)!!
100113
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package com.omega_r.base.crash
2+
3+
import android.graphics.Bitmap
4+
5+
6+
class CrashReport(
7+
val info: Map<String, Map<String, String>>,
8+
val stacktrace: String,
9+
val screenshot: Bitmap?
10+
)
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
package com.omega_r.base.crash
2+
3+
import android.annotation.SuppressLint
4+
import android.app.Activity
5+
import android.app.Application
6+
import android.content.Context
7+
import android.content.pm.PackageManager
8+
import android.graphics.Bitmap
9+
import android.graphics.Canvas
10+
import android.os.Build
11+
import android.os.Bundle
12+
import android.view.View
13+
import androidx.core.content.pm.PackageInfoCompat
14+
import java.io.PrintWriter
15+
import java.io.StringWriter
16+
import java.lang.Exception
17+
import java.lang.ref.WeakReference
18+
import java.util.concurrent.CopyOnWriteArraySet
19+
20+
21+
class CrashSender(context: Context, private val senderWays: Array<out SenderCrashWay>) : Thread.UncaughtExceptionHandler,
22+
Application.ActivityLifecycleCallbacks {
23+
24+
companion object {
25+
26+
@SuppressLint("StaticFieldLeak")
27+
private var singleCrashSender: CrashSender? = null
28+
29+
private val reporters = CopyOnWriteArraySet<CrashReporter>()
30+
31+
fun setup(application: Application, vararg otherSenderWays: SenderCrashWay) {
32+
singleCrashSender?.let {
33+
application.unregisterActivityLifecycleCallbacks(it)
34+
OmegaUncaughtExceptionHandler.remove(it)
35+
}
36+
val handler = CrashSender(application, otherSenderWays)
37+
OmegaUncaughtExceptionHandler.add(handler)
38+
application.registerActivityLifecycleCallbacks(handler)
39+
singleCrashSender = handler
40+
}
41+
42+
fun addReporter(reporter: CrashReporter) {
43+
reporters.add(reporter)
44+
}
45+
46+
}
47+
48+
private val context = context.applicationContext
49+
private var activityWeakRef: WeakReference<Activity>? = null
50+
51+
override fun uncaughtException(thread: Thread, error: Throwable) {
52+
try {
53+
val currentActivity = activityWeakRef?.get()
54+
val screenshotBitmap = currentActivity?.createScreenshotBitmap()
55+
val crashReport = createCrashReport(currentActivity, error, screenshotBitmap)
56+
57+
senderWays.forEach {
58+
try {
59+
it.send(context, currentActivity, error, crashReport)
60+
} catch (e: Exception) {
61+
e.printStackTrace()
62+
}
63+
}
64+
65+
screenshotBitmap?.recycle()
66+
} catch (e: Exception) {
67+
e.printStackTrace()
68+
}
69+
}
70+
71+
private fun createCrashReport(currentActivity: Activity?, error: Throwable, screenshotBitmap: Bitmap?): CrashReport {
72+
val info = mutableMapOf<String, Map<String, String>>().apply {
73+
putGroupMap("OS", getOsInfo())
74+
putGroupMap("Application", getApplicationInfo())
75+
putGroupMap("Activity", getActivityInfo(currentActivity))
76+
}
77+
78+
reporters.forEach {
79+
it.report(info)
80+
}
81+
82+
return CrashReport(info, getStackTrace(error) ?: "", screenshotBitmap)
83+
}
84+
85+
private fun MutableMap<String, Map<String, String>>.putGroupMap(groupName: String, map: Map<String, String>?) {
86+
if (map?.isNotEmpty() == true) {
87+
put(groupName, map)
88+
}
89+
}
90+
91+
private fun getActivityInfo(currentActivity: Activity?): Map<String, String>? {
92+
if (currentActivity != null) {
93+
val title = currentActivity.title
94+
95+
return mutableMapOf<String, String>().also { map ->
96+
currentActivity::class.simpleName?.let { map["ClassName"] = it }
97+
title?.let { map["title"] = title.toString() }
98+
}
99+
}
100+
return null
101+
}
102+
103+
private fun getOsInfo(): Map<String, String>? {
104+
return mapOf("Version" to "Android ${Build.VERSION.RELEASE} (API ${Build.VERSION.SDK_INT})")
105+
}
106+
107+
108+
private fun getApplicationInfo(): Map<String, String>? {
109+
try {
110+
with(context.packageManager.getPackageInfo(context.packageName, 0)) {
111+
return mapOf(
112+
"Package" to packageName,
113+
"Version Name" to versionName,
114+
"Version Code" to PackageInfoCompat.getLongVersionCode(this).toString()
115+
)
116+
}
117+
} catch (e: PackageManager.NameNotFoundException) {
118+
return null // Ignored, this shouldn't happen
119+
}
120+
}
121+
122+
private fun getStackTrace(error: Throwable): String? {
123+
val stack: Array<StackTraceElement> = error.stackTrace
124+
if (stack.isNotEmpty()) {
125+
val stringWriter = StringWriter()
126+
val printWriter = PrintWriter(stringWriter)
127+
error.printStackTrace(printWriter)
128+
return stringWriter.toString()
129+
}
130+
return null
131+
}
132+
133+
private fun Activity.createScreenshotBitmap(): Bitmap? {
134+
val window = window ?: return null
135+
val view = window.decorView
136+
return getBitmapFromView(view)
137+
}
138+
139+
private fun getBitmapFromView(view: View): Bitmap? {
140+
val bitmap = Bitmap.createBitmap(view.width, view.height, Bitmap.Config.ARGB_8888)
141+
val canvas = Canvas(bitmap)
142+
view.draw(canvas)
143+
return bitmap
144+
}
145+
146+
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
147+
// nothing
148+
}
149+
150+
override fun onActivityStarted(activity: Activity) {
151+
// nothing
152+
}
153+
154+
override fun onActivityResumed(activity: Activity) {
155+
activityWeakRef = WeakReference(activity)
156+
}
157+
158+
override fun onActivityPaused(activity: Activity) {
159+
activityWeakRef = null
160+
}
161+
162+
override fun onActivityStopped(activity: Activity) {
163+
// nothing
164+
}
165+
166+
override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {
167+
// nothing
168+
}
169+
170+
override fun onActivityDestroyed(activity: Activity) {
171+
// nothing
172+
}
173+
174+
interface SenderCrashWay {
175+
176+
fun send(context: Context, currentActivity: Activity?, error: Throwable, crashReport: CrashReport)
177+
178+
}
179+
180+
interface CrashReporter {
181+
182+
fun report(map: MutableMap<String, Map<String, String>>)
183+
184+
}
185+
186+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package com.omega_r.base.crash
2+
3+
import android.app.Activity
4+
import android.content.Context
5+
import com.omega_r.libs.omegaintentbuilder.OmegaIntentBuilder
6+
import java.io.PrintWriter
7+
import java.io.StringWriter
8+
9+
class EmailSenderCrashWay(private val emailTo: String? = null) : CrashSender.SenderCrashWay {
10+
11+
override fun send(context: Context, currentActivity: Activity?, error: Throwable, crashReport: CrashReport) {
12+
OmegaIntentBuilder.share()
13+
.subject("~ CRASH REPORT ~")
14+
.text(createBodyText(crashReport))
15+
.apply {
16+
crashReport.screenshot?.let { bitmap(crashReport.screenshot) }
17+
emailTo?.let { emailTo(emailTo) }
18+
}
19+
.startActivity(context)
20+
}
21+
22+
private fun createBodyText(crashReport: CrashReport): String {
23+
val stringWriter = StringWriter()
24+
with(PrintWriter(stringWriter)) {
25+
printGroupMap(crashReport.info)
26+
printGroupSingleValue("Stack Trace", crashReport.stacktrace)
27+
}
28+
return stringWriter.toString()
29+
}
30+
31+
32+
private fun PrintWriter.printGroupMap(map: Map<String, Map<String, String>>) {
33+
map.forEach {
34+
println("[${it.key}]: ")
35+
printMap(it.value)
36+
println()
37+
}
38+
}
39+
40+
private fun PrintWriter.printGroupSingleValue(groupName: String, value: String) {
41+
if (value.isNotEmpty()) {
42+
println("[$groupName]: ")
43+
println(value)
44+
println()
45+
}
46+
}
47+
48+
private fun PrintWriter.printMap(map: Map<String, String>) {
49+
map.forEach {
50+
println(" - ${it.key}: ${it.value}")
51+
}
52+
}
53+
54+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package com.omega_r.base.crash
2+
3+
import java.lang.Thread.UncaughtExceptionHandler
4+
import java.util.concurrent.CopyOnWriteArraySet
5+
6+
object OmegaUncaughtExceptionHandler : UncaughtExceptionHandler {
7+
8+
private var defaultHandler: UncaughtExceptionHandler? = null
9+
10+
private val uncaughtExceptionHandlers = CopyOnWriteArraySet<UncaughtExceptionHandler>()
11+
12+
init {
13+
defaultHandler = Thread.getDefaultUncaughtExceptionHandler()
14+
Thread.setDefaultUncaughtExceptionHandler(this)
15+
}
16+
17+
18+
override fun uncaughtException(thread: Thread, error: Throwable) {
19+
try {
20+
uncaughtExceptionHandlers.forEach {
21+
it.uncaughtException(thread, error)
22+
}
23+
} finally {
24+
defaultHandler?.uncaughtException(thread, error)
25+
}
26+
}
27+
28+
fun add(handler: UncaughtExceptionHandler) {
29+
uncaughtExceptionHandlers.add(handler)
30+
}
31+
32+
fun remove(handler: UncaughtExceptionHandler) {
33+
uncaughtExceptionHandlers.remove(handler)
34+
}
35+
36+
}

0 commit comments

Comments
 (0)