The past and the future of Android R class
9/15/2020
The past and the future of Android R class

​​​Every Android application contains some resources like localized strings, icons, screen layouts, or navigation targets. And every native Android application accesses these resources using Android’s retrieval mechanism based on resource IDs listed in R class. Let’s deep dive into the world of almighty R to see whether there are any gotchas or possible improvements. Spoiler alert: There are!

What is a R class?

Android application resources are placed in a directory structure under res root (do not confuse that with Java application’s resources directory) but may differ in a format. Icons and layouts are placed in their individual files, but strings might (or might not) be stored all in a single file and navigation targets are part of much richer content of navigation graphs.

Nevertheless, all application resources have an unique resource ID generated by aapt tool during compilation. All resource IDs are listed in a Java class with a simple name—R. For each resource type, there is a nested class with the resource type name (e.g., R.string for string resources), and for each resource of that type, there is a static integer (e.g., R.string.app_name).

This resource retrieval mechanism is heavily used in many parts of Android SDK and is optimized for performance. Unfortunately, it is not optimized for developer happiness and safety. As all resource IDs are of type Int, one can easily use a string resource ID to retrieve an icon which won’t end up well.

Let’s focus now on how the R class is generated and used by the rest of the application code.

The origins

Since the first public Android release in 2008, the principle of resource retrieval has stayed the same. Before the application code gets compiled, all resources under the res directory must be found, the R class source code is then  generated and merged with the rest of application source code. Once done, the application code can reference resources IDs of R class like any other code.

At that time, it was the responsibility of Android Development Tools (ADT) Plugin to initiate aapt, generate the R.java source file, merge it with other application source files and compile them together.

   public final class R {
    public static final class string {
        public static final int cancel = 17039360;
        public static final int copy = 17039361;
        public static final int cut = 17039363;
        public static final int no = 17039369;
        public static final int ok = 17039370;
        public static final int paste = 17039371;
        public static final int yes = 17039379;
    }

    public static final class layout {
        public static final int activity_list_item = 17367040;
        public static final int list_content = 17367060;
        public static final int preference_category = 17367042;
        public static final int select_dialog_item = 17367057;
    }

    public static final class drawable {
        public static final int btn_default = 17301508;
        public static final int btn_dialog = 17301527;
        public static final int btn_dropdown = 17301510;
        public static final int btn_minus = 17301511;
        public static final int btn_plus = 17301512;
        public static final int checkbox_off_background = 17301519;
        public static final int checkbox_on_background = 17301520;
        public static final int divider_horizontal_bright = 17301522;
    }
}

The first versions of appt were generating all resource IDs in R class as constants (public static final int in Java). This appears to be very bad for build performance of modularized projects that have multiple library modules. Actual values of resource IDs in these modules might collide and as a result all library modules had to be recompiled more frequently. So from ADT version 14 the library module’s R classes have their fields generated as public static int (non-final), but the leaf application modules kept their fields as final since no other modules depend on them.

This change was quite an issue at that time since Java’s switch statements require compile time constants and one cannot use a library module resource ID as a statement value. But with Kotlin’s when expression this isn’t an issue anymore.

Gradle times

At Google I/O 2014, Android Studio with Gradle-based build system was announced as a replacement for ADT and Ant-based build. But since R class code generation is done by the aapt tool, not much has changed in the way resource IDs are generated and consumed.

Elephant in the build

As projects grow, their R classes grow too. It was around 2017 when some bigger development teams realized that the R classes grow much faster. Elin Nilsson in her talk about app modularization mentioned that in their 1,2 million LoC codebase the generated R classes together would sum up to 56 million LoC that need to be compiled and dexed on every clean build.

Wait, what? Do I actually have millions of unique resources in my modularized app? Of course not! The main reason why the R classes are so huge is that they contain duplicates. R classes are generated for every module of your build and the module specific R classes include references to all resources of its transitive dependencies.

R classes in modules

In this example, the MDC library contains com.google.android.material.R class with references to material resources.

Our :lib module depends on the MDC library and contains few other resources. It also has its own com.example.myapp.lib.R class where references to its own resources and transitive references to resources of MDC library are listed.

Finally, the :app module has its own generated com.example.myapp.R class and lists both :lib module and MDC library reference IDs again.

Material Components library resources IDs are generated three times! And this is the story of how a small modularized application can achieve few millions LoC in R classes.

The rescue

The Android Developer Tools team noticed this problem and rolled out a few tweaks that can help to tame this monster. Let’s go through the list in the order in which they have been released.

Android Gradle Plugin 3.3 (January 2019) introduces a not very well documented flag you can enable in gradle.properties:

   android.namespacedRClass=true

It enables non-transitive R class namespacing where each library only contains references to its own resources without pulling references from dependencies. This has a huge effect on R classes size which leads to much faster builds.

As a bonus, it makes the module better isolated from an architectural point of view. Unless there is an explicit import for the R class from another module, the module can use only its own resources. It prevents the module from accidentally using a drawable or a string from another module.

Unfortunately, Android Studio isn't aware of this so it won't prevent you from making incorrect references, but at least it will fail at compile time.

This is all great (actually more than that!), but at the end of the day, the R class is just a list of Ints known at compile time and you still need to compile it. Wouldn't it be nice if AGP could generate Java bytecode directly? This dream comes true with another gradle.properties flag:

   android.enableSeparateRClassCompilation=true

This will make AGP to generate the R classes as compiled jar files instead of source code and merge them with the rest of the project automatically. Technically, instead of R.java file in build/generated directory, the build process will generate R.jar file in build/intermediates directory.

This won’t speed up your build in the order of magnitudes, but it’s still better than nothing. And of course, you can use any of these flags separately if it is too hard for you to enable them together at once.

Android Gradle Plugin 3.4 (April 2019) doesn’t give us much but there is an interesting paragraph in the release notes:

The correct usage of unique package names is currently not enforced but will become more strict on later versions of the plugin. On Android Gradle plugin version 3.4, you can opt-in to check whether your project declares acceptable package names by adding the line below to your gradle.properties file:

   android.uniquePackageNames=true

There were no details for this requirement at that time, but having one package name used only in one project module seems a good thing anyway, so why not enable this check right now and have no migration issues in the future, right?

Android Gradle Plugin 3.6 (February 2020) gives the answer to the requirement for a unique package name per module. Starting with this version, AGP simplifies the compile classpath by generating only one R class for each module to speed up the build.

AGP 3.6 also makes the android.enableSeparateRClassCompilation flag enabled by default and it cannot be disabled anymore.

On the other hand, a new android.enableAppCompileTimeRClass experimental flag was introduced. It is limited to Android application modules only. Before a compilation phase of an application module can begin, all R classes from all other modules need to be re-generated to create a final set of unique resource IDs that will be used in the application module at runtime. This new experimental flag solves this limitation by generating a fake application module R class in advance while updating it with real resource ID values afterwards. One limitation of this approach is that the application modules resource IDs cannot be final anymore (same as in library modules), therefore they cannot be used in Java’s switch statements or as annotation parameters.

Android Gradle Plugin 4.1 (release candidate in October 2020) makes another step to make R class namespacing enabled by default as it renames the flag from

   android.namespacedRClass=true

to

   android.nonTransitiveRClass=true

There is also a similar flag for app modules

   android.experimental.nonTransitiveAppRClass=true

but according to the comments in AGP source code this seems to be just temporary and will be removed in the future so you don’t have to bother now.

Conclusion

Access to app resources through R class has a long history full of slow builds.

If you are a conservative developer, just try to stay with the current stable version of Android Gradle Plugin and things will get better over time. Sometimes you might be surprised with a new requirement or package limitation and the list of changes above might help you to overcome these.

If you are more progressive, just try the latest RC’s or betas and definitely try to enable android.nonTransitiveRClass and android.enableAppCompileTimeRClass flags. It is a game changer and my guess is that it is the future for all of us anyway.


Pavel Švéda

Twitter: @xsveda​​​

Tags

#android; #gradle

Author

Pavel Švéda

Versions

Android Gradle Plugin 4.1.0-rc03