background
In AGP 4.2, all modules can use non-passable R files. Non-transitive R files make your builds faster and faster, and your AAB/APK smaller.Copy the code
If A depends on B and B depends on C, A doesn't know C. In the Android world, non-passed R files:Copy the code
Non-pass-through R classes enable the namespace of each library's R class so that its R class contains only resources declared in the library itself and not resources in the library's dependencies, thereby reducing the size of that library's R class.Copy the code
The past and future of Android class R
Every Android application contains resources such as localized strings, ICONS, screen layouts, or navigation targets. Each native Android application uses the Android retrieval mechanism based on the resource ids listed in the R class to access these resources.Copy the code
What is class R?
Android application resources are located in a directory structure under the res root (not to be confused with the resource directory for Java applications), but in a different format. ICONS and layouts are placed in separate files, but strings may (or may not) all be stored in a single file, and navigation targets are part of the richer content of the navigation diagram.Copy the code
However, all application resources have a unique resource ID, which is generated by the AAPT tool at compile time. All resource ids are listed in Java classes with simple names -r. For each resource type, there is a nested class with the resource type name (for example, r.string for a string resource), and for each resource of that type, a static integer (for example, r.string.app_name).Copy the code
This resource retrieval mechanism is heavily used in many parts of the Android SDK and has been optimized for performance. Unfortunately, it's not optimized for developer well-being and safety. Since all resource ids are of type Int, string resource ids can easily be used to retrieve ICONS that won't turn out very well.Copy the code
The origin of
The principles of resource retrieval have remained the same since Android was first publicly released in 2008. Before compiling the application code, you must find all the resources in the RES directory, then generate the R class source code and merge it with the rest of the application source code. When done, the application code can refer to the resource ID of class R just like any other code.Copy the code
At that time, the Android Development Tools (ADT) Plugin was responsible for launching AAPT, generating r.Java source files, merging them with other application sources, and compiling them together.Copy the code
The first version of APPT generated all resource ids in class R as constants (public static Final Int in Java). This can seem very detrimental to build performance for modular projects with multiple library modules. The actual values of resource ids in these modules can conflict, so all library modules must be recompiled more frequently. Thus, starting with ADT version 14, the R classes of library modules generate their fields as public static int (non-final), but leaf application modules keep their fields final because no other modules depend on them.Copy the code
This variation was a big problem at the time because Java switch statements required compile-time constants and could not use library module resource ids as statement values. But for Kotlin's when expression, this is no longer a problem.Copy the code
Gradle era
At Google I/O 2014, Android Studio with a Gradle-based build system was announced as an alternative to ADT and Ant based builds. But because class R code generation is done by the AAPT tool, the way resource ids are generated and used hasn't changed much.Copy the code
Elephants are building
As the project grew, so did their R category. Around 2017, some larger development teams realized that the R class was growing much faster. Elin Nilsson mentioned in her presentation on application modularity that out of their 1.2 million LoC code base, the generated R classes add up to 56 million LOCs that need to be compiled and indexed with each clean build.Copy the code
The main reason classes R are so large is that they contain duplicates. Classes R are generated for each module you build, and the module-specific R class includes references to all the resources that it passes dependencies to.Copy the code
In this example, the MDC library contains com. Google. Android. Material. The R class, which contains the reference of material resources.Copy the code
Our :lib module relies on the MDC library and contains few other resources. It also has its own com.example.myapp.lib.r class, which lists references to its own resources and passing references to MDC library resources.Copy the code
Finally, the :app module has its own com.example.myapp.R class, and again lists the :lib module and MDC library reference ids.Copy the code
The Material Components library resource ID is generated 3 times! This is the story of how a small modular application implements millions of LOCs in the R class.Copy the code
The rescue
Android Gradle plugin 3.3 (January 2019) introduces an inadequate logging flag that you can enable in gradle.properties: Android.namespacedrClass =trueCopy the code
It supports non-transitive R-class namespaces, where each library contains only references to its own resources and does not extract references from dependencies. This has a significant impact on the size of the R class, which leads to faster builds.Copy the code
It allows modules to be better isolated from an architectural perspective. Unless an R class is explicitly imported from another module, that module can only use its own resources. It prevents a module from accidentally using a drawable or string from another module.Copy the code
Unfortunately, Android Studio doesn't realize this, so it won't stop you from making the wrong reference, but at least it will fail at compile time.Copy the code
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 great if AGP could generate Java bytecode directly? Another gradle. Properties achieved this dream signs: android. EnableSeparateRClassCompilation = trueCopy the code
This will enable AGP to generate R classes as compiled JAR files instead of source code and automatically merge them with the rest of the project. Technically, the build process will generate an R.jar file in the Build /intermediates directory, not the R.Java file in the Build /generated directory.Copy the code
In Android Gradle plug-in version 3.4, you can use in Gradle. The properties file add the following line to select check whether your project declares the acceptable package name: Android. UniquePackageNames = trueCopy the code
There were no details of this requirement at the time, but it seems like a good thing that a package name is only used in one project module, so why not enable this check now and have no migration issues in the future, right?Copy the code
Android Gradle plugin 3.6 (February 2020) provides an answer to the requirement to provide a unique package name for each module. Starting with this version, AGP simplifies compilation classpath by accelerating compilation by generating only one R class per module.Copy the code
AGP 3.6 also make android. EnableSeparateRClassCompilation sign enabled by default and cannot be disabled.Copy the code
On the other hand, the introduction of the new android. EnableAppCompileTimeRClass experimental marks. It is limited to Android application modules. Before the compilation phase of an application module begins, all R classes for all other modules need to be regenerated to create the final set of unique resource ids that will be used in the application module at run time. This new experimental flag addresses this limitation by pre-generating a fake application module R class and then updating it with the real resource ID value. One limitation of this approach is that the resource ids of application modules are no longer final (as in library modules), so they cannot be used in Java switch statements or as comment parameters.Copy the code
Android Gradle plugin 4.1 (release candidate in October 2020) takes a step further by making the R class namespace enabled by default, as it converts the flag from: android.namespacedrClass =true to: android.nonTransitiveRClass=trueCopy the code
Application modules have similar signs: android. Experimental. NonTransitiveAppRClass = trueCopy the code
But according to the comments in the AGP source code, this seems to be only temporary and will be removed in the future, so don't bother now.Copy the code
Nontransitive module
If module A depends on B, B depends on C. How do we reference resources? Let's take a look:Copy the code
Module A can refer to its own resource (as normal) : r.string.hello_worldCopy the code
Modules can reference module B resources (fully qualified package) : com. My. ModuleB. R.s. Tring hello_neighbourCopy the code
Module A cannot reference module C's resources.Copy the code
Non-transitive R classes with Android Gradle plug-ins (>4.2) can be used to build faster builds for applications with multiple modules. . This leads to more up-date builds and compilation avoidance benefits.Copy the code
Benefits of non-transitive R classes?
Reducing the AAB/APK size, including the DEX field reference count, reduces incremental build speed because fewer dependencies can be included when making changes. Modularity increases, dependencies become more explicit and complexity is reduced, and resources cannot be derived from passing dependencies as the code contained decreases, and the overall build duration decreasesCopy the code
Can edit/gradle. The properties file to open the Settings for oneself, to include: android. NonTransitiveRClass = trueCopy the code
This automatic refactoring is not a panacea, it may cause some resource reference errors, i.e. it will add the wrong package name before the R class, or it won't be able to select one at all, or it will add a dependency that your module doesn't have. In my experience, these issues are discovered at build time and you can choose from the following three solution steps to complete the refactoring tool startup:Copy the code
When you're done, you'll have to build the project and fix the bugs. Errors can take several forms:Copy the code
Use resources from another module. Fix: Add fully qualified packages, or import R files, or (Kotlin only) use aliases.Copy the code
// Fully qualifed package\ val foo = com.my.moduleB.R.string.hello_neighbour\ // Import then use string.hello_neighbour\ import com.my.moduleB.R\ // Alias then use RB.string.hello_neighbour\ import com.my.moduleB.R as RBCopy the code
Use resources from another module that you do not declare as a dependency on. Fix: Do as #1, but also add dependencies in Gradle.Copy the code
implementation project(":libraries:moduleB")
Copy the code
Use resources from another module, but you don't declare a dependency on that module, and you don't want to declare a dependency. Fix: The solution here is to generate the reference and use the same name, or copy/create the new resource you want.Copy the code