Search code examples
androiddagger-2

dagger-android and first creation of DispatchingAndroidInjector


I am working on speeding up the app launches. I am seeing that the very first creation of a ~1400 entries map of DispatchingAndroidInjector takes more than 2 seconds. My theory is that each entry in the map results in the corresponding class load and that results in dex file IO. Can anyone confirm the reason of this slowness? 

I am looking to reduce this latency and trying to determine if reducing the map size will speed up the cold starts.

DispatchingAndroidInjector(
    Map<Class<?>, Provider<AndroidInjector.Factory<?>>> injectorFactoriesWithClassKeys,
    Map<String, Provider<AndroidInjector.Factory<?>>> injectorFactoriesWithStringKeys) {
  this.injectorFactories = merge(injectorFactoriesWithClassKeys, injectorFactoriesWithStringKeys);
}```

Solution

  • Initial classloading for dagger.android Map population is known to be an expensive startup cost, and experimentalUseStringKeys (introduced in Dagger 2.17 in August 2018) was meant to fix it. See Ron Shapiro's comments in issue 1064.

    From the commit:

    Add an option to use string keys for dagger.android and allow the keys to be obfuscated

    This is enabled by default internally, where proguard understands -identifiernamestring, but disabled externally where it doesn't (and where R8 is not yet the standard). It can be enabled with the -Adagger.android.experimentalUseStringKeys flag for users that don't use proguard. This way we can guarantee that any project without modification will continue to work when proguarded.

    This solves an issue where classloading of every Activity/Fragment/Service injector that's a child of the root component is forced into application startup. [...]

    One of the hazards of this solution is that class names can be obfuscated via Proguard/R8/AppReduce, so the most built-in workaround requires a version of R8 that supports -identifiernamestring as in the AndroidProcessor documentation linked above. (R8 was new at the time that this feature came out, but after five years the availability and stability of R8 have increased significantly.)

    Other solutions:

    • Configure Proguard to keep the name of any class you might inject via dagger.android using -keepnames.
    • Avoid using dagger.android specifically for your application entry point fragments/activities while you load the full Map<Class, AndroidInjector> in the background.

    Though it is possible to work around this by keeping Class keys but adding a qualifier annotation onto your @Binds @IntoMap binding, you would be shutting yourself out of using @ContributesAndroidInjector and the default AndroidInjection/AndroidSupportInjection classes. If the other solutions above don't work, and you would find it necessary to refactor all 1400 (!) of your bindings to support this manual binding workaround, you might find it easier just to pursue a migration to Hilt instead.