Fighting application size with ProGuard and beyond Eric Lafortune
Transcription
Fighting application size with ProGuard and beyond Eric Lafortune
Fighting application size with ProGuard and beyond Eric Lafortune Developer of ProGuard and DexGuard Technical director at Saikoa www.saikoa.com Applications App Resources Dalvik bytecode Assets Native code Renderscript code Technical constraints Google Play Store: max 50 MB App Resources Dalvik bytecode Assets Native code Renderscript code Dex format: max 65536 method IDs Froyo/Gingerbread: 5 MB LinearAlloc buffer Android build process Android build process Assets Assets XML resources Application Java source Aapt Javac Application Java bytecode Dex Libraries Java bytecode Compiled XML resources Libraries Java bytecode Dalvik bytecode Compress media ● Images: JPEG (lossy) ● Images: PNG aapt crunch -S input_dir -C output_dir ● Audio: AAC ● Video: H264 AVC Remove unused resources ● Lint (Android SDK) gradle lint ● android-resource-remover (Philipp Berner) ● android-unused-resources (S. Kennedy) Splitting apk files Splitting apk files Assets Assets Assets XML resources Application Java source Aapt Javac Compiled XML resources Application Java bytecode Dex Libraries Java bytecode Compiled XML resources Libraries Java bytecode Dalvik bytecode Dalvik bytecode Splitting apk files ● Different variants in Gradle: android android {{ ... ... productFlavors productFlavors {{ flavor1 flavor1 {{ ignoreAssetsPattern ignoreAssetsPattern '!*_large.png' '!*_large.png' assets.srcDirs assets.srcDirs == ['assets1'] ['assets1'] res.srcDirs == ['res1'] res.srcDirs ['res1'] }} }} }} flavor2 flavor2 {{ ignoreAssetsPattern ignoreAssetsPattern '!*_small.png' '!*_small.png' assets.srcDirs = ['assets2'] assets.srcDirs = ['assets2'] res.srcDirs == ['res2'] res.srcDirs ['res2'] }} Splitting apk files ● Splits in Gradle: android android {{ ... splits { density density {{ enable enable true reset() reset() include include 'mdpi', 'hdpi', 'xhdpi' }} } } abi abi { enable enable true reset() reset() include include 'armeabi-v7a', 'x86', 'mips' 'mips' }} Code size Code size Assets Assets java.lang.IllegalArgumentException: XML resources method ID not in [0, 0xffff]: 65536 Application Java source Javac Aapt Application Java bytecode Dex Libraries Java bytecode Compiled XML resources Libraries Java bytecode Dalvik bytecode Dex file Strings Types Prototypes Field IDs Method IDs Class definitions ... Method IDs #0: Accounts . <init>(.....) #1: AccountManager . get(......) #2: AccountManager . ….. #3: ….. #4: ….. ... ... ... ... ... ... ... ... ... ... ... ... ... ... #65535: ….. ProGuard Application code Shrink Optimize Obfuscate Processed code Libraries Android runtime Android runtime ProGuard Assets Assets XML resources Application Java source Aapt Javac Application Java bytecode ProGuard Libraries Java bytecode Compiled XML resources Libraries Java bytecode Processed Java bytecode Dex Dalvik bytecode Example configuration: Dagger -keepclassmembers,allowobfuscation -keepclassmembers,allowobfuscation class class ** {{ @dagger.** @dagger.** <fields>; <fields>; @dagger.** <methods>; @dagger.** <methods>; }} -keep -keep -keep -keep -keep -keep class class class class class class Generated classes **$$ModuleAdapter **$$ModuleAdapter **$$InjectAdapter **$$InjectAdapter **$$StaticInjection **$$StaticInjection -keep -keep class class com.example.DemoModule com.example.DemoModule -keep -keep class class com.example.AndroidModule com.example.AndroidModule -keep -keep class class com.example.HomeActivity com.example.HomeActivity -keepnames -keepnames class class dagger.Lazy dagger.Lazy Corresponding base classes Libraries Library Google Play Services Guava Method IDs 6.0 14,495 15.0 Yahoo Flurry Ads Facebook 8,511 4.1.0 ActionBarSherlock RoboGuice 23,607 4.4.0 4,300 2.0 3,787 3.5.2 3,166 Yahoo Flurry Analytics Scala Core 2.11.2 4.1.0 1,148 50,786 ProGuard on libraries Library Java bytecode ProGuard Processed library Java bytecode Shrink Ads library configuration.txt -injars -injars -outjars -outjars google-play-services.jar google-play-services.jar google-play-services-ads.jar google-play-services-ads.jar -libraryjars -libraryjars -libraryjars -libraryjars android-sdk/extras/android/support/v4/android-support-v4.jar android-sdk/platforms/android-20/android.jar -dontoptimize -dontoptimize -dontobfuscate -dontobfuscate -dontwarn -dontwarn com.google.**.R -dontwarn -dontwarn com.google.**.R$* -dontnote -dontnote -keep -keep public public class class com.google.android.gms.ads.** { public public protected protected *; }} -keep -keep class com.google.android.gms.common.internal.safeparcel.SafeParcelable { java.lang.String NULL; }} Command line: proguard proguard @@ configuration.txt configuration.txt Shrink Ads library Google Play Services library ProGuard Ads library Before After Reduction Size 3.3 M 578 K 83 % Classes 3294 497 85 % Method IDs 23607 3751 84 % Shrink Maps library -injars -injars -outjars -outjars google-play-services.jar google-play-services.jar google-play-services-maps.jar google-play-services-maps.jar -libraryjars -libraryjars -libraryjars -libraryjars android-sdk/extras/android/support/v4/android-support-v4.jar android-sdk/platforms/android-20/android.jar -dontoptimize -dontoptimize -dontobfuscate -dontobfuscate -dontwarn -dontwarn com.google.**.R -dontwarn -dontwarn com.google.**.R$* -dontnote -dontnote -keep -keep public public class class com.google.android.gms.maps.**, com.google.android.gms.common.**, com.google.android.gms.common.**, com.google.android.gms.location.** com.google.android.gms.location.** { public public protected protected *; }} -keep -keep class com.google.android.gms.common.internal.safeparcel.SafeParcelable { java.lang.String NULL; }} proguard proguard @@ configuration.txt configuration.txt Shrink Maps library Google Play Services library ProGuard Maps library Before After Reduction Size 3.3 M 583 K 82 % Classes 3294 546 83 % Method IDs 23607 4891 79 % Splitting dex files Main dex file Secondary dex file Build-time Run-time Splitting dex files Assets Assets XML resources Application Java source Libraries Java bytecode Aapt Javac Compiled XML resources Application Java bytecode Dex Main Dalvik bytecode Libraries Java bytecode Dex Secondary Dalvik bytecode Dex splitting: tools ● secondary-dex-gradle (Mohit Kanwal) ● Dex 65536 (Mmin18) ● DexGuard (Saikoa) ● Android 5.0 / android.support.multidex Dex splitting: secondary-dex-gradle ● ● ApplicationProject LibraryProject settings.gradle include include ':ApplicationProject', ':ApplicationProject', ':LibraryProject' ':LibraryProject' ● AndroidManifest.xml <application <application android:name="com.example.App" android:name="com.example.App" ... ... >> ● AppProject/src/com/example/App.java AppProject/src/com/example/SecondaryDex.java AppProject/src/com/example/FrameworkHack.java Dex splitting: secondary-dex-gradle Advantages: ● It works! Disadvantages: ● Split project ● No references library → application ● Dex classpath hack Dex splitting: Dex 65536 ● libs/google-play-services.jar (for example) ● custom_rules.xml ... ... <pathtool <pathtool libs="libs/google-play-services.jar" libs="libs/google-play-services.jar" ... ... /> /> ... ... ● AndroidManifest.xml <application <application android:name="com.example.App" android:name="com.example.App" ... ... >> ● src/com/example/App.java new new DexClassLoader(dexFile.getAbsolutePath(), DexClassLoader(dexFile.getAbsolutePath(), dexOpt.getAbsolutePath(), nativeLibraryDir, classLoader.getParent()); Dex splitting: Dex 65536 Advantages: ● It works! Disadvantages: ● Only for library jars ● Dex classloader hack Dex splitting: DexGuard ● Gradle, Ant, Eclipse, Maven, Android Studio,... ● dexguard-project.txt -splitdexfile com.google.** com.google.** -splitdexfile !com.facebook.samples.**, !com.facebook.samples.**, com.facebook.** com.facebook.** Dex splitting: DexGuard Advantages: ● Transparent ● Flexible ● Lazy loading Disadvantages: ● Reflection Dex splitting: Android 5.0 Android 5.0 loads classes2.dex, classes3.dex, etc. ● build.gradle ... ... dx.additionalParameters dx.additionalParameters += += '--multi-dex' '--multi-dex' dx.additionalParameters dx.additionalParameters += += "--main-dex-list=$projectDir/list.txt".toString() "--main-dex-list=$projectDir/list.txt".toString() ● list.txt com/example/SomeClass.class com/example/SomeClass.class ... ... ● AndroidManifest.xml (for legacy platforms) <application <application android:name="android.support.multidex.MultiDexApplication" android:name="android.support.multidex.MultiDexApplication" ... ... >> Dex splitting: Android 5.0 Advantages: ● Latest standard Disadvantages: ● Eager loading Summary Resources and assets: – Compress – Trim – Split apk Bytecode: – Shrink libraries – Shrink application – Split dex file Further reading ● “Custom Class Loading in Dalvik”, Fred Chung, Google http://android-developers.blogspot.com/2011/07/custom-class-loading-in-dalvik.html ● “Play Services 5.0 Is A Monolith Abomination”, Jake Wharton http://jakewharton.com/play-services-is-a-monolith/ ● “DEX Sky’s the limit? No, 65K methods is”, Sebastiano Gottardo https://medium.com/@rotxed/dex-skys-the-limit-no-65k-methods-is-28e6cb40cf71 ● “Multi-dex to rescue from the infamous 65536 methods limit”, Alex Lipov http://blog.osom.info/2014/10/multi-dex-to-rescue-from-infamous-65536.html ● “Under the Hood: Dalvik patch for Facebook for Android”, David Reiss https://www.facebook.com/notes/facebook-engineering/under-the-hood-dalvik-patch-for-facebook-for-android/10151345597798920 ● “Tips for reducing APK file size”, David Karlsson, Sony http://developer.sonymobile.com/2012/01/31/tips-for-reducing-apk-file-size/ Software tools ● android-resource-remover, Philipp Berner, KeepSafe https://github.com/KeepSafe/android-resource-remover ● android-unused-resources, S. Kennedy https://code.google.com/p/android-unused-resources/ ● secondary-dex-gradle, Mohit Kanwal https://github.com/creativepsyco/secondary-dex-gradle ● Dex 65536, Mmin18 https://github.com/mmin18/Dex65536 ● ProGuard http://proguard.sourceforge.net/ ● DexGuard http://www.saikoa.com/dexguard Questions? Open source ProGuard Optimization Shrinking Saikoa Java bytecode DexGuard Dalvik bytecode