Dynamic Code Loading
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:
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)
}
}