How to Load a Java Class Dynamically on Android/Dalvik

How to load a Java class dynamically on android/dalvik?

There's an example of DexClassLoader in the Dalvik test suite. It accesses the classloader reflectively, but if you're building against the Android SDK you can just do this:

String jarFile = "path/to/jarfile.jar";
DexClassLoader classLoader = new DexClassLoader(
jarFile, "/tmp", null, getClass().getClassLoader());
Class<?> myClass = classLoader.loadClass("MyClass");

For this to work, the jar file should contain an entry named classes.dex. You can create such a jar with the dx tool that ships with your SDK.

Does Android ART support runtime dynamic class loading just like Dalvik?

It seems to work just like it did with Dalvik.

Thanks to matiash for referencing the I/O 2014 talk! I've watched the video recording of it, and here is what the developers have to say on runtime code loading (taken from the transcript):

[Question from the audience:] So I was wondering how ART is going to
jive[?] with byte code injection that might happen right after
compilation or even at runtime.

[...]

[Answer by Ian Rogers:] So the model that Dalvik has and ART continues
is that for class loaders, we have to have everything that the class
loader has backed up by a file. So Dalvik never had supports for the
kind of doing end memory injection of instructions, and so on.

If you have a file on the disk, then this is something we can do ahead
of time compilation for and put into our cache so that we're not
regenerating it all of the time. So basically, it works the same way
as with Dalvik.

How can I make Android load dynamically generated dalvik bytecode without writing to a file first?

No, that might be possible on a jailbroken device but it's not possible in a sandboxed app.

I tried several ways to load dynamic code on Android but the only feasible way is via the DexClassLoader where the dex file must be stored in a privileged region.

You can have a look at my project Byte Buddy where I implemented such class loading: https://github.com/raphw/byte-buddy/blob/master/byte-buddy-android/src/main/java/net/bytebuddy/android/AndroidClassLoadingStrategy.java

Dalvik runtime compiler or other way, to load class in runtime

You can use DexClassLoader to load a classes.dex file. This question might help you as well.

Custom Class Loading in Dalvik with Gradle (Android New Build System)

My team and I recently reached the 64K method references in our app, which is the maximum number of supported in a dex file. To get around this limitation, we need to partition part of the program into multiple secondary dex files, and load them at runtime.

We followed the blog post mentioned in the question for the old, Ant based, build system and everything was working just fine. But we recently felt the need to move to the new build system, based on Gradle.

This answer does not intend to replace the full blog post with a complete example. Instead, it will simply explain how to use Gradle to tweak the build process and achieve the same thing. Please note that this is probably just one way of doing it and how we are currently doing it in our team. It doesn't necessarily mean it's the only way.

Our project is structured a little different and this example works as an individual Java project that will compile all the source code into .class files, assemble them into a single .dex file and to finish, package that single .dex file into a .jar file.

Let's start...

In the root build.gradle we have the following piece of code to define some defaults:

ext.androidSdkDir = System.env.ANDROID_HOME

if(androidSdkDir == null) {
Properties localProps = new Properties()
localProps.load(new FileInputStream(file('local.properties')))

ext.androidSdkDir = localProps['sdk.dir']
}

ext.buildToolsVersion = '18.0.1'
ext.compileSdkVersion = 18

We need the code above because although the example is an individual Java project, we still need to use components from the Android SDK. And we will also be needing some of the other properties later on... So, on the build.gradle of the main project, we have this dependency:

dependencies {
compile files("${androidSdkDir}/platforms/android-${compileSdkVersion}/android.jar")
}

We are also simplifying the source sets of this project, which might not be necessary for your project:

sourceSets {
main {
java.srcDirs = ['src']
}
}

Next, we change the default configuration of the build-in jar task to simply include the classes.dex file instead of all .class files:

configure(jar) {
include 'classes.dex'
}

Now we need to have new task that will actually assemble all .class files into a single .dex file. In our case, we also need to include the Protobuf library JAR into the .dex file. So I'm including that in the example here:

task dexClasses << {
String protobufJarPath = ''

String cmdExt = Os.isFamily(Os.FAMILY_WINDOWS) ? '.bat' : ''

configurations.compile.files.find {
if(it.name.startsWith('protobuf-java')) {
protobufJarPath = it.path
}
}

exec {
commandLine "${androidSdkDir}/build-tools/${buildToolsVersion}/dx${cmdExt}", '--dex',
"--output=${buildDir}/classes/main/classes.dex",
"${buildDir}/classes/main", "${protobufJarPath}"
}
}

Also, make sure you have the following import somewhere (usually at the top, of course) on your build.gradle file:

import org.apache.tools.ant.taskdefs.condition.Os

Now we must make the jar task depend on our dexClasses task, to make sure that our task is executed before the final .jar file is assembled. We do that with a simple line of code:

jar.dependsOn(dexClasses)

And we're done... Simply invoke Gradle with the usual assemble task and your final .jar file, ${buildDir}/libs/${archivesBaseName}.jar will contain a single classes.dex file (besides the MANIFEST.MF file). Just copy that into your app assets folder (you can always automate that with Gradle as we've done but that is out of scope of this question) and follow the rest of the blog post.

If you have any questions, just shout in the comments. I'll try to help to the best of my abilities.



Related Topics



Leave a reply



Submit