Chapters

Hide chapters

Real-World Android by Tutorials

First Edition · Android 10 · Kotlin 1.4 · AS 4

Section I: Developing Real World Apps

Section 1: 7 chapters
Show chapters Hide chapters

20. Release Optimizations
Written by Kolin Stürt

Heads up... You're reading this book for free, with parts of this chapter shown beyond this point as scrambled text.

App development today favors small apps rather than large ones. This supports popular trends, like entry-level devices and the “internet of things”. Furthermore, smaller apps download, install and run faster, which is important for your business. This chapter will help you keep your apps as small as possible.

In this chapter, you’ll learn how to prepare a build for release. You’ll learn about the optimizations that ProGuard performs and how to translate to a certain level of obfuscation. This adds a minimal layer of security to help prevent reverse engineering or tampering with your app.

In the process, you’ll learn:

  • How to use APK Analyzer.
  • How to leverage optimization rules.
  • How to fix compile and runtime errors.

Using APK Analyzer

APK Analyzer is a tool that inspects your finalized app and determines what contributes to its size. It presents a breakdown of your app’s files. You can see what takes up the most space, along with the total method and reference counts.

Launch the analyzer by selecting Build ▸ Analyze APK, which opens a dialog for your file system. If it isn’t already selected, navigate to your debug folder and select app-debug.apk. Click OK to open APK Analyzer.

Figure 20.1 — Using APK Analyzer
Figure 20.1 — Using APK Analyzer

Note the file size of the current APK. You’ll use this tool again later in the chapter to see the result of your changes.

Enabling an optimizer

Next, you’ll use an optimizer to evaluate your app size.

buildTypes {
  release {
    minifyEnabled true
    proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
  }
  debug {
    minifyEnabled true
    proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
  }
}

ProGuard versus R8

Android Studio comes with two main optimizers: ProGuard and R8. ProGuard has been the de facto for Android for a long time, while R8 is a more recent addition. They’re compatible with each other and perform similar operations to optimize Java bytecode. Both remove unused code, such as methods, fields and classes, and attempt to optimize code for performance.

android.enableR8=false
android.enableR8.libraries=false

Fixing compilation errors

As optimizers do their work, they often mistakenly obfuscate and remove code that they think you’re not using — even when you are. Therefore, as you go along, you’ll need to test that everything still works with ProGuard enabled. The earlier you find problems in the build, the easier it will be to fix them. :]

Figure 20.2 — Compilation Errors
Powumo 45.6 — Kabnakuguex Iwdexv

Adding “don’t warn” rules

Don’t warn rules tell Android Studio to ignore warnings. This is dangerous, but if you know for sure that you’re not using part of the code, it can come in handy.

-dontwarn javax.xml.stream.**

Solving the okhttp errors

When you’re faced with unknown errors, the first step to solve them is to research them online. Popular libraries often publish ProGuard/R8 rules on their sites, so you’ll start your research there.

# JSR 305 annotations are for embedding nullability information.
-dontwarn javax.annotation.**

# A resource is loaded with a relative path so the package of this class must be preserved.
-keepnames class okhttp3.internal.publicsuffix.PublicSuffixDatabase

# Animal Sniffer compileOnly dependency to ensure APIs are compatible with older versions of Java.
-dontwarn org.codehaus.mojo.animal_sniffer.*
-dontwarn okio.**

# OkHttp platform used only on JVM and when Conscrypt dependency is available.
-dontwarn okhttp3.**
-dontwarn org.conscrypt.ConscryptHostnameVerifier

Solving the sl4j error

Head to the Bubble Picker library’s GitHub page at https://github.com/igalata/Bubble-Picker to see if there’s any documentation about using the library with ProGuard. In the previous cases, the README page had ProGuard information, but this library doesn’t.

-dontwarn org.slf4j.**

Adding keep rules

Keep rules tell ProGuard not to obfuscate certain parts of your code. Some options are:

-keep class kotlin.reflect.jvm.internal.** { *; }
-keep class kotlin.Metadata { *; }
-dontwarn com.google.crypto.tink.**
-dontwarn org.kobjects.**
-dontwarn org.ksoap2.**
-dontwarn org.kxml2.**
-dontwarn org.xmlpull.v1.**

-keep class org.kobjects.** { *; }
-keep class org.ksoap2.** { *; }
-keep class org.kxml2.** { *; }
-keep class org.xmlpull.** { *; }
-keepnames class okhttp3.internal.publicsuffix.PublicSuffixDatabase
-keepclassmembers class * extends com.google.crypto.tink.shaded.protobuf.GeneratedMessageLite {
  <fields>;
}
Figure 20.3 — Build Successful
Selejo 31.7 — Teafk Sabwazfnoy

Figure 20.4 — The APK Is Smaller Now
Hucaha 03.4 — Yxi IKD Id Wgabsix Jul

Fixing runtime errors

Build and run the app. Uh, oh — the app crashes with a ClassNotFoundException!

Figure 20.5 — Crash at Runtime
Yoluzo 32.1 — Fpekj ep Kixname

Adding annotations

The Annotations Support Library lets you add @Keep to methods and classes you want to preserve. This is a great feature because it acts like documentation. The ProGuard information sits above your method, as opposed to being in a separate file. Adding @Keep to a class will preserve the entire class. Adding @Keep to a method or field will keep the name of the method or field as-is.

@Keep
@Root(name = "user", strict = false)
...
@Keep
@Root(name = "users", strict = false)
...

Enabling more optimizations

At this point, you’ve successfully applied optimizations for your app. However, there are a few more steps you can take for your release version.

proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'),
          'proguard-rules.pro'
implementation ('com.squareup.retrofit2:converter-simplexml:2.7.1') {
  exclude group: 'xpp3', module: 'xpp3'
  exclude group: 'stax', module: 'stax-api'
  exclude group: 'stax', module: 'stax'
}

Shrinking resources

As long as you’ve set minifyEnabled in the optimizer, you can enable the resource shrinker, which removes unused resources after the code shrinker does its job. It will also remove resources in libraries that you include. To make sure it knows which resources your app uses, remove unused library code to make the resources in the library unreferenced.

    buildTypes {
        release {
            ...
            shrinkResources true
            crunchPngs true
            ...

NDK optimizations

If you’ve been working with NDK, you’ll have an Android.mk file under the project’s jni directory. This file tells the compiler how it should optimize native code. Changing the option is as simple as appending a line in the file, as follows:

LOCAL_CFLAGS  := -O3 

A few things to keep in mind…

The makers of ProGuard, GuardSquare, also have a commercial solution called DexGuard. It minimizes code, but offers more protection regarding its side effect of obfuscation. DexGuard encrypts the classes and strings as well as assets and resource files. It also provides app and device integrity checking, which is important to keep spammers out of your app.

Key points

  • You can choose between ProGuard and R8 in gradle.properties.
  • Don’t warn rules ignore warnings and errors.
  • Keep rules allow you to keep the optimizer from touching specific code.
  • Instead of keeping entire classes or large parts of code,keep only the minimum code you need, giving you better optimizations.
Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2024 Kodeco Inc.

You're reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a Kodeco Personal Plan.

Unlock now