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