ENPLUG BLOG

Inspiring digital communication ideas.


Loading Classes Dynamically on Android … Particularly When You Have to Use Them in a Third-Party Framework Like LibGDX

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:

    1. Using reflection.
    2. 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.

Project structure

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 ApplicationListenerinterface.
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.

3. MyPlugin

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.

The Goal

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;
[/csharp]

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);
[/csharp]

However, if you don’t load the class properly, you’ll run into a great deal of unexpected trouble.

Problem #1: 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.

[csharp] // fullClassName is the fully qualified name of the class you want to load e.g. “com.enplug.games.PhotoWall”;
// 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,
dexOutputDir.getAbsolutePath(),
null,
getClassLoader());
// Use dex loader to load the class
//
Class<?> loadedClass = Class.forName(fullClassName, true, loader);
return loadedClass;
}

[/csharp]

Problem #2: 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.

Note
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.

Summary
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.


About Enplug Digital Signage Software

Enplug digital signage makes it simple for businesses to create and share compelling visual content for their marketing and employee communications. Our software powers content on thousands of TVs worldwide with news feeds, social media walls, sports scores, employee leaderboards, graphics, and videos. Enplug was founded in 2012 in Los Angeles, California.