Skip to main content

Dynamic Code Loading

danger

Dynamic code loading is strongly discouraged by the Android team. Only use this method when you truly really need it.

Dynamic Code Loading (DCL) allows an application to run a code that was not installed as part of the application initially. In Android, it is possible to run separate .jar, .apk, or .dex files within our application using subclasses of BaseDexClassLoader. In this example, we will use DexClassLoader class.

Basic Usage​

Let's say I have an APK file named greeting.apk that contains a simple Greeting class that contains 2 methods, one of them being static:

Greeting.kt
package com.hanmajid.greeting

/**
* Simple greeting class.
*/
class Greeting {

/**
* Returns greeting to [name].
*/
fun greeting(name: String): String {
return "Hello, $name!"
}

companion object {
/**
* Returns good night to [name].
*/
@JvmStatic
fun goodNight(name: String): String {
return "Good night, $name!"
}
}
}

We can utilize this class and its methods from other application using dynamic code loading.

First, we need to put this APK file inside our application's raw resource folder: app/src/main/res/raw/greeting.apk.

Invoking Class Methods​

To invoke Greeting class non-static methods within our application, we can do something like this:

package com.hanmajid.androidnotebook

import android.content.Context
import android.util.Log
import dalvik.system.DexClassLoader
import java.io.File

// Move the apk file to temporary file.
val file = File.createTempFile("temp", ".apk")
context.resources.openRawResource(R.raw.greeting).use { input ->
file.outputStream().use {
file.setReadOnly() // Required in Android 14+
input.copyTo(it)
}
}

// Load the apk file.
val loader = DexClassLoader(file.absolutePath, null, null, javaClass.classLoader)

// Load the Greeting class.
val greetingClass = loader.loadClass("com.hanmajid.greeting.Greeting")

// Get the `greeting` method with the correct parameter type (String).
val greetingMethod = greetingClass.getMethod("greeting", String::class.java)

// Create an instance of Greeting class.
val greetingClassInstance = greetingClass.getDeclaredConstructor().newInstance()

// Invoke the `greeting` method.
val greeting = greetingMethod.invoke(greetingClassInstance, "John") as String

Log.i("TAG", greeting)

Invoking Class Static Methods​

Invoking static methods is quite similar to invoking non-static methods. The only difference is you don't have to create a class instance for the invoke method:

package com.hanmajid.androidnotebook

import android.content.Context
import android.util.Log
import dalvik.system.DexClassLoader
import java.io.File

// Move the apk file to temporary file.
val file = File.createTempFile("temp", ".apk")
context.resources.openRawResource(R.raw.greeting).use { input ->
file.outputStream().use {
file.setReadOnly()
input.copyTo(it)
}
}

// Load the apk file.
val loader = DexClassLoader(file.absolutePath, null, null, javaClass.classLoader)

// Load the Greeting class.
val loadClass = loader.loadClass("com.hanmajid.greeting.Greeting")

// Get the `greeting` method with the correct parameter type (String).
val method = loadClass.getMethod("goodNight", String::class.java)

// Invoke the `greeting` method.
val goodNight = method.invoke(null, "Jane") as String

Log.i("TAG", goodNight)

Android 14 Safe Dynamic Code Loading​

Starting from Android 14 (API level 34), the system will throw an exception if you don't mark the dynamically-loaded files (.apk, .jar, or .dex) as read-only. So if you're targeting Android 14, make sure to include this line of code:

// Move the apk file to temporary file.
val file = File.createTempFile("temp", ".apk")
context.resources.openRawResource(R.raw.greeting).use { input ->
file.outputStream().use {
file.setReadOnly() // Required in Android 14+
input.copyTo(it)
}
}

References​