Slide 1

Slide 1 text

PROGUARD

Slide 2

Slide 2 text

chalup.github.io ! " + @jchalupski +Jerzy Chalupski

Slide 3

Slide 3 text

PROGUARD

Slide 4

Slide 4 text

Hello, world!

Slide 5

Slide 5 text

dependencies {
 
 }

Slide 6

Slide 6 text

$ g assembleDebug
 Total time: 1.908 secs
 
 $ du -h ./app-debug.apk
 24K ./app-debug.apk

Slide 7

Slide 7 text

dependencies {
 compile ‘google-play-services'
 }

Slide 8

Slide 8 text

$ g assembleDebug
 Total time: 3.014 secs
 
 $ du -h ./app-debug.apk
 1.8M ./app-debug.apk

Slide 9

Slide 9 text

$ g assembleDebug
 Total time: 3.014 secs
 
 $ du -h ./app-debug.apk
 1.8M ./app-debug.apk $ tar -xzvf app-debug.apk
 
 $ du -h -d 0 classes.dex res
 3.8M classes.dex
 592K res

Slide 10

Slide 10 text

! futuresimple/dex-method-counts

Slide 11

Slide 11 text

$ dexcount ./app-debug.apk
 android.support.v4: 6285
 com.google.ads: 124
 com.google.android.gms: 22730
 org.chalup.proguardtechtalk: 18

Slide 12

Slide 12 text

#YOLO

Slide 13

Slide 13 text

dependencies {
 compile ‘google-play-services’ compile ‘support.*’ compile ‘guava’ compile ‘gson’ compile ‘joda-time’ compile ‘square.*’ compile ‘butterknife’ compile ‘rxjava’ compile ‘rxandroid’ compile ‘android-arsenal.*’ }

Slide 14

Slide 14 text

$ g assembleDebug
 :app:dexDebug FAILED
 
 UNEXPECTED TOP-LEVEL EXCEPTION:
 DexIndexOverflowException method ID not in [0, 0xffff]

Slide 15

Slide 15 text

dependencies {
 compile ‘support.multidex’ } buildTypes {
 debug {
 multiDexEnabled true
 }
 }


Slide 16

Slide 16 text

$ g assembleDebug


Slide 17

Slide 17 text

$ g assembleDebug BUILD SUCCESSFUL

Slide 18

Slide 18 text

$ g assembleDebug BUILD SUCCESSFUL 
 Total time: 48.784 secs


Slide 19

Slide 19 text

$ g assembleDebug BUILD SUCCESSFUL 
 Total time: 48.784 secs
 $ du -h ./app-debug.apk
 7.5M ./app-debug.apk

Slide 20

Slide 20 text

$ g assembleDebug BUILD SUCCESSFUL 
 Total time: 48.784 secs
 $ du -h ./app-debug.apk
 7.5M ./app-debug.apk $ dexcount ./app-debug.apk Total method count: 81986


Slide 21

Slide 21 text

@Override
 protected void attachBaseContext(Context c) {
 super.attachBaseContext(c);
 
 MultiDex.install(this);
 }

Slide 22

Slide 22 text

@Override
 protected void attachBaseContext(Context c) {
 super.attachBaseContext(c);
 long start = System.currentTimeMillis();
 MultiDex.install(this);
 long end = System.currentTimeMillis(); long elapsed = end - start;
 Log.d(TAG, “multidex: " + elapsed + "ms");
 }

Slide 23

Slide 23 text

@Override
 protected void attachBaseContext(Context c) {
 super.attachBaseContext(c);
 long start = System.currentTimeMillis();
 MultiDex.install(this);
 long end = System.currentTimeMillis(); long elapsed = end - start;
 Log.d(TAG, “multidex: " + elapsed + "ms");
 } 252ms on Samsung S5 with KitKat at 66k methods

Slide 24

Slide 24 text

@Override
 protected void attachBaseContext(Context c) {
 super.attachBaseContext(c);
 long start = System.currentTimeMillis();
 MultiDex.install(this);
 long end = System.currentTimeMillis(); long elapsed = end - start;
 Log.d(TAG, “multidex: " + elapsed + "ms");
 } 1083ms on Samsung S5 with KitKat at 81k methods

Slide 25

Slide 25 text

@Override
 protected void attachBaseContext(Context c) {
 super.attachBaseContext(c);
 long start = System.currentTimeMillis();
 MultiDex.install(this);
 long end = System.currentTimeMillis(); long elapsed = end - start;
 Log.d(TAG, “multidex: " + elapsed + "ms");
 } ~4000ms on Nexus 5 with KitKat at ~130k methods

Slide 26

Slide 26 text

@Override
 protected void attachBaseContext(Context c) {
 super.attachBaseContext(c);
 long start = System.currentTimeMillis();
 MultiDex.install(this);
 long end = System.currentTimeMillis(); long elapsed = end - start;
 Log.d(TAG, “multidex: " + elapsed + "ms");
 } 2ms on Nexus 5 with Lollipop at 81k methods

Slide 27

Slide 27 text

TAKEAWAY #1 multidex is not a silver bullet

Slide 28

Slide 28 text

PROGUARD?

Slide 29

Slide 29 text

dependencies {
 
 }

Slide 30

Slide 30 text

$ dexcount ./app-debug.apk : 15
 android.app: 2
 java.lang: 2
 org.chalup.proguardtechtalk: 11

Slide 31

Slide 31 text

buildTypes {
 all {
 minifyEnabled true
 proguardFiles = [
 getDefaultProguardFile( ‘proguard-android.txt' ),
 'proguard-rules.pro'
 ]
 }
 }


Slide 32

Slide 32 text

$ dexcount ./app-debug.apk : 5
 android.app: 2
 org.chalup.proguardtechtalk: 3

Slide 33

Slide 33 text

dependencies {
 compile ‘google-play-services'
 }

Slide 34

Slide 34 text

$ dexcount ./app-debug.apk android: 1874
 com.google.android.gms: 6453
 java: 381
 org.chalup.proguardtechtalk: 3
 org.json: 13
 
 Total method count: 8726

Slide 35

Slide 35 text

buildTypes {
 all {
 minifyEnabled true
 proguardFiles = [
 getDefaultProguardFile( ‘proguard-android.txt' ),
 'proguard-rules.pro'
 ]
 }
 }


Slide 36

Slide 36 text

$ANDROID_HOME/tools/proguard/proguard-android.txt --keep class * implements android.os.Parcelable { - public static final android.os.Parcelable$Creator *; +-keepnames class * implements android.os.Parcelable { + public static final ** CREATOR;

Slide 37

Slide 37 text

$ANDROID_HOME/tools/proguard/proguard-android.txt --keep class * implements android.os.Parcelable { - public static final android.os.Parcelable$Creator *; +-keepnames class * implements android.os.Parcelable { + public static final ** CREATOR; “OK ProGuard, please keep all Parcelables”

Slide 38

Slide 38 text

$ANDROID_HOME/tools/proguard/proguard-android.txt --keep class * implements android.os.Parcelable { - public static final android.os.Parcelable$Creator *; +-keepnames class * implements android.os.Parcelable { + public static final ** CREATOR; “OK ProGuard, please keep all *USED* Parcelables”

Slide 39

Slide 39 text

$ANDROID_HOME/tools/proguard/proguard-android.txt --keep class * implements android.os.Parcelable { - public static final android.os.Parcelable$Creator *; +-keepnames class * implements android.os.Parcelable { + public static final ** CREATOR; http://proguard.sourceforge.net/manual/usage.html

Slide 40

Slide 40 text

buildTypes {
 all {
 minifyEnabled true
 proguardFiles ‘copypasta-rules.pro'
 }
 }


Slide 41

Slide 41 text

$ dexcount ./app-debug.apk : 5
 android.app: 2
 org.chalup.proguardtechtalk: 3

Slide 42

Slide 42 text

$ du -h ./app-debug.apk
 554K ./app-debug.apk

Slide 43

Slide 43 text

buildTypes {
 all {
 minifyEnabled true
 proguardFiles ‘copypasta-rules.pro'
 }
 }


Slide 44

Slide 44 text

buildTypes {
 all {
 minifyEnabled true shrinkResources true
 proguardFiles ‘copypasta-rules.pro'
 }
 }


Slide 45

Slide 45 text

$ du -h ./app-debug.apk
 228K ./app-debug.apk

Slide 46

Slide 46 text

$ du -h ./app-debug.apk
 228K ./app-debug.apk https://code.google.com/p/android/issues/detail?id=80872

Slide 47

Slide 47 text

TAKEAWAY #2 Tune getDefaultProguardFile

Slide 48

Slide 48 text

PROGUARD Y U NO PRUNE?

Slide 49

Slide 49 text

dependencies {
 compile ‘google-play-services’ compile 'appcompat-v7'
 }

Slide 50

Slide 50 text

$ dexcount ./app-debug.apk android: 3173
 java: 125
 org.chalup.proguardtechtalk: 3
 org.xmlpull.v1: 3
 
 Total method count: 3304

Slide 51

Slide 51 text

$ dexcount ./app-debug.apk android: 3173
 graphics: 53
 support: 2584
 v4: 652
 v7: 1932
 internal: 1298
 widget: 621
 view: 201
 widget: 162

Slide 52

Slide 52 text

$ cat build/ intermediates/ proguard-rules/ debug/ aapt_rules.txt 
 # view AndroidManifest.xml #generated:16
 -keep class org.chalup.proguardtechtalk.MainActivity { (...); }
 
 # view res/layout/abc_action_menu_item_layout.xml #generated:17
 -keep class android.support.v7.internal.view.menu.ActionMenuItemView { (...); }

Slide 53

Slide 53 text

TAKEAWAY #3 you can’t prune 100% of unused code in UI libs

Slide 54

Slide 54 text

PROGUARD Y U NO KEEP?

Slide 55

Slide 55 text

dependencies {
 compile ‘google-play-services’ compile ‘appcompat-v7' compile 'butterknife'
 }

Slide 56

Slide 56 text

public class MainActivity extends Activity {
 
 @InjectView(R.id.hello_text)
 TextView mTextView;
 
 @Override
 protected void onCreate(Bundle state) {
 super.onCreate(state);
 setContentView(R.layout.activity_main);
 
 ButterKnife.inject(this);
 mTextView.setText(R.string.hello_world);
 }
 }

Slide 57

Slide 57 text

AndroidRuntime D Shutting down VM E FATAL EXCEPTION: main E Process: org.chalup.proguardtechtalk, PID: 21061 E java.lang.RuntimeException: Unable to start activity ComponentInfo{…}: java.lang.NullPointerException: E at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2298) E at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2360) E at android.app.ActivityThread.access$800(ActivityThread.java:144) E at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1278) E at android.os.Handler.dispatchMessage(Handler.java:102) E at android.os.Looper.loop(Looper.java:135) E at android.app.ActivityThread.main(ActivityThread.java:5221) E at java.lang.reflect.Method.invoke(Native Method) E at java.lang.reflect.Method.invoke(Method.java:372) E at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:899) E at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:694) E Caused by: java.lang.NullPointerException: E at org.chalup.proguardtechtalk.MainActivity.onCreate(Unknown Source) E at android.app.Activity.performCreate(Activity.java:5933) E at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1105) E at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2251) E ... 10 more

Slide 58

Slide 58 text

+-dontwarn butterknife.internal.** +-keep class **$$ViewInjector { + *; +} +-keepnames class * { + @butterknife.InjectView *; +}

Slide 59

Slide 59 text

TAKEAWAY #4 @ + ProGuard = troubles

Slide 60

Slide 60 text

TAKEAWAY #4 @ + ProGuard = troubles https://github.com/krschultz/android-proguard-snippets

Slide 61

Slide 61 text

TAKEAWAY #4 @ + ProGuard = troubles https://bitbucket.org/littlerobots/squadleader

Slide 62

Slide 62 text

apply plugin: 'com.android.library'
 
 android {
 defaultConfig {
 consumerProguardFiles ‘…’
 }
 } https://bitbucket.org/littlerobots/squadleader

Slide 63

Slide 63 text

TAKEAWAY #5 Writing a library? Provide ProGuard config!

Slide 64

Slide 64 text

PROGUARD!

Slide 65

Slide 65 text

dependencies {
 compile ‘google-play-services’ compile ‘support.*’ compile ‘guava’ compile ‘gson’ compile ‘joda-time’ compile ‘square.*’ compile ‘butterknife’ compile ‘rxjava’ compile ‘rxandroid’ compile ‘android-arsenal.*’ }

Slide 66

Slide 66 text

$ g assembleDebug

Slide 67

Slide 67 text

$ g assembleDebug BUILD SUCCESSFUL 
 Total time: 16.096 secs


Slide 68

Slide 68 text

$ g assembleDebug BUILD SUCCESSFUL 
 Total time: 16.096 secs
 $ du -h ./app-debug.apk
 3.5M ./app-debug.apk

Slide 69

Slide 69 text

$ g assembleDebug BUILD SUCCESSFUL 
 Total time: 16.096 secs
 $ du -h ./app-debug.apk
 3.5M ./app-debug.apk $ dexcount ./app-debug.apk Total method count: 6636


Slide 70

Slide 70 text

$ g assembleDebug BUILD SUCCESSFUL 
 Total time: 48.784 secs
 $ du -h ./app-debug.apk
 7.5M ./app-debug.apk $ dexcount ./app-debug.apk android.support.v4: 81986


Slide 71

Slide 71 text

$ g assembleDebug BUILD SUCCESSFUL 
 Total time: 16.096 secs
 $ du -h ./app-debug.apk
 3.5M ./app-debug.apk $ dexcount ./app-debug.apk Total method count: 6636


Slide 72

Slide 72 text

- multiDexEnabled true

Slide 73

Slide 73 text

$ g assembleDebug BUILD SUCCESSFUL 
 Total time: 15.049 secs
 $ du -h ./app-debug.apk
 3.5M ./app-debug.apk $ dexcount ./app-debug.apk Total method count: 6539


Slide 74

Slide 74 text

TAKEAWAY #6 Good ProGuard config FTW!

Slide 75

Slide 75 text

GOTCHA #1

Slide 76

Slide 76 text

-dontobfuscate -dontoptimize $ g assembleDebug BUILD SUCCESSFUL

Slide 77

Slide 77 text

# -dontobfuscate -dontoptimize $ g assembleDebug BUILD SUCCESSFUL

Slide 78

Slide 78 text

-dontobfuscate $ g assembleDebug :app:dexDebug FAILED EXCEPTION FROM SIMULATION: local variable type mismatch: Ph'nglui mglw’nafh Cthulhu R'lyeh wgah'nagl fhtagn

Slide 79

Slide 79 text

-dontobfuscate -optimizations !code/allocation/variable

Slide 80

Slide 80 text

-dontobfuscate -optimizations !code/allocation/variable $ g assembleDebug BUILD SUCCESSFUL

Slide 81

Slide 81 text

http://stackoverflow.com/a/7587680/184953 -dontobfuscate -optimizations !code/allocation/variable $ g assembleDebug BUILD SUCCESSFUL

Slide 82

Slide 82 text

GOTCHA #2

Slide 83

Slide 83 text

LinkedHashMultimap map =
 LinkedHashMultimap.create(); 
 map.put(1, "1");
 map.put(1, "2");
 map.put(1, "3");
 
 Collection data = map.asMap().get(1);
 Log.d("JCH", new Gson().toJson(data));

Slide 84

Slide 84 text

E FATAL EXCEPTION: main E Process: org.chalup.proguardtechtalk, PID: 31243 E java.lang.reflect.GenericSignatureFormatError E at libcore.reflect.GenericSignatureParser.expect(GenericSignatureParser.java:473) E at libcore.reflect.GenericSignatureParser.parseClassTypeSignature(GenericSignatureParser.java:332) E at libcore.reflect.GenericSignatureParser.parseClassSignature(GenericSignatureParser.java:234) E at libcore.reflect.GenericSignatureParser.parseForClass(GenericSignatureParser.java:126) E at java.lang.Class.getGenericInterfaces(Class.java:1134) E at com.google.gson.internal.$Gson$Types.getGenericSupertype($Gson$Types.java:238) E at com.google.gson.internal.$Gson$Types.getSupertype($Gson$Types.java:269) E at com.google.gson.internal.$Gson$Types.getCollectionElementType($Gson$Types.java:288) E at com.google.gson.internal.bind.CollectionTypeAdapterFactory.create(CollectionTypeAdapterFactory.java:51) E at com.google.gson.Gson.getAdapter(Gson.java:359) E at com.google.gson.Gson.toJson(Gson.java:592) E at com.google.gson.Gson.toJson(Gson.java:579) E at com.google.gson.Gson.toJson(Gson.java:534) E at com.google.gson.Gson.toJson(Gson.java:514) E at org.chalup.proguardtechtalk.MainActivity.getString(MainActivity.java:40) E at org.chalup.proguardtechtalk.MainActivity.onCreate(MainActivity.java:36) E at android.app.Activity.performCreate(Activity.java:5933)

Slide 85

Slide 85 text

buildscript {
 repositories {
 jcenter()
 }
 dependencies {
 classpath 'com.android.tools.build:gradle:1.0.1'
 }
 }

Slide 86

Slide 86 text

buildscript {
 repositories {
 jcenter()
 }
 dependencies {
 classpath 'com.android.tools.build:gradle:1.0.1'
 }
 configurations.all {
 resolutionStrategy {
 force 'net.sf.proguard:proguard-gradle:4.11'
 }
 }
 }

Slide 87

Slide 87 text

buildscript {
 repositories {
 jcenter()
 }
 dependencies {
 classpath 'com.android.tools.build:gradle:1.0.1'
 }
 configurations.all {
 resolutionStrategy {
 force 'net.sf.proguard:proguard-gradle:4.11'
 }
 }
 } NOTE: this is top level build.gradle, not module one

Slide 88

Slide 88 text

GOTCHA #3

Slide 89

Slide 89 text

# Release ProGuard config -assumenosideeffects class android.util.Log {
 public static *** d(...);
 }


Slide 90

Slide 90 text

@Override
 protected void onCreate(Bundle state) {
 super.onCreate(state);
 setContentView(R.layout.activity_main);
 
 Log.d(TAG, doNotRunOnProduction());
 }
 
 private String doNotRunOnProduction() {
 Log.e(TAG, "FIRE ZE MISSILES!");
 
 return "Harmless log message";
 }


Slide 91

Slide 91 text

E FIRE ZE MISSILES! D Harmless log message $ g assembleDebug

Slide 92

Slide 92 text

@Override
 protected void onCreate(Bundle state) {
 super.onCreate(state);
 setContentView(R.layout.activity_main);
 
 Log.d(TAG, doNotRunOnProduction());
 }
 
 private String doNotRunOnProduction() {
 Log.e(TAG, "FIRE ZE MISSILES!");
 
 return "Harmless log message";
 }


Slide 93

Slide 93 text

E FIRE ZE MISSILES! $ g assembleRelease

Slide 94

Slide 94 text

@Override
 protected void onCreate(Bundle state) {
 super.onCreate(state);
 setContentView(R.layout.activity_main);
 
 Log.d(TAG, doNotRunOnProduction());
 }
 
 private String doNotRunOnProduction() {
 Log.e(TAG, "FIRE ZE MISSILES!");
 
 return "Harmless log message";
 }


Slide 95

Slide 95 text

@Override
 protected void onCreate(Bundle state) {
 super.onCreate(state);
 setContentView(R.layout.activity_main);
 String tmp = doNotRunOnProduction();
 Log.d(TAG, tmp);
 }
 
 private String doNotRunOnProduction() {
 Log.e(TAG, "FIRE ZE MISSILES!");
 
 return "Harmless log message";
 }


Slide 96

Slide 96 text

@Override
 protected void onCreate(Bundle state) {
 super.onCreate(state);
 setContentView(R.layout.activity_main);
 String tmp = doNotRunOnProduction();
 // Log.d(TAG, tmp); pruned by ProGuard
 }
 
 private String doNotRunOnProduction() {
 Log.e(TAG, "FIRE ZE MISSILES!");
 
 return "Harmless log message";
 }


Slide 97

Slide 97 text

http://chalup.github.io/blog/2013/08/20/proguard-gotcha/ E FIRE ZE MISSILES! $ g assembleRelease

Slide 98

Slide 98 text

TAKEAWAY #7 Beware of gotchas

Slide 99

Slide 99 text

RECAP

Slide 100

Slide 100 text

TAKEAWAY #1 multidex is not a silver bullet

Slide 101

Slide 101 text

TAKEAWAY #2 Tune getDefaultProguardFile

Slide 102

Slide 102 text

TAKEAWAY #3 you can’t prune 100% of unused code in UI libs

Slide 103

Slide 103 text

TAKEAWAY #4 @ + ProGuard = troubles

Slide 104

Slide 104 text

TAKEAWAY #5 Writing a library? Provide ProGuard config!

Slide 105

Slide 105 text

TAKEAWAY #6 Good ProGuard config FTW!

Slide 106

Slide 106 text

TAKEAWAY #7 Beware of gotchas

Slide 107

Slide 107 text

?

Slide 108

Slide 108 text

Thanks.