Slide 1

Slide 1 text

Jared Burrows The Road to Single Dex Jared Burrows

Slide 2

Slide 2 text

Motivation • Yammer is a social networking service used for communication within an organization • 3 APKs: Min SDK 15, 16 and 19, Target SDK 26 • Loading extra DEX files may lead to possible ANRs • Multidex makes very large memory allocation requests may crash during run time

Slide 3

Slide 3 text

Focus • Reducing DEX method count • Reducing APK size

Slide 4

Slide 4 text

Results 1 DEX file, Smaller APK, All features kept! Before After Reduction Method Count 85k+ 60.8k 28.47% APK Size 10.7MB+ 7.1MB 33.64%

Slide 5

Slide 5 text

What makes an APK so big? 
 Multiple DEX files + Many Resources

Slide 6

Slide 6 text

What is inside an APK? • AndroidManifest.xml • assets/ • classes.dex • META-INF/
 - CERT.SF
 - MANIFEST.MF
 - CERT.RSA • lib/ • res/
 - drawable/
 - layout/ • resources.arsc

Slide 7

Slide 7 text

Big APKs - DEX size • Remove all/any dead code • Remove any old/unused libraries • Refactor out large/non-mobile libraries • Verify Gradle configurations

Slide 8

Slide 8 text

Big APKs - Resources • Remove extra and unused files in your “/res” folder • Remove extra and used files in your “/assets” folder

Slide 9

Slide 9 text

Before Optimizations - Debug build $ gradlew countDebugDexMethods sizeDebugApk
 
 > Task :countDebugDexMethods
 Total methods in debug.apk: 113007 (172.44% used) 
 Total fields in debug.apk: 50547 (77.13% used) 
 Methods remaining in debug.apk: 0 
 Fields remaining in debug.apk: 14988
 
 > Task :sizeDebugApk
 Total APK Size in debug.apk in bytes: 12386152 (12.3MB)

Slide 10

Slide 10 text

Before Optimizations - Release build $ gradlew countReleaseDexMethods sizeReleaseApk (with Proguard)
 
 > Task :countReleaseDexMethods
 Total methods in release.apk: 85259 (130.10% used) 
 Total fields in release.apk: 39887 (60.86% used) 
 Methods remaining in release.apk: 0 
 Fields remaining in release.apk: 25648
 
 > Task :sizeReleaseApk
 Total APK Size in release.apk in bytes: 10764242 (10.7MB)

Slide 11

Slide 11 text

Baselined Results 85k+ > 64k DEX limit = 2 DEX files Debug Release Method Count 113k+ 85k+ APK Size 12.3MB+ 10.7MB+

Slide 12

Slide 12 text

Optimizing the DEX size

Slide 13

Slide 13 text

Trimming the DEX size - Dead Code • Remove any and all unused code
 - PMD - Comes with Gradle
 - Proguard - Comes with Android Gradle Plugin
 - Intellij/Android Studio Inspections - Click “Analyze” > Click “Inspect Code…” • Remove all unused libraries
 - We chose HockeyApp over an open source analytics library called ApplicationInsights
 - HockeyApp’s functionality encompasses all of the functionality that we need from ApplicationInsights

Slide 14

Slide 14 text

Trimming the DEX size - Old Libraries • Remove old libraries
 - Using HockeyApp for releasing versioning/nightlies - Allowed us to remove AndroidQuery - version checking • Remove unnecessary libraries
 - Try not add libraries that duplicate Android’s existing functionality
 - Able to remove Apache Commons validator - validate emails addresses • Removing differential versions of same library
 - Jackson 1 and Jackson 2 are the same library but Jackson 2 has a different package name
 - Duplicating functionality across across prevent tools such as Proguard from optimizing

Slide 15

Slide 15 text

Trimming the DEX size - Transitive Libraries • Watch out for Library Duplication
 - Jackson 1 vs Jackson 2 vs Gson
 - Consider using a Groovy map in your build.gradle • Bringing in extra libraries based on a libraries’ dependencies
 - Use “gradlew dependencies” to see what you are actually using

Slide 16

Slide 16 text

Trimming the DEX size - Transitive Libraries ext {
 supportLibraryVersion = "27.0.0"
 }
 
 ext.deps = [
 "design" : "com.android.support:design:$supportLibraryVersion",
 "recyclerviewV7" : "com.android.support:recyclerview-v7:$supportLibraryVersion",
 "appcompatV7" : "com.android.support:appcompat-v7:$supportLibraryVersion",
 "cardviewV7" : "com.android.support:cardview-v7:$supportLibraryVersion"
 ] dependencies {
 compile deps.design
 } Keep track of dependencies, Enforce versions

Slide 17

Slide 17 text

Trimming the DEX size - Transitive Libraries gradlew dependencies --configuration compile* compile - Classpath for compiling the main sources. +--- project :yammer-common | \--- com.google.code.gson:gson:2.8.2 +--- project :yammer-data | \--- com.squareup.retrofit:retrofit:1.9.0 | \--- com.google.code.gson:gson:2.3.1 -> 2.8.2 +--- com.microsoft.aad:adal:1.12.0 | \--- com.google.code.gson:gson:2.2.4 -> 2.8.2

Slide 18

Slide 18 text

Trimming the DEX size - Large Libraries • Try not to use Large/Non-mobile libraries
 - Guava - 15k+ methods
 - Jackson2 2.9.0.pr3 - 12k+ methods
 - Check size of libraries with methodscount.com • Necessary Evils
 - AppCompat - 25.4.0 has 16.5k+ methods, 26.0.0-beta2 has 18.3k+ (continues to increase)
 - Google Play Services GCM 11.0.0 - 16.7k+ methods
 - Simply using both App Compat + Play Services together brings your APK to 30k+ methods, halfway to the 64k limit per DEX file!

Slide 19

Slide 19 text

Trimming the DEX size - MethodsCount methodscount.com/?lib=com.android.support:appcompat-v7:24.2.1

Slide 20

Slide 20 text

Trimming the DEX size - Gradle Configurations dependencies { compile project(":common")
 compile project(":common-test")
 } Pre Android Gradle Plugin 3.0

Slide 21

Slide 21 text

Trimming the DEX size - Gradle Configurations dependencies { compile project(":common")
 compile project(":common-test")
 } Pre Android Gradle Plugin 3.0

Slide 22

Slide 22 text

Trimming the DEX size - Gradle Configurations Pre Android Gradle Plugin 3.0 dependencies { compile project(":common")
 testCompile project(":common-test")
 }

Slide 23

Slide 23 text

Trimming the DEX size - Gradle Configurations dependencies { implementation project(":common")
 testImplementation project(":common-test")
 } With Android Gradle Plugin 3.0

Slide 24

Slide 24 text

Trimming the DEX size - Proguard • Use Proguard on Release builds
 - Set “minifyEnabled” to true!
 - Easiest way to help lower your DEX size • Consider using “proguard-android-optimize.txt” over “proguard-android.txt”
 - Proguard will make 5 optimization passes on your code

Slide 25

Slide 25 text

Trimming the DEX size - Proguard android {
 buildTypes {
 release {
 minifyEnabled true
 proguardFile getDefaultProguardFile("proguard-android.txt")
 proguardFile "custom-proguard-rules.txt"
 signingConfig signingConfigs.release
 }
 }
 }
 Make sure you turn on “minifyEnabled”

Slide 26

Slide 26 text

Trimming the DEX size - Proguard android {
 buildTypes {
 release {
 minifyEnabled true
 proguardFile getDefaultProguardFile("proguard-android.txt")
 proguardFile "custom-proguard-rules.txt"
 signingConfig signingConfigs.release
 }
 }
 }
 Make sure you turn on “minifyEnabled”

Slide 27

Slide 27 text

Trimming the DEX size - Proguard Make sure you provide Proguard rules android {
 buildTypes {
 release {
 minifyEnabled true
 proguardFile getDefaultProguardFile("proguard-android.txt")
 proguardFile "custom-proguard-rules.txt"
 signingConfig signingConfigs.release
 }
 }
 }


Slide 28

Slide 28 text

Trimming the DEX size - Proguard Try using more aggressive Proguard rules android {
 buildTypes {
 release {
 minifyEnabled true
 proguardFile getDefaultProguardFile("proguard-android-optimize.txt")
 proguardFile "custom-proguard-rules.txt"
 signingConfig signingConfigs.release
 }
 }
 }


Slide 29

Slide 29 text

Trimming the DEX size - Proguard • Remove extra Proguard rules • Keep them up to date with the current libraries in your app • Make sure to use “-keep” sparingly

Slide 30

Slide 30 text

Trimming the DEX size - Proguard Over using “-keep” can be costly -keep class okhttp3.* { ; }
 -keep interface okhttp3.* { ; }
 -dontwarn okhttp3.**
 -dontnote okhttp3.**

Slide 31

Slide 31 text

Trimming the DEX size - Proguard Over using “-keep” can be costly -keep class okhttp3.* { ; }
 -keep interface okhttp3.* { ; }
 -dontwarn okhttp3.**
 -dontnote okhttp3.**

Slide 32

Slide 32 text

Trimming the DEX size - Proguard Try and let Proguard decide -dontwarn okhttp3.**
 -dontnote okhttp3.**

Slide 33

Slide 33 text

Optimizing the Resources

Slide 34

Slide 34 text

Trimming the Resources - Lint • Make sure to use lint to check for “UnusedResources” • Do not suppress this lint rule unless you are writing an Android library • Consider failing the builds based on lint warnings/errors

Slide 35

Slide 35 text

Trimming the Resources - Lint Strict lint options android {
 lintOptions {
 textReport true
 textOutput "stdout"
 checkAllWarnings true
 warningsAsErrors true
 }
 }

Slide 36

Slide 36 text

Trimming the Resources - Lint android {
 lintOptions {
 error "UnusedResources"
 }
 } Less strict, check for “UnusedResources”

Slide 37

Slide 37 text

Trimming the Resources - ShrinkResources • What Lint does not find, you have to remove manually • Remove all unused resources in “assets/” folder
 - Raw media such as Font files • Use “shrinkResources” to remove unused resources
 - Great for removing library dependencies resources in your final APK

Slide 38

Slide 38 text

Trimming the Resources - ShrinkResources android {
 buildTypes {
 release {
 minifyEnabled true
 proguardFile getDefaultProguardFile("proguard-android-optimize.txt")
 proguardFile "custom-proguard-rules.txt"
 signingConfig signingConfigs.release
 }
 }
 }
 Missing “shrinkResources”

Slide 39

Slide 39 text

Trimming the Resources - ShrinkResources Added “shrinkResources” android {
 buildTypes {
 release {
 minifyEnabled true shrinkResources true
 proguardFile getDefaultProguardFile("proguard-android-optimize.txt")
 proguardFile "custom-proguard-rules.txt"
 signingConfig signingConfigs.release
 }
 }
 }


Slide 40

Slide 40 text

Trimming the Resources - ShrinkResources Using “shrinkResources” android {
 buildTypes {
 release {
 minifyEnabled true shrinkResources true
 proguardFile getDefaultProguardFile("proguard-android-optimize.txt")
 proguardFile "custom-proguard-rules.txt"
 signingConfig signingConfigs.release
 }
 }
 }


Slide 41

Slide 41 text

Trimming the Resources - ResConfigs • Use “resConfigs” to filter out localizations that you do not want/support in your app

Slide 42

Slide 42 text

Trimming the Resources - ResConfig Using “resConfigs” to keep multiple resources or languages android { defaultConfig { resConfigs "en" } }

Slide 43

Slide 43 text

Trimming the Resources - ResConfig Using “resConfigs” to keep multiple languages android { defaultConfig {
 resConfigs "en","de","fr"
 }
 }

Slide 44

Slide 44 text

Trimming the Resources - PackagingOptions • Remove extra files that are baked into your APK • Use the APK Analyzer to find these files
 - In Android Studio, Click “Build” > Click “Analyze APK…” • Use “packagingOptions” to filter out these files

Slide 45

Slide 45 text

Trimming the Resources - PackagingOptions android {
 packagingOptions {
 exclude "META-INF/LICENSE.txt"
 exclude "META-INF/NOTICE.txt"
 }
 } Common “packagingOptions” excludes

Slide 46

Slide 46 text

Trimming the Resources - PackagingOptions After using common “packagingOptions” excludes

Slide 47

Slide 47 text

Trimming the Resources - PackagingOptions Strict “packagingOptions” excludes android {
 packagingOptions {
 exclude "**/*.kotlin_module" exclude "**/*.version"
 exclude "**/kotlin/**"
 exclude "**/*.xml"
 exclude “**/*.txt" exclude "**/*.properties"
 }
 }

Slide 48

Slide 48 text

Trimming the Resources - PackagingOptions After using stricter “packagingOptions” excludes

Slide 49

Slide 49 text

Trimming the Resources - Vector Drawables • Consider converting simple PNGs to “Vector” Drawables • Single XML file is smaller than multiple PNGs • Use tools such as “svg2android” to convert SVGs to Android Vector Drawables

Slide 50

Slide 50 text

Trimming the Resources - Vector Drawables android {
 defaultConfig {
 resConfigs "en", "de", "fr"
 vectorDrawables {
 useSupportLibrary = true
 }
 }
 } Using “vectorDrawables.useSupportLibrary = true” to prevent PNG generation

Slide 51

Slide 51 text

Trimming the Resources - Vector Drawables android {
 defaultConfig {
 resConfigs "en", "de", "fr"
 vectorDrawables {
 useSupportLibrary = true
 }
 }
 } Using “vectorDrawables.useSupportLibrary = true” to prevent PNG generation

Slide 52

Slide 52 text

Trimming the Resources - Vector Drawables inloop.github.io/svg2android/

Slide 53

Slide 53 text

Trimming the Resources - Vector Drawables After dropping your SVG file, Copy it to your project in “res/drawable”

Slide 54

Slide 54 text

Trimming the Resources - Vector Drawables Make sure to use “app:srcCompat” your in XML 
 
 
 
 
 


Slide 55

Slide 55 text

Trimming the Resources - Vector Drawables Make sure to use “app:srcCompat” your in XML 
 
 
 
 
 


Slide 56

Slide 56 text

Trimming the Resources - Vector Drawables Using “app:srcCompat” your in XML 
 
 
 
 
 


Slide 57

Slide 57 text

Trimming the Resources - WebP • Reduces image sizes by converting to WebP • Does not need to compress at build time(PNG crunching) • By converting beforehand, this will help speed up your build

Slide 58

Slide 58 text

Trimming the Resources - WebP Right-click on “drawable” folders, click on “Convert to WebP”

Slide 59

Slide 59 text

After Optimizations - Debug build $ gradlew countDebugDexMethods sizeDebugApk
 
 > Task :countDebugDexMethods
 Total methods in debug.apk: 100595 (153.50% used) 
 Total fields in debug.apk: 51278 (78.25% used) 
 Methods remaining in debug.apk: 0 
 Fields remaining in debug.apk: 14257
 
 > Task :sizeDebugApk
 Total APK Size in debug.apk in bytes: 10053036 (10.1MB)

Slide 60

Slide 60 text

After Optimizations - Release build $ gradlew countReleaseDexMethods sizeReleaseApk (with Proguard)
 
 > Task :countReleaseDexMethods
 Total methods in release.apk: 60810 (92.79% used) 
 Total fields in release.apk: 29445 (44.93% used) 
 Methods remaining in release.apk: 4725
 Fields remaining in release.apk: 36090
 
 > Task :sizeReleaseApk
 Total APK Size in release.apk in bytes: 7049930 (7.1MB)

Slide 61

Slide 61 text

Optimized Results 60.8k+ < 64k DEX limit = 1 DEX file! Debug Release Method Count 100k+ 60.8k APK Size 10.1MB+ 7.1MB

Slide 62

Slide 62 text

Before vs After Release Builds 1 DEX file, Smaller APK, All features kept! Before After Reduction Method Count 85k+ 60.8k 28.47% APK Size 10.7MB+ 7.1MB 33.64%

Slide 63

Slide 63 text

Automating Dex Count Plugin gradle.taskGraph.whenReady { taskGraph ->
 if (taskGraph.hasTask(":assembleRelease")) {
 dexcount {
 maxMethodCount = 61000 // 61k methods
 }
 }
 } github.com/KeepSafe/dexcount-gradle-plugin

Slide 64

Slide 64 text

Automating APK Size Plugin gradle.taskGraph.whenReady { taskGraph ->
 if (taskGraph.hasTask(":assembleRelease")) {
 apkSize {
 maxApkSize = 8000 // in Kb
 }
 }
 } github.com/vanniktech/gradle-android-apk-size-plugin

Slide 65

Slide 65 text

Thank you! Questions? twitter.com/jaredsburrows github.com/jaredsburrows [email protected] jaredsburrows.com