Optimizing Java Code

This doc describes how Java code is optimized in Chrome on Android and how to deal with issues caused by the optimizer. For tips on how to write optimized code, see //docs/speed/binary_size/optimization_advice.md#optimizing-java-code.

ProGuard vs R8

ProGuard is the original open-source tool used by many Android applications to perform whole-program bytecode optimization. R8, is a re-implementation that is used by Chrome (and the default for Android Studio). The terms “ProGuard” and “R8” are used interchangeably within Chromium but generally they're meant to refer to the tool providing Java code optimizations.

What does ProGuard do?

  1. Shrinking: ProGuard will remove unused code. This is especially useful when depending on third party libraries where only a few functions are used.

  2. Obfuscation: ProGuard will rename classes/fields/methods to use shorter names. Obfuscation is used for minification purposes only (not security).

  3. Optimization: ProGuard performs a series of optimizations to shrink code further through various approaches (ex. inlining, outlining, class merging, etc).

Build Process

ProGuard is enabled only for release builds of Chrome because it is a slow build step and breaks Java debugging. It can also be enabled manually via the GN arg: is_java_debug = false

ProGuard configuration files

Most GN Java targets can specify ProGuard configuration files by setting the proguard_configs variable. //base/android/proguard contains common flags shared by most Chrome applications.

GN build rules

When is_java_debug = false and a target has enabled ProGuard, the proguard step generates the .dex files for the application. The proguard step takes as input a list of .jar files, runs R8/ProGuard on those .jar files, and produces the final .dex file(s) that will be packaged into your .apk

Deobfuscation

Obfuscation can be turned off for local builds while leaving ProGuard enabled by setting enable_proguard_obfuscation = false in GN args.

There are two main methods for deobfuscating Java stack traces locally:

  1. Using APK wrapper scripts (stacks are automatically deobfuscated)
  • $OUT/bin/chrome_public_apk logcat # Run adb logcat
  • $OUT/bin/chrome_public_apk run # Launch chrome and run adb logcat
  1. Using java_deobfuscate
  • build/android/stacktrace/java_deobfuscate.py $OUT/apks/ChromePublic.apk.mapping < logcat.txt`
    • ProGuard mapping files are located beside APKs (ex. $OUT/apks/ChromePublic.apk and $OUT/apks/ChromePublic.apk.mapping)

Helpful links for deobfuscation:

Debugging common failures

ProGuard failures are often hard to debug. This section aims to outline some of the more common errors.

Classes expected to be discarded

The -checkdiscard directive can be used to ensure that certain items are removed by ProGuard. A common use of -checkdiscard it to ensure that ProGuard optimizations do not regress in their ability to remove code, such as code intended only for debug builds, or generated JNI classes that are meant to be zero-overhead abstractions. Annotating a class with @CheckDiscard will add a -checkdiscard rule automatically.

Item void org.chromium.base.library_loader.LibraryPrefetcherJni.<init>() was not discarded.
void org.chromium.base.library_loader.LibraryPrefetcherJni.<init>()
|- is invoked from:
|  void org.chromium.base.library_loader.LibraryPrefetcher.asyncPrefetchLibrariesToMemory()
... more code path lines
|- is referenced in keep rule:
|  obj/chrome/android/chrome_public_apk/chrome_public_apk.resources.proguard.txt:104:1

Error: Discard checks failed.

Things to check

  • Did you add code that is referenced by code path in the error message?
  • If so, check the original class for why the CheckDiscard was added originally and verify that the reason is still valid with your change (may need git blame to do this).
  • Try the extra debugging steps listed in the JNI section below.

JNI wrapper classes not discarded

Proxy native methods (@NativeMethods) use generated wrapper classes to provide access to native methods. We rely on ProGuard to fully optimize the generated code so that native methods aren‘t a source of binary size bloat. The above error message is an example when a JNI wrapper class wasn’t discarded (notice the name of the offending class).

  • The ProGuard rule pointed to in the error message isn't helpful (just tells us a code path that reaches the not-inlined class).
  • Common causes:
    • Caching the result of ClassNameJni.get() in a member variable.
    • Passing a native wrapper method reference instead of using a lambda (i.e. Jni.get()::methodName vs. () -> Jni.get.methodName()).
  • For more debugging info, add to base/android/proguard/chromium_code.flags:
    -whyareyounotinlining class org.chromium.base.library_loader.LibraryPrefetcherJni {
        <init>();
    }
    

Duplicate classes

Type YourClassName is defined multiple times: obj/jar1.jar:YourClassName.class, obj/jar2.jar:YourClassName.class

Common causes:

  • Multiple targets with overlapping srcjar_deps:
    • Each .srcjar can only be depended on by a single Java target in any given APK target. srcjar_deps are just a convenient way to depend on generated files and should be treated like source files rather than deps.
    • Solution: Wrap the srcjar in an android_library target or have only a single Java target depend on the srcjar and have other targets depend on the containing Java target instead.
  • Accidentally enabling APK level generated files for multiple targets that share generated code (ex. Trichrome or App Bundles):
    • Solution: Make sure the generated file is only added once.

Debugging ProGuard failures isn't easy, so please message java@chromium.org or file a bug with component=Build os=Android for any issues related to Java code optimization.