[AAR to DEX] Loading and Running Code at Runtime in Android Application

Artyom
6 min readJan 16, 2019

There are many articles out there that describe how to do some things, but there comes a moment when you are stuck with a task which is not easy to do and there isn’t an article describing the solution in some corner of the internet. At that point, you have two choices. The first one, convince yourself that doing that way is wrong and everybody knows that and because of that there isn’t a solution, or create a solution yourself and write an article about it. So in this article, I want to talk about loading code at runtime in android application.

Let’s say that you are developing an application and you have some libraries. Some of those libraries are JARs and some of those AARs.

The difference between JAR and AAR

JAR is just a Java library containing java class files and nothing more.
AAR is a format for android libraries which contains a JAR file, android resource file (like layout XML, attribute XML, etc..) and an android manifest file.
During the build, an R.java file is generated for each android library and for the main project of course, and all java files are compiled to a one or multiple DEX files (DEX is a Dalvik executable format which can be loaded by android runtime (ART) ). So in an APK, there are only DEX files(not JAR or AAR file), and resources and manifest. Android R.java file is an auto-generated file by AAPT (Android Asset Packaging Tool) that contains resource IDs for all the resources of res/ directory.

Why do I need to load some code at runtime?

There are many reasons to do that. Maybe your dependency libraries are too big and you want you APK to have a small size or maybe that libraries are requested for some feature which is not supported for all devices or it is not required at first launch and you have your own logic for differentiation if device supports that feature or not, or if you need to show user the feature or not. Why ship the APK with that feature code? If you are reading this I think you already have your own reasons :)

JAR To DEX

Android does not support loading JAR file, so there must be a way to compile the JAR file to DEX file. For that, there is the D8 tool which is located in android_sdk/build-tools/version/. To convert JAR to DEX you can run this command from command line

d8 --release --output lib.dex path_to_jar_lib.jar

The DEX file is generated and there is no need to build the android project with that JAR library, so in gradle dependancies section instead of declaring that library as implementation or an api configuration, it needs to be a provided configuration which means that build this project as if this library exists but DO NOT include that JAR in application source files from which the DEX files are compiled.

AAR to DEX

To get DEX file from AAR library is a little bit difficult because you must deal with resource files. AAR contains a JAR file and resources. There isn’t a need to make that resources downloadable because most libraries only contain a few resource files which aren’t large and are mostly layout XML files or some general numbers or booleans or something else. So the right thing to do is to merge that resources with the main project resources and change that dependency to be a provided dependency and convert the JAR file to DEX file. But there is a problem with that JAR file. it is not an ordinary JAR file. During the build time, AAPT will not generate an R java file for that library because the library is a provided dependency and the R file usages in that JAR file will crash at runtime. Instead of that, the application R java file will contain the resources ids including the library resources. So the solution to this problem is to manulay create a R.java file which will delegate all resources ids to the R file with the app package name and compile that R file and put it in jar file which can be done with jar -ufv option. And now imagine that an update for this library is released.

This process will become a true nightmare.

Solution: Injector

As I said at the beginning I have created a solution to this problem. What if I told you that this could be done at build time and you even can’t notice that some resource is being moved from one project to another and you don’t have to remember the command line tools with their flags. The solution is Injector. Injector is a Gradle plugin that does all the above explained for you automatically.
First of all, you need to add injector to your Gradle buildscript classpath. Your gradle buildscript should look like this

buildscript {
repositories {
google()
mavenCentral()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.3.0'
classpath 'app.artyomd.injector:injector:{latest-version}'
}
}

After this in a project(which has at least one dependency that you want to make dynamic, doesn’t matter if it is JAR or AAR) gradle file you must enable the plugin, like this

apply plugin: 'app.artyomd.injector'

Note, the project in which you are enabling the plugin must be android library project because android application project does not support provided dependencies for now. So you must have a library module where you will put all the code that uses that dynamic dependency.
After that, you can change the configuration of your desired dependency to inject. Inject is a configuration that during the project evaluation step the plugin will track and mark as a provided dependency. The dependencies section should look like this

dependencies {
implementation 'androidx.appcompat:appcompat:1.0.2'
inject 'com.airbnb.android:lottie:2.8.0' }

You can also specify an injectConfig block where you can group the output DEXs by dependency group or artifact id, like this

injectConfig {
enabled = true
groups = [
"lottie": ["com.airbnb.android"]
]
}

With this config, you are specifying that you want one DEX with name “lottie” which will contain all the dependencies which group is “com.aribnb.android” and the rest of the dependencies will be in a DEX with the name “inject”.
How to build DEXs? Injector automatically creates a task names createInject+variantName+Dexes. For example, if you want to get DEXs for Release to build you can execute task createInjectReleaseDexes (by the way, the release and debug DEXs should not be too different). The DEXs should be in /build/outputs/inject/ folder. No, not a DEX file, a ZIP file which contains a DEX file, because DEX in the ZIP is much smaller :). After that, you can upload those DEXs to your server and download them when you need them. There is left only one question.

How to load the DEX file at runtime?

I have great news for you. The Injector library already has that functionality. All you need to do is to declare injector-android dependency in your main application dependencies

dependencies {
implementation "app.artyomd.injector:injector-android:{latest-version}"
}

Injector-android is a small library build on the top of android muiltidex. With this library, you must only call this one method to load a list of DEX files (or ZIP files containing DEX file) at runtime

DexUtils.loadDex(context, list_of_dex_files);

…and you are good to go. Isn’t this awesome?

You can take a look at injector example project which is showing a simple Lottie animation, but the library is being loaded in the Application class and the DEX file for Lottie is stored in assets.

Feel free to open an issue or contribute :).

--

--