Loading Classes Dynamically on Android … Particularly When You Have to Use Them in a Third-Party Framework Like LibGDX
There are two ways of using dynamically loaded classes in your code:
- Using reflection.
- Using a pre-defined interface or base class.
Reflection is fairly easy to get working, but is slow and more importantly you have to keep using reflection throughout your code. It is therefore not usable if you need to integrate your class with a third-party framework. Unfortunately, this is exactly what we needed to do: to load LibGDX games dynamically and run them on our devices.
This article describes all the tricks involved in using pre-defined base classes (the second option above) to achieve this.
Note: In what follows, we will use the base class approach, but it works just as well with interfaces.
There are three major components in play:
1. Host Application / Platform
Loads the classes dynamically. It casts them to a specific base class
PluginGame, and executes their logic.
2. Framework Sdk
Defines the base class
PluginGame. In our case this base class implements the LibGDX
The sdk also contains any shared libraries and interfaces that are used between the host application and the game implementations. During development time Sdk.jar is linked to both the host application project and to any plugin game implementation project. At runtime it is provided by the host application.
Extends the base class
PluginGame. This is the actual game that will be loaded dynamically. It could be downloaded from your server at runtime or stored on the device’s SD card, depending on your particular scenario.
Our end goal is to be able to write something like this:[csharp] Class<?> loadedClass = loadClassDynamically(fullClassName, fullPath);
Object result = loadedClass.getConstructor().newInstance();
PluginGame game = (PluginGame)result;
Once this is done, you can forget in the rest of your code that you loaded the class dynamically, and treat it just like as a ‘regular’ object that was created using ‘new’. In our case, we can pass the object into LibGDX:[csharp] View libgdxView = initializeForView(game, config);
However, if you don’t load the class properly, you’ll run into a great deal of unexpected trouble.
ClassCastException when casting the dynamically created object to your base class
You will get this exception if the
PluginGame class that you loaded is different from the
PluginGame class that the host application knows about.
The Java class loaders are hierarchical, so to make sure that you only have one
PluginGame class at runtime, you must pass in the host’s class loader as the parent to your class loader. This way, your loader will first ask the parent for any class definition it has to resolve.
// fullPathToApk is the full path to the apk or jar containing classes.dex with that class definition
public Class<?> loadClassDynamically(String fullClassName, String fullPathToApk)
File dexOutputDir = androidContext.getDir(“dex”, Context.MODE_PRIVATE);
// Get the current class loader and pass it as parent when creating DexClassLoader
DexClassLoader dexLoader = new DexClassLoader(fullPathToApk,
// Use dex loader to load the class
Class<?> loadedClass = Class.forName(fullClassName, true, loader);
java.lang.IllegalAccessError: Class ref in pre-verified class resolved to unexpected implementation.
Android programs are compiled into .dex files, executables in Dalvik format, which are then included inside the .apk file. After the java compiler is finished, the
Dx utility included in the Android SDK converts the compiled java .class files into a
classes.dex file. By default this includes all the classes from your application as well as all the non-platform libraries your application depends on.
To fix this error, you need to make sure that the
classes.dex file of your plugin doesn’t include the libraries shared with your host application, such as your sdk jar or LibGDX jars.
If you are using IntelliJ it’s easy. All you need to do is to set the Scope of your libraries as ‘Provided’. This tells the environment to use them at compile time but not at runtime. For Android, this means they won’t be added to the dex file. This page describes dependencies in IntelliJ.
If you’re using Maven, setting
<scope>provided</scope> should have the same effect.
You can also create your dex file manually from the command line, by calling the dx utility yourself and making sure that it doesn’t get the shared libraries in its input.
If the object you are loading is more than just a class — e.g., it is a LibGDX game that includes assets — then the standard way of resolving those assets will not work.
One easy way of dealing with this is to implement a custom FileHandleResolver and use it in your plugin game for resolving all internal assets. When your game is running standalone i.e. during testing, this resolver would default to calling
Gdx.files.internal. When running as part of the host, it would look in the location where you downloaded and unzipped your dynamically loaded package.
In this article I described how to dynamically load Java classes on Android and cast them to your interfaces, so you don’t have to use reflection throughout your code.
Enplug digital signage software was co-founded by CEO Nanxi Liu and CTO Tina Denuit-Wojcik in 2012 to enable organizations to use customized real-time streaming content to create engaging external and internal communications.