Android专栏-JavaCrash默认处理

Java Crash 默认处理

CrashHandler 处理 Java 异常流程:

  • 区分Debug模式和Release模式、主进程和子进程、主线程和子线程来处理
  • 捕获Activity的生命周期内异常,并主动杀死Activity
  • View绘制流程异常捕获
  • 自定义上报

CrashHandler 源码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
import android.os.Build
import android.os.Environment
import android.os.Handler
import android.os.Looper
import android.text.TextUtils
import android.util.Log
import com.alibaba.ha.adapter.AliHaAdapter
import com.rrtv.action.manager.ThreadPoolManager
import com.rrtv.utils.utils.UiUtils
import java.io.ByteArrayInputStream
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import java.lang.reflect.Field
import java.text.DateFormat
import java.text.SimpleDateFormat
import java.util.*


/**
* Java Crash 默认处理
*/
class CrashHandler : Thread.UncaughtExceptionHandler {

private var isDebug = false
private var isMainProcess = false

private var sActivityKiller: IActivityKiller? = null

companion object {
private const val TAG = "CrashHandler"
private var instance = CrashHandler()

@Volatile
@JvmStatic
private var hasInit = false


/**
* 初始化
*
* 在 @link Application#onCreate() 方法中 install()
*
* @param isDebug 是否debug模式 BuildConfig.DEBUG
* @param isMainProcess 是否是主进程 android.os.Process.myPid()
*/
@JvmStatic
fun install(isDebug: Boolean, isMainProcess: Boolean) {
if (!hasInit) {
synchronized(CrashHandler::class.java) {
if (!hasInit) {
Log.d(TAG, "install: isDebug = $isDebug, mainPid = $isMainProcess")
hasInit = true
instance.setup(isDebug, isMainProcess)
Log.d(TAG, "install success.")
}
}
}
}
}


private fun setup(isDebug: Boolean, isMainProcess: Boolean) {
Log.d(TAG, "setup:")
this.isDebug = isDebug
this.isMainProcess = isMainProcess

Thread.setDefaultUncaughtExceptionHandler(this)

if (!isDebug && isMainProcess) {
// release 模式防止主线程奔溃
Handler(Looper.getMainLooper()).post {
while (true) {
try {
Looper.loop()
} catch (e: Exception) {
handleLooperException(e)
}
}
}

try {
// 生命周期的 ActivityKiller
initActivityKiller()
} catch (e: Exception) {
Log.e(TAG, "拦截生命周期失败", e)
}
}
}

/**
* 替换ActivityThread.mH.mCallback,实现拦截Activity生命周期,直接忽略生命周期的异常的话会导致黑屏,目前
* 会调用ActivityManager的finishActivity结束掉生命周期抛出异常的Activity
*/
private fun initActivityKiller() {
Log.d(TAG, "initActivityKiller: Build.VERSION.SDK_INT=${Build.VERSION.SDK_INT}")
//各版本android的ActivityManager获取方式,finishActivity的参数,token(binder对象)的获取不一样
if (Build.VERSION.SDK_INT >= 28) {
sActivityKiller = ActivityKillerV28()
} else if (Build.VERSION.SDK_INT >= 26) {
sActivityKiller = ActivityKillerV26()
} else if (Build.VERSION.SDK_INT == 25 || Build.VERSION.SDK_INT == 24) {
sActivityKiller = ActivityKillerV24_V25()
} else if (Build.VERSION.SDK_INT in 21..23) {
sActivityKiller = ActivityKillerV21_V23()
} else if (Build.VERSION.SDK_INT in 15..20) {
sActivityKiller = ActivityKillerV15_V20()
} else if (Build.VERSION.SDK_INT < 15) {
sActivityKiller = ActivityKillerV15_V20()
}
try {
hookMH()
Log.e(TAG, "hookMH Success.")
} catch (e: Throwable) {
Log.e(TAG, "hookMH 失败: ", e)
}
}

@Throws(Exception::class)
private fun hookMH() {
Log.d(TAG, "hookMH: ")
val LAUNCH_ACTIVITY = 100
val PAUSE_ACTIVITY = 101
val PAUSE_ACTIVITY_FINISHING = 102
val STOP_ACTIVITY_HIDE = 104
val RESUME_ACTIVITY = 107
val DESTROY_ACTIVITY = 109
val NEW_INTENT = 112
val RELAUNCH_ACTIVITY = 126
val activityThreadClass =
Class.forName("android.app.ActivityThread")
val activityThread =
activityThreadClass.getDeclaredMethod("currentActivityThread").invoke(null)
val mhField: Field = activityThreadClass.getDeclaredField("mH")
mhField.setAccessible(true)
val mhHandler = mhField.get(activityThread) as Handler
val callbackField: Field = Handler::class.java.getDeclaredField("mCallback")
callbackField.setAccessible(true)
callbackField.set(mhHandler, Handler.Callback { msg ->
if (Build.VERSION.SDK_INT >= 28) { //android P 生命周期全部走这
val EXECUTE_TRANSACTION = 159
if (msg.what === EXECUTE_TRANSACTION) {
try {
mhHandler.handleMessage(msg)
} catch (throwable: Throwable) {
sActivityKiller?.finishLaunchActivity(msg)
handleLifecycleException(throwable)
}
return@Callback true
}
return@Callback false
}
when (msg.what) {
LAUNCH_ACTIVITY -> {
try {
mhHandler.handleMessage(msg)
} catch (throwable: Throwable) {
sActivityKiller?.finishLaunchActivity(msg)
handleLifecycleException(throwable)
}
return@Callback true
}
RESUME_ACTIVITY -> {
try {
mhHandler.handleMessage(msg)
} catch (throwable: Throwable) {
sActivityKiller?.finishResumeActivity(msg)
handleLifecycleException(throwable)
}
return@Callback true
}
PAUSE_ACTIVITY_FINISHING -> {
try {
mhHandler.handleMessage(msg)
} catch (throwable: Throwable) {
sActivityKiller?.finishPauseActivity(msg)
handleLifecycleException(throwable)
}
return@Callback true
}
PAUSE_ACTIVITY -> {
try {
mhHandler.handleMessage(msg)
} catch (throwable: Throwable) {
sActivityKiller?.finishPauseActivity(msg)
handleLifecycleException(throwable)
}
return@Callback true
}
STOP_ACTIVITY_HIDE -> {
try {
mhHandler.handleMessage(msg)
} catch (throwable: Throwable) {
sActivityKiller?.finishStopActivity(msg)
handleLifecycleException(throwable)
}
return@Callback true
}
DESTROY_ACTIVITY -> {
try {
mhHandler.handleMessage(msg)
} catch (throwable: Throwable) {
handleLifecycleException(throwable)
}
return@Callback true
}
}
false
})
}

/**
* 生命周期异常处理
*/
private fun handleLifecycleException(e: Throwable) {
Log.e(TAG, "lifecycleException: ", e)
reportCustomThrowable(Thread.currentThread(), RuntimeException("Activity生命周期出现异常", e))
// 给个Toast提示
try {
UiUtils.showToastSafe("小猿刚刚捕获了一只BUG")
} catch (e: Exception) {
e.printStackTrace()
}
}

/**
* 主线程Looper异常
*/
private fun handleLooperException(e: Exception) {
// 主线程内发生异常 主动 catch
Log.e(TAG, "handleLooperException: ", e)
reportCustomThrowable(Thread.currentThread(), e)
handleMainThreadException(e)
}


/**
* 本版本不处理
*
* view measure layout draw时抛出异常会导致Choreographer挂掉
* 建议直接杀死app。以后的版本会只关闭黑屏的Activity
*
* @param e
*/
private fun isChoreographerException(e: Throwable?): Boolean {
val elements = e?.stackTrace ?: return false
for (i in elements.size - 1 downTo -1 + 1) {
if (elements.size - i > 20) {
return false
}
val element = elements[i]
if ("android.view.Choreographer" == element.className && "Choreographer.java" == element.fileName && "doFrame" == element.methodName) {
//View 绘制流程出的问题
return true
}
}
return false
}


override fun uncaughtException(t: Thread, e: Throwable) {
Log.e(TAG, "uncaughtException: ")
if (isDebug) {
handleDebugException(t, e)
} else {
handleReleaseException(t, e)
}
}

/**
* 处理 Debug 模式的异常
*/
private fun handleDebugException(t: Thread, e: Throwable) {
Log.e(TAG, "handleDebugException: $t", e)
// 记录本地日志
saveThrowableMessage(Log.getStackTraceString(e))
}

/**
* 处理 !Debug 模式的异常
*/
private fun handleReleaseException(t: Thread, e: Throwable) {
Log.e(TAG, "handleReleaseException: $t", e)
// 自定义 Bug 上报
reportCustomThrowable(t, e)
// 根据情况来处理异常
if (isMainProcess) {
// 为主进程
if (Looper.myLooper() == Looper.getMainLooper()) {
// 主线程异常处理
handleMainThreadException(e)
} else {
// 非主线程
Log.e(TAG, "子线程异常: finish.")
}
} else {
// 如果是子进程发生异常 直接殺掉子進程
Log.e(TAG, "子进程异常: killProcess.")
android.os.Process.killProcess(android.os.Process.myPid())
}

}

/**
* 主线程异常处理
*
* Looper.loop & MainThreadUncaughtException
*/
private fun handleMainThreadException(e: Throwable) {
Log.e(TAG, "handleMainThreadException: ")
try {
// 主线程
when (e) {
is IllegalArgumentException,
is IllegalStateException,
is IndexOutOfBoundsException,
is UnsupportedOperationException,
is ArithmeticException,
is NumberFormatException,
is NullPointerException,
is ClassCastException,
is AssertionError,
is NoSuchElementException -> {
Log.e(TAG, "主线程异常: handle finish.")
if (isChoreographerException(e)) {
UiUtils.showToastSafe("界面刷新出了个小问题")
}
}
else -> {
Log.e(TAG, "主线程未知异常:System exit.")
// 这里也可以只给个提示,反正不会程序不会奔溃
android.os.Process.killProcess(android.os.Process.myPid())
System.exit(0)
}
}
} catch (e: Exception) {
e.printStackTrace()
}

}

private val logFilePath = Environment.getExternalStorageDirectory().toString() +
File.separator + "Example" +
File.separator + "CrashLog"

private fun saveThrowableMessage(errorMessage: String) {
if (TextUtils.isEmpty(errorMessage)) {
return
}
val file = File(logFilePath)
if (file.exists() && file.isDirectory) {
// fall through
} else {
file.mkdirs()
}
writeToFile(errorMessage, file)
}

private val formatter: DateFormat = SimpleDateFormat("yyyy-MM-dd-HH-mm-ss", Locale.CHINA)

private fun writeToFile(errorMessage: String, file: File) {
ThreadPoolManager.getShortPool()?.execute {
var outputStream: FileOutputStream? = null
try {
val timestamp = System.currentTimeMillis()
val time = formatter.format(Date())
val fileName = "crash-$time-$timestamp"
val inputStream = ByteArrayInputStream(errorMessage.toByteArray())
outputStream = FileOutputStream(File(file, "$fileName.txt"))
var len: Int
val bytes = ByteArray(1024)
while (inputStream.read(bytes).also { len = it } != -1) {
outputStream.write(bytes, 0, len)
}
outputStream.flush()
Log.e(TAG, "异常奔溃日志成功写入本地文件:${file.absolutePath}")
} catch (e: Exception) {
Log.e(TAG, "异常奔溃日志写入本地文件失败: ", e)
} finally {
if (outputStream != null) {
try {
outputStream.close()
} catch (e: IOException) {
// nothing
}
}
}
}
}

/**
* 自定义 Bug 上报
*/
private fun reportCustomThrowable(t: Thread, e: Throwable) {
Log.e(TAG, "reportCustomThrowable: ")
try {
AliHaAdapter.getInstance().reportCustomError(e) //配置项:自定义错误
} catch (ex: Exception) {
Log.e(TAG, "上报自定义异常Error: ", ex)
}
}
}

使用方式:

ApplicationonCreate() 方法中, 调用 RrCrashHandler.install(BuildConfig.DEBUG, AppUtils.isMainProgress(this))

参考链接:

https://github.com/android-notes/Cockroach

https://github.com/android-notes/Cockroach/blob/master/%E5%8E%9F%E7%90%86%E5%88%86%E6%9E%90.md

作者

Dench

发布于

2021-11-19

更新于

2021-11-19

许可协议

CC BY-NC-SA 4.0

Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×