Android Plugin Architecture Overview and Implementation Guide
This guide explains Android pluginization, showing how a host app can dynamically load separate APK modules—including native libraries, classes via a custom DexClassLoader, and mixed resources—while supporting hot updates, modular isolation, and reduced initial size, and details each loading step and manager implementation.
This article provides a comprehensive overview of Android pluginization, describing how a host application can dynamically load and use code, resources, and native libraries from separate APK modules (plugins). It explains the motivations—reducing initial APK size, enabling hot updates, and achieving module isolation.
Key functional points of pluginization:
Dynamic modularization: load modules on demand.
Hot update: fix bugs or update parts without reinstalling the whole app.
Module isolation: lower coupling between components.
Implementation content includes:
Plugin configuration.
Plugin loading (so library loading, APK loading, resource loading).
Application pluginization.
Four‑component pluginization (Activity, Service, BroadcastReceiver, ContentProvider).
Plugin Loading Process
The host must first load the plugin APK, which involves four main steps:
Parse and filter the plugin’s native .so libraries according to the device’s CPU architecture.
Load the plugin APK and create a DexClassLoader .
Create a mixed Resources object that can retrieve resources from both host and plugin.
Cache plugin information for later use.
1. Parsing and Filtering Plugin .so Libraries
The host obtains the plugin’s CPU architecture and copies matching .so files to a dedicated lib directory. Core code:
public static String getCpuArchByAppInfo(Context context) { ... }Utility method to choose a suitable CPU ABI:
public static String chooseByX86andArm(Context context) { ... }Method to retrieve the system‑supported ABIs:
public static String[] getCpuABIs() { ... }2. Loading the Plugin APK
The host creates a custom class loader ApkClassLoader that extends DexClassLoader . It overrides loadClass to apply a whitelist (classes loaded from the host) and otherwise tries the plugin first, then the host.
public class ApkClassLoader extends DexClassLoader { ... @Override protected Class
loadClass(String className, boolean resolve) throws ClassNotFoundException { ... } }Example of initializing the loader:
String libDirPath = new File(pluginApkFile.getParent(), "lib").getAbsolutePath();
File optimizeFile = application.getDir("plugin-optimize", Context.MODE_PRIVATE);
String[] whiteInterfaces = new String[]{"com.xxx.yyy.standard"};
ApkClassLoader pluginClassLoader = new ApkClassLoader(pluginPath, optimizeFile.getAbsolutePath(), libDirPath, application.getClassLoader(), whiteInterfaces);3. Custom Resources Handling
Three custom resource classes are introduced:
SuperHostResources : builds a host Resources object with the plugin’s asset path added.
PluginResources : obtains a Resources instance for the plugin APK.
MixResources (extends ResourcesWrapper ): first tries host resources, then falls back to plugin resources for all resource types.
Key snippets:
public class SuperHostResources { private final Resources resources; public SuperHostResources(Context ctx, String pluginPath) { ... } public Resources get() { return resources; } } public class PluginResources { private final Resources resources; public PluginResources(Context ctx, String pluginPath) { ... } public Resources get() { return resources; } } public class ResourcesWrapper extends Resources { private final Resources hostResources; public ResourcesWrapper(Resources hostResources) { super(hostResources.getAssets(), hostResources.getDisplayMetrics(), hostResources.getConfiguration()); this.hostResources = hostResources; } @Override public CharSequence getText(int id) throws NotFoundException { return hostResources.getText(id); } ... } public class MixResources extends ResourcesWrapper { private final Resources pluginResources; public MixResources(Resources hostResources, Context ctx, String pluginPath) { super(hostResources); PluginResources pr = new PluginResources(ctx, pluginPath); this.pluginResources = pr.get(); } @Override public CharSequence getText(int id) throws NotFoundException { try { return super.getText(id); } catch (Resources.NotFoundException e) { return pluginResources.getText(id); } } ... }Resource ID lookup is adjusted so that the plugin first attempts to resolve IDs from its own resources, then from the host:
private static int getIdentifier(Context context, String name, String type) { int id = ID_DEFAULT; try { id = getIdentifierFromPlugin(context, name, type); if (id == ID_DEFAULT) { id = context.getResources().getIdentifier(name, type, context.getPackageName()); } } catch (Exception e) { e.printStackTrace(); } return id; }4. Caching Plugin Information
A simple Plugin class holds the custom class loader and mixed resources, and a singleton PluginManager creates and stores a Plugin instance during application startup:
public class Plugin { private ApkClassLoader classLoader; private Resources resource; public ClassLoader getClassLoader() { return classLoader; } public void setClassLoader(ApkClassLoader cl) { this.classLoader = cl; } public Resources getResource() { return resource; } public void setResources(Resources res) { this.resource = res; } } public class PluginManager { private static PluginManager instance; private Plugin plugin; public static PluginManager getInstance() { if (instance == null) { synchronized (PluginManager.class) { if (instance == null) { instance = new PluginManager(); } } } return instance; } public void loadPlugin(Application app, String pluginPath) { plugin = new Plugin(); // load so libraries, create ApkClassLoader, create MixResources, set into plugin ... } }It is recommended to invoke PluginManager.loadPlugin() as early as possible, e.g., in the host application's attachBaseContext() method.
Summary
The host and plugin are independent APKs; to use plugin functionality the host must load the plugin’s native libraries, class loader, and resources. The article details each step, provides concrete implementations, and emphasizes early loading to avoid context‑caching issues.
37 Interactive Technology Team
37 Interactive Technology Center
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.