@@ -5,6 +5,13 @@ import java.net.*
5
5
import java.util.*
6
6
import java.util.jar.*
7
7
import java.util.zip.*
8
+ import kotlin.collections.ArrayList
9
+
10
+ /* *
11
+ * TODO investigate possible java.vendor values for fallbacks
12
+ * Don't use JvmField here to enable R8 optimizations via "assumenosideeffects"
13
+ */
14
+ internal val ANDROID_DETECTED = systemProp(" java.vendor" )?.toLowerCase()?.contains(" android" ) ? : false
8
15
9
16
/* *
10
17
* A simplified version of [ServiceLoader].
@@ -20,7 +27,59 @@ import java.util.zip.*
20
27
internal object FastServiceLoader {
21
28
private const val PREFIX : String = " META-INF/services/"
22
29
23
- internal fun <S > load (service : Class <S >, loader : ClassLoader ): List <S > {
30
+ /* *
31
+ * This method attempts to load [MainDispatcherFactory] in Android-friendly way.
32
+ *
33
+ * If we are not on Android, this method fallbacks to a regular service loading,
34
+ * else we attempt to do `Class.forName` lookup for
35
+ * `AndroidDispatcherFactory` and `TestMainDispatcherFactory`.
36
+ * If lookups are successful, we return resulting instances because we know that
37
+ * `MainDispatcherFactory` API is internal and this is the only possible classes of `MainDispatcherFactory` Service on Android.
38
+ *
39
+ * Such intricate dance is required to avoid calls to `ServiceLoader.load` for multiple reasons:
40
+ * 1) It eliminates disk lookup on potentially slow devices on the Main thread.
41
+ * 2) Various Android toolchain versions by various vendors don't tend to handle ServiceLoader calls properly.
42
+ * Sometimes META-INF is removed from the resulting APK, sometimes class names are mangled, etc.
43
+ * While it is not the problem of `kotlinx.coroutines`, it significantly worsens user experience, thus we are workarounding it.
44
+ * Examples of such issues are #932, #1072, #1557, #1567
45
+ *
46
+ * We also use SL for [CoroutineExceptionHandler], but we do not experience the same problems and CEH is a public API
47
+ * that may already be injected vis SL, so we are not using the same technique for it.
48
+ */
49
+ internal fun loadMainDispatcherFactory (): List <MainDispatcherFactory > {
50
+ val clz = MainDispatcherFactory ::class .java
51
+ if (! ANDROID_DETECTED ) {
52
+ return load(clz, clz.classLoader)
53
+ }
54
+
55
+ return try {
56
+ val result = ArrayList <MainDispatcherFactory >()
57
+ createInstanceOf(clz, " kotlinx.coroutines.android.AndroidDispatcherFactory" )?.apply { result.add(this ) }
58
+ createInstanceOf(clz, " kotlinx.coroutines.test.internal.TestMainDispatcherFactory" )?.apply { result.add(this ) }
59
+ result
60
+ } catch (e: Throwable ) {
61
+ // Fallback to the regular SL in case of any unexpected exception
62
+ load(clz, clz.classLoader)
63
+ }
64
+ }
65
+
66
+ /*
67
+ * This method is inline to have a direct Class.forName("string literal") in the byte code to avoid weird interactions with ProGuard/R8.
68
+ */
69
+ @Suppress(" NOTHING_TO_INLINE" )
70
+ private inline fun createInstanceOf (
71
+ baseClass : Class <MainDispatcherFactory >,
72
+ serviceClass : String
73
+ ): MainDispatcherFactory ? {
74
+ return try {
75
+ val clz = Class .forName(serviceClass, true , baseClass.classLoader)
76
+ baseClass.cast(clz.getDeclaredConstructor().newInstance())
77
+ } catch (e: ClassNotFoundException ) { // Do not fail if TestMainDispatcherFactory is not found
78
+ null
79
+ }
80
+ }
81
+
82
+ private fun <S > load (service : Class <S >, loader : ClassLoader ): List <S > {
24
83
return try {
25
84
loadProviders(service, loader)
26
85
} catch (e: Throwable ) {
0 commit comments