Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Shrink before you MultiDex

Shrink before you MultiDex

This presentation introduces available techniques for avoiding dex method count issue when developing Android apps which contain over 64k methods. Comparison of those two is also included in this presentation.

Avatar for Luka Babić

Luka Babić

April 19, 2016

Other Decks in Programming

Transcript

  1. Content • Intro to the problem • ProGuard • MultiDex

    • ProGuard vs. MultiDex • Pitfalls
  2. Content • Intro to the problem • ProGuard • MultiDex

    • ProGuard vs. MultiDex • Pitfalls
  3. Dex method count issue • Android system framework restriction •

    Maximum number of referenced methods in single dex - 65536
  4. How does it look? • “Conversion to Dalvik format failed:

    unable to execute dex: method ID not in [0, 0xffff]: 65536” - older build tools • “Trouble writing output: Too many field references 131270; max is 65536. You may try using --multi-dex option.”
  5. Early solutions • Reduce number of libraries used in code.

    • Use Jar Jar Links tool to shrink huge libraries (like Google Play Services, back when they were monolithic) (2) • Configure ProGuard to be used in Ant debug builds. Was not popular since it extended build time from few seconds to minute or two.
  6. Build customization evolved • Gradle based Android plugin introduced at

    Google I/O 2013. • Variant (and flavor) specific builds. • Easier to configure ProGuard to run in debug builds. buildTypes { release { minifyEnabled true proguardFiles (...) } debug { minifyEnabled true proguardFiles (...) } }
  7. Content • Intro to the problem • ProGuard • MultiDex

    • ProGuard vs. MultiDex • Pitfalls
  8. ProGuard • Java shrinking, optimization and obfuscation tool. • Removes

    unused classes, fields, methods and attributes. • We are mostly interested in ‘shrinking’ feature since it removes unused code. • Based on known entry points, ProGuard recursively determines which classes and methods are used and then removes unused ones.
  9. ProGuard - usage with Android • What would be default

    entry points for the Android app? • Based on default ‘proguard-android.txt’ rules: ◦ Activity methods accessible from layout XML-s (onClick) ◦ Getters and setters in Views ◦ Enumeration classes ◦ Static CREATOR member used for Parcelable ◦ Inner classes of R class
  10. When default config isn’t enough (I) • ProGuard has ability

    to trace used code by following direct references or some kind of simple reflection. • For example: ◦ Class.forName(“some.package.SomeClass”) ◦ SomeClass.class ◦ SomeClass.class.getField(“someField”) ◦ SomeClass.class.getDeclaredField(“someOtherField”) ◦ (...)
  11. When default config isn’t enough (II) • Problem occurs when

    there’s some more complex reflection involved. • Example: Butterknife, Android field and method view binder. • Generates “(...)$$ViewBinder” for the object, let’s say Activity. • Name of generated class: ◦ “com.package.SomeActivity$$ViewBinder”. • When using default configuration, view binding won’t happen in runtime.
  12. Why is $$ViewBinder removed? • What actually happens inside Butterknife:

    • ProGuard can’t resolve if specific (...)$$ViewBinder is used and it gets removed. (...) Class<?> viewBindingClass = Class.forName(clsName + ButterKnifeProcessor.SUFFIX; //clsName = “com.package.Someactivity” //ButterKnifeProcessor.SUFFIX = “$$ViewBinder” (...)
  13. • Include it inside ‘proguard-rules.txt’. • -keep **$$ViewBinder { *;

    } • ** - matches any kind of class name, possibly including any number of package separators. • { *; } - actually, optional in this case, class specification can come in here. How to keep $$ViewBinder?
  14. • Is there an easy way to start using ProGuard

    in a project? • From our experience, configuring ProGuard for a project can be divided into few steps: a. Keep dependencies referenced in build.gradle b. Silent warnings you get when keeping dependencies. c. Test your app, broad keep configurations when you get runtime errors. Silver bullet for ProGuard config
  15. • Gradle dependency id sometimes doesn’t match code package: •

    com.jakewharton:butterknife → butterknife.** • io.reactivex:rxjava → rx.** Not knowing this can get you to keep wrong packages in your ProGuard configuration file. Beware of the real package name
  16. Sample app dependencies dependencies { compile ‘io.reactivex:rxjava:1.1.0’ compile ‘io.reactivex:rxandroid:1.1.0’ compile

    ‘com.jakewharton:butterknife:7.0.1’ compile ‘org.simpleframework:simple-xml:2.7.1’ compile ‘com.squareup.okhttp:okhttp:2.5.0’ compile ‘com.raizlabs.android:DbFlow:2.2.1’ }
  17. Proguard config file (...usual stuff...) -keep class org.simpleframework.** {*;} -keep,

    allowshrinking class rx.** {*;} -keep, allowshrinking class butterknife.** {*;} -keep, allowshrinking class com.squareup.okhttp.** {*;} -keep, allowshrinking class com.raizlabs.android.** {*;} -dontwarn org.simpleframework.** -dontwarn rx.** -dontwarn butterknife.** -dontwarn com.squareup.okhttp.** -dontwarn okio.** //transitive dependency warning suppress. -dontwarn com.raizlabs.android.**
  18. -dontoptimize //dx tool doesn’t like premature bytecode optimizations -dontobfuscate //not

    interested in obfuscation (for now) -verbose //display additional information during process. -dontnote //don’t keep us noted -keepattributes Exceptions, InnerClasses, Signature, Deprecated, SourceFile, LineNumberTable, *Annotation*, EnclosingMethod, RuntimeVisibilityAnnotations -keepclassmembers enum ** {*;} -keep public class ** extends java.lang.Exception -dontwarn java.lang.invoke.** {*;} -dontwarn sun.misc.Unsafe Usual stuff
  19. Content • Intro to the problem • ProGuard • MultiDex

    • ProGuard vs. MultiDex • Pitfalls
  20. • Supported by Google since October 2014, as part of

    v0.14.0 Android Gradle plugin. • Straightforward implementation: ◦ Add dependency com.android.support:multidex:1.0.0 ◦ Extend MultiDexApplication or invoke MultiDex.install() in attachBaseContext()in the application class. ◦ Set multiDexEnabled=true under defaultConfig in build. gradle MultiDex
  21. How MultiDex works • Whole process comes down to: ◦

    Properly determine what to keep in main dex file ◦ Split classes into multiple dex files ◦ Load secondary dexes at runtime into classloader • It’s important to keep classes referenced directly from Android components (Application, Activity, Service, Broadcast receivers…) in main dex file. • They are listed in manifest_keep.txt
  22. Content • Intro to the problem • ProGuard • MultiDex

    • ProGuard vs. MultiDex • Pitfalls
  23. ProGuard vs. Multidex • What properties should we compare? ◦

    Dex method count ◦ Compile time length ◦ Configuration complexity ◦ Possible error handling
  24. Comparison on few apps App With MultiDex With ProGuard Reduction

    ratio How far from 64K Ministry of Sound 96057 51340 -47%, 44717 ~14200 Cilabs 116911 51463 -56%, 65448 ~14000 ADI 77361 40577 -48%, 36784 ~ 25000 • Another good thing about ProGuard is that removing unused classes and methods reduces APK size for a couple of megabytes.
  25. Configuration complexity and error fixing • MultiDex requires virtually zero

    configuration, while ProGuard takes certain amount of time to set it up. • Error fixing with ProGuard is possible, because its configuration can be updated. With MultiDex however, we have no control over configuration at. Multidex ProGuard Configuration complexity + - Error fixing - + APK size - +