Code shrinking is an strategy that permits us to generate smaller APKs by eradicating unused code or refactoring present code, leading to a smaller footprint. Along with shrinking, obfuscating is one other tactic that permits us to protect our Android apps in opposition to reverse engineering.
Utilizing each of those methods will be sure that your app is quicker to obtain and harder to change by others.
On this put up, we’ll cowl:
R8 vs. Proguard
Within the early variations of Android, code shrinking and optimization had been delegated to a device known as Proguard. Nonetheless, since Android Gradle Plugin (AGP) v 3.4.0, Android has used the R8 compiler.
Whereas each instruments assist with code compaction, R8 has richer performance than code shrinking. For starters, R8 has restricted assist for Kotlin, whereas Proguard was constructed for Java toolchains. R8 achieves higher inlining and outlining (extracting frequent code right into a operate) than Proguard, whereas the latter is healthier at propagating fixed arguments.
Talking of the particular code compaction course of, R8 performs higher by reaching 10 % compaction, versus 8.5 % for Proguard.
Phases in R8
The R8 compiler does numerous issues to cut back the dimensions of your ultimate APK. A few of these embrace:
- Desugaring: This enables us to make use of Java 8 and above API options with out worrying about assist, the R8 compiler handles back-porting newer options utilized in your code to older java APIs.
- Code shrinking: That is the stage the place R8 removes unused code out of your app, together with unused code in library dependencies
- Useful resource shrinking: As soon as it’s performed shrinking code, R8 identifies sources which might be unused and eliminates unused strings, drawables, and so forth.
- Obfuscation: At this stage, R8 ensures your courses and their fields are renamed and probably repackaged as effectively with a purpose to shield it from reverse engineering. This course of generates a mapping file, which can be utilized to reobtain the precise entity names if wanted
- Optimizing code: Throughout code optimization, R8 appears to be like to cut back your app footprint and/or enhance effectivity additional, by eradicating branches of your code that aren’t reachable (versus courses/recordsdata). It makes use of superior optimization guidelines, like inlining a way on the name website when it was solely known as from one place
- Different methods embrace vertical class merging, the place, if an interface has just one implementation, it merges each of them underneath a single class
As soon as all the above steps are accomplished, R8 converts the bytecode into dexcode by a course of known as dexing. Earlier, this was part of D8 compiler, however has now been built-in into the R8 compiler.
Now that we all know a bit concerning the R8 compiler, let’s see how code shrinking really works.
Configuring code shrinking
In Android, we will configure code shrinking by setting the minifyEnabled
flag as true
in your construct.gradle
file. Optionally, you might also allow shrinkResources
to take away unneeded sources.
buildTypes{ launch{ minifyEnabled true shrinkResources true proguardFiles getDefaultProguardFile('proguard-android-optimize.txt '), 'proguard-rules.professional' } }
Code shrinking begins by inspecting what are known as entry factors. Entry factors are declared in a config file and made out there through the proguardFiles
parameter within the construct.gradle
.
As soon as R8 has a set of entry factors, it begins trying to find all courses and entities which might be reachable from these entry factors. It proceeds to construct a listing of such tokens. Any token that isn’t reachable is stripped from the ultimate output.
This course of is usually not foolproof as a result of:
- A few of our code might use reflection to lookup courses, which makes it tough for the compiler to know whether or not a specific class is used or not
- Your app might name a way from the native aspect through JNI. Since R8 is designed to work with Kotlin/Java code fairly than native, we have to direct it to maintain these courses
Many of those entry factors are outlined within the proguard-android-optimize.txt
file made out there through the AGP plugin. Right here’s a partial snapshot of what it appears to be like like:
Let’s go over what the 2 guidelines above imply:
- Retains all features that getters and setters current in courses that stretch the
View
, thus retaining theView
courses as effectively - Retain all features of Actions that match the signature of receiving a single
View
parameter, specifically click on listeners used within the XML, that are appeared up reflectively
Subsequent, let’s get to know the schema that powers R8.
Understanding the Proguard rule schema
Although we’ll be discussing these as Proguard guidelines, they’re the identical guidelines that configure R8 as effectively. Let’s take deeper dive into write them.
A typical R8 or Proguard rule consists of three sections:
- A hold possibility: A hold possibility defines “whom” to retain for, as follows:
hold
ensures we retain the goal that matches the rulekeepclass
ensures we retain the category that matches the rulekeepclasswithmembers
retains courses whose members match the rule- Equally, we’ve
keepclassmembers
to retain solely the members of a category
- A token sort: This denotes the kind of goal entity of our rule, i.e.,
class
,enum
orinterface
- Wild playing cards: These permit us to outline completely different codecs to match completely different tokens, as follows:
?
: Matches a single character in a reputation. So, for a rule likehold class T???Supplier
, we should guarantee we retain each theTaskProvider
andTrapProvider
courses*
: Matches any a part of a reputation excluding the bundle separator. This ensures a rule likehold class com.demo.*Supplier extends ActionProvider
matchescom.demo.TaskProvider
, however doesn’t matchcom.demo.inside.StorageProvider
**
: Matches any a part of a reputation together with the bundle separator. Within the above instance, it might even match theStorageProvider
class<n>
: Permits us to match dynamic parts inside our rule. For instance, if we want to have courses that match the next template:class TaskProvider { enjoyable getTaskKey(): String } class StorageProvider { enjoyable getStorageKey(): String }
we will write:
-keepclasseswithmembers class *Supplier { public java.lang.String get<1>Key(); }
It’s because our first wildcard matcher, *
, matches Process
and Storage
, which we will reuse to outline the dynamic components of our operate’s identify.
Writing your individual R8 guidelines
The R8 or Proguard guidelines shipped through AGP are usually adequate, nevertheless, a necessity might come up to write down your individual guidelines. Whereas writing R8 guidelines, we should always try to keep away from together with greater than what’s wanted in our hold guidelines to make sure that we will compress most of our code. Additionally, all courses we specify have to be absolutely certified, i.e., they have to embrace the bundle identify.
Sometimes, enums utilized in XML recordsdata are the culprits stripped away by R8. However we will outline our personal rule to maintain them, as follows:
-keep enum com.demo.primary.MediaType{ *; }
Observe: The
{*;}
within the braces implies that we intend to protect all members of the category/enum.
One other rule is to protect class constructors; right here’s the way you’d do it utilizing the key phrase init
:
-keep public class * extends android.view.View { public <init>(android.content material.Context); }
Sometimes, there could also be entities included in your app which might be reflectively appeared up through their absolutely certified identify inside jars. You’d need to protect solely the names and forestall R8 from obfuscating or renaming the category. You’ll be able to retain names by utilizing the keepnames
qualifier:
-keepnames class com.ext.library.ServiceProvider
One other option to retain courses is to annotate them with the @Maintain
annotation. These courses are retained through the androidx.annotation
library Proguard rule. Nonetheless, you possibly can solely use this on supply code you management; moreover, this can be a extra generic resolution and can consequence within the inclusion of members that aren’t used.
Useful resource shrinking
Useful resource shrinking is often performed after code shrinking, however as a substitute of utilizing Proguard guidelines, we will specify useful resource retention utilizing a hold.xml
in our res/uncooked
folder. We usually don’t want this except we’re searching for sources through Sources.getIdentifier()
.
In such instances, the useful resource shrinker behaves conservatively. Beneath is an instance:
val identify = String.format("ic_percent1d", angle + 1) val res = sources.getIdentifier(identify, "drawable", packageName)
The shrinker makes use of sample matching and retains all property, beginning with ic_
. We are able to additionally retain some property explicitly in our hold.xml
, as follows:
<?xml model="1.0" encoding="utf-8"?> <sources xmlns:instruments="http://schemas.android.com/instruments" instruments:hold="@drawable/ic_sport*, @drawable/ic_banner_option, @format/item_header" instruments:discard="@drawable/wip" />
Observe: The
discard
possibility ensures thewip
is faraway from the ultimate construct if unused.
Debugging R8 errors
Sometimes whereas utilizing R8, you’ll find yourself with a lacking useful resource error within the type of ClassNotFoundException
or FieldNotFoundException
. Nonetheless, because the hint is obfuscated, we’ll want to make use of a device known as retrace.
Retrace is often current on the next path: Android/sdk/instruments/proguard/bin
. You might optionally use the GUI-based route by utilizing the proguardgui.sh command, as proven beneath:
When you’ve discovered which class or member is inflicting this challenge, you possibly can simply repair this by together with a selected hold rule for it:
-keep class com.demo.actions.MainActivity
R8 usually strips meta properties like line numbers and supply file names. We are able to retain this info by utilizing keepattributes
, as proven within the beneath rule:
-keepattributes SourceFile, LineNumberTable
You could find the whole listing of attributes right here.
Sometimes, you may even see {that a} member that was alleged to be faraway from the ultimate APK has not really been eliminated. We are able to work out why by utilizing whyareyoukeeping
:
-whyareyoukeeping class com.android.AndroidApplication
This can print the beneath output:
com.android.AndroidApplication |- is referenced in hold rule: | /Customers/anvith/Improvement/Android/project-demo/app/construct/intermediates/aapt_proguard_file/launch/aapt_rules.txt:3:1
One other useful gizmo whereas debugging is to listing all of the unused courses. This may be performed utilizing printusage
, as follows:
-printusage
A fast observe about R8 guidelines: Probably the most broad guidelines take priority. So, if
libraryA
ships with a rule to incorporate one methodology of a category, and `libraryB` is shipped with a rule to incorporate all members,libraryB
’s rule takes priority.
Lastly, should you want to see courses matched by your guidelines, you could use the next command to look at the matched outcomes:
-printseeds
Aggressive shrinking choices
We are able to instruct R8 to be extra aggressive by letting it run in non-compat mode and declaring the next property within the gradle.properties
file:
android.enableR8.fullMode=true
This flag ends in a number of the extra rigorous optimizations, like:
- Keep away from retaining the default constructor except specified explicitly
- Attributes (like
Signature
,Annotations
, and so forth.) are solely retained for matching courses, even when we specify the generickeepattributes
for all entities
Just like the code shrinking possibility, there’s an aggressive useful resource shrink mode that may be added to the hold.xml
:
<?xml model="1.0" encoding="utf-8"?> <sources xmlns:instruments="http://schemas.android.com/instruments" instruments:shrinkMode="strict" />
Conclusion
On this article, we discovered about R8 and configure guidelines for it. Within the course of, we additionally lined numerous debugging choices to handle the perils of aggressive shrinking.
I hope you’ve discovered the data on this article helpful for addressing your code shrinking issues and are able to leverage the R8 toolchain!
LogRocket: Immediately recreate points in your Android apps.
LogRocket is an Android monitoring resolution that helps you reproduce points immediately, prioritize bugs, and perceive efficiency in your Android apps.
LogRocket additionally helps you enhance conversion charges and product utilization by exhibiting you precisely how customers are interacting along with your app. LogRocket’s product analytics options floor the explanation why customers do not full a specific move or do not undertake a brand new function.
Begin proactively monitoring your Android apps — attempt LogRocket totally free.