Skip to content

Toothpick javax Annotations

Stéphane Nicolas edited this page Jan 20, 2017 · 10 revisions

Multidex and Android pre-Lollipop, such an issue...

Android APKs enclose executable bytecode in the form of Dalvik Executable (DEX) files. The Dalvik Executable specification limits the total number of methods that can be referenced within a single DEX file to 65,536. With Multidex, we can bypass that limit by allowing an app to build and read multiple DEX files. Lolipop supports multidex natively, and a support library from Google provides backward multidex compatibility for pre-Lolipop devices.

However, all classes required during startup must be provided in the primary DEX file, otherwise the app will crash. The build tools perform complex decision-making to determine which classes are added in the primary DEX file. But, what would happen if the number of methods that we reference within the primary DEX is higher than 65,536? Then, if your build min sdk is Lolipop+ it would not be a problem, but for pre-Lolipop devices, such an app cannot be built.

And, due to a problem in some old class loaders, all annotations using runtime retention are added in the primary DEX. And also, all the classes that use them: whether a field, a constructor, a method, or the class itself is annotated using a runtime retained annotation, it will also be added in the primary DEX.

JSR 330 annotations, the ones used by Toothpick or Dagger, have runtime retention policy. Thus, all classes using those annotations (@Inject, @Named, ...) will be included in the primary DEX, contributing to exceed the limit.

Toothpick processes JSR 330 annotations annotations at compile time, hence the runtime retention is not needed for TP. For that reason, we provide an artifact with the same annotations but avoiding the runtime retention:

com.github.stephanenicolas.toothpick:toothpick-javax-annotations:X.Y.Z

By using this package, then one can drastically decrease the number of methods inside the first dex. This little advanced trick can help to build large apps that still need to support pre-Lolipop devices.

To use these TP set of annotations, you have to exclude any transitive dependency to the original JSR 330 annotations in your build. There are 2 Toothpick artifacts using the JSR 330 annotations:

  • toothpick-runtime
  • and toothpick:smoothie.

Setting up Toothpick using toothpick-javax-annotations will thus look like this:

dependencies {
  compile 'com.github.stephanenicolas.toothpick:toothpick-runtime:X.Y.Z' {
    exclude group: 'javax.inject'
  }
  compile 'com.github.stephanenicolas.toothpick:smoothie:X.Y.Z' {
    exclude group: 'javax.inject'
  }
  compile "com.github.stephanenicolas.toothpick:toothpick-javax-annotations:X.Y.Z"
  annotationProcessor 'com.github.stephanenicolas.toothpick:toothpick-compiler:X.Y.Z'

  // Test
  testCompile 'com.github.stephanenicolas.toothpick:toothpick-testing:X.Y.Z'
}

At compile time, by explicitly excluding the JSR 330 annotations, and using the TP annotations, we ensure a minimal first dex.

At test compile time, you can still use the JSR 330 annotations, they actually make your life easier to create tests. TP testing relies on using the JSR 330 annotations for creating named bindings of mocked dependencies. And yes, TP testing does use reflection, but TP runtime does not.