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+ }
0 commit comments