• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3  */
4 
5 package kotlinx.coroutines.android
6 
7 import android.os.*
8 import kotlinx.coroutines.*
9 import java.lang.reflect.*
10 import kotlin.coroutines.*
11 
12 internal class AndroidExceptionPreHandler :
13     AbstractCoroutineContextElement(CoroutineExceptionHandler), CoroutineExceptionHandler
14 {
15     @Volatile
16     private var _preHandler: Any? = this // uninitialized marker
17 
18     // Reflectively lookup pre-handler.
preHandlernull19     private fun preHandler(): Method? {
20         val current = _preHandler
21         if (current !== this) return current as Method?
22         val declared = try {
23             Thread::class.java.getDeclaredMethod("getUncaughtExceptionPreHandler").takeIf {
24                 Modifier.isPublic(it.modifiers) && Modifier.isStatic(it.modifiers)
25             }
26         } catch (e: Throwable) {
27             null /* not found */
28         }
29         _preHandler = declared
30         return declared
31     }
32 
handleExceptionnull33     override fun handleException(context: CoroutineContext, exception: Throwable) {
34         /*
35          * Android Oreo introduced private API for a global pre-handler for uncaught exceptions, to ensure that the
36          * exceptions are logged even if the default uncaught exception handler is replaced by the app. The pre-handler
37          * is invoked from the Thread's private dispatchUncaughtException() method, so our manual invocation of the
38          * Thread's uncaught exception handler bypasses the pre-handler in Android Oreo, and uncaught coroutine
39          * exceptions are not logged. This issue was addressed in Android Pie, which added a check in the default
40          * uncaught exception handler to invoke the pre-handler if it was not invoked already (see
41          * https://android-review.googlesource.com/c/platform/frameworks/base/+/654578/). So the issue is present only
42          * in Android Oreo.
43          *
44          * We're fixing this by manually invoking the pre-handler using reflection, if running on an Android Oreo SDK
45          * version (26 and 27).
46          */
47         if (Build.VERSION.SDK_INT in 26..27) {
48             (preHandler()?.invoke(null) as? Thread.UncaughtExceptionHandler)
49                 ?.uncaughtException(Thread.currentThread(), exception)
50         }
51     }
52 }
53