A dispatcher of many threads (part 1/2)

Kotlin Coroutines have been almost unanimously received with applause by the Android software development community. However, there are some significant issues I have noticed in practice that are still unknown to most teams.

This week, one major issue was finally resolved.

Dispatchers.Main

Dispatchers.Main is the Kotlin Coroutines thread dispatcher for accessing Android’s main thread. Until recent improvements, the very first time this call is used, most apps experience a delay of up to a quarter second. Blocking the main thread pauses drawing the user interface and is enough to cause a noticeable lag to users.

It takes 16ms to draw one frame on Android. Blocking the main UI thread for 250ms skips fifteen frames at 60fps or thirty frames at 120fps. This is in addition to any other slow, blocking operations that might occur during your application’s initial startup.

This blocking operation serves no purpose. The Main Dispatcher was written using a ServiceLoader call. This choice resulted in a wasteful checksum operation being performed on the entire JAR file inside the APK.

I was able to workaround this problem several months ago on Firefox Preview by using a pre-release version of Google’s R8 code stripper along with custom rules. This wasn’t ideal. We were potentially subjecting our app to early stage bugs and were unlikely to get the same level of support for an unreleased version. We made this sacrifice because a quarter second startup time reduction was worth the cost.

At long last, there’s now a solution in a stable version of the Kotlin coroutines library itself. To take advantage of this fix, you must perform the following steps.

  1. Upgrade your version of Kotlin coroutines to 1.3.3 or higher in your build.gradle dependencies.
  2. Insert the required Proguard rules into your proguard-rules.pro file or whatever you’ve named it. See below.
  3. Ensure you’re using proguard-android-optimize.txt instead of the default, non-optimizing version.
  4. Ensure R8 is enabled. The R8 code stripper was introduced in Android Studio 3.3.0 and became default in Android Studio 3.4.0 or greater. It replaces Proguard’s code stripper, but should accept the same rules files. It can be configured in the gradle.properties file at the root of your application code.
# Add or replace these lines in your build.gradle file
# dependencies section
implementation org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.3
implementation org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.3
# Add these lines to proguard-rules.pro

# Allow R8 to optimize away the FastServiceLoader.
# Together with ServiceLoader optimization in R8
# this results in direct instantiation when loading Dispatchers.Main
-assumenosideeffects class kotlinx.coroutines.internal.MainDispatcherLoader {
    boolean FAST_SERVICE_LOADER_ENABLED return false;
}

-assumenosideeffects class kotlinx.coroutines.internal.FastServiceLoader {
    boolean ANDROID_DETECTED return true;
}
# Add these lines to your application build.gradle file
# or simply change the getDefaultProguardFile line

android {
   buildTypes {
      release {
        shrinkResources true
        minifyEnabled true
        proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
      }
   }
}
# Put this line somewhere in gradle.properties
android.enableR8=true

If you perform the above steps, gradle sync, and then perform a release build with optimization enabled, you should see a significant improvement in your cold start time versus previous release builds.

There are many options to quickly see this improvement with a flame chart. You could use Google’s Traceview or a dedicated, third-party tool like Nimbledroid.

Google’s new Jetpack Benchmark tool seems useful for UI code, but it is not recommended by its creators for measuring cold application startup time.

Part Two of this blog post will continue with a thorough discussion of Dispatchers.Default and why it’s such a dangerous default.

Leave a Reply

Your email address will not be published. Required fields are marked *