Android Plugin Architecture: Class Loading, Resource Injection, Component Communication, and Popular Frameworks
This article explains how Android installs APKs, introduces the concepts of plugins and pluginization, details class‑loader and resource‑injection mechanisms, describes four‑component communication strategies, compares several hooking techniques, and reviews major open‑source plugin frameworks such as Atlas, RePlugin, VirtualApp and Shadow.
Preface
In Android, applications are packaged as APK files and must be installed before they can be used. The installation process is actually very simple: copy the APK to a system directory and extract the native libraries.
Common installation directories are: /system/app: system app /system/priv-app: privileged system app /data/app: user app
An APK typically contains classes.dex (bytecode), res (resources), lib (native .so files), assets (static assets) and AndroidManifest.xml (manifest).
When Android launches an app it creates a process and uses ClassLoader to load classes.dex into the process.
Plugin‑related Concepts
Plugin: essentially an APK.
Plugin Project: a project that can compile a plugin APK.
Pluginization: the process of splitting a large APK into multiple smaller APKs.
Plugin architecture includes a dedicated PluginClassLoader, PluginAssetManager, and PluginContext.
Advantages and Disadvantages of Pluginization
Advantages
Smaller APK size; download only needed modules.
Independent debugging and decoupling of modules, improving development efficiency.
Dynamic updates of plugins or patches.
Disadvantages
High refactoring cost for mature projects.
Many frameworks cannot guarantee compatibility across all Android versions.
Difference Between Pluginization and Componentization
Componentization : splits an app into modules that are compiled into a single final APK.
Pluginization : splits an app into multiple APKs; only the host APK is published, plugins are delivered on demand.
Problems to Solve When Refactoring to Pluginization
Plugin class loading
Plugin resource loading
Four‑component communication
Dynamic deployment of plugins
1. Plugin Class Loading
Understanding the parent‑delegation principle is essential. Android’s ClassLoader hierarchy follows this principle.
If a class loader receives a load request, it first delegates to its parent; only when the parent cannot load the class does the request go down to the child.
Key class loaders:
PathClassLoader DexClassLoader public class DexClassLoader extends BaseDexClassLoader {
public DexClassLoader(String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent) {
super((String) null, (File) null, (String) null, (ClassLoader) null);
throw new RuntimeException("Stub!");
}
}The only difference between the two constructors is the optimizedDirectory parameter. PathClassLoader loads only installed APKs, while DexClassLoader can load APKs or dex files from arbitrary paths.
private void loadClass() {
init();
try {
// Load class from optimized dex file
clazz = pluginClassLoader.loadClass("com.iflytek.test.HelloWorld");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
private void init() {
extractPlugin();
pluginPath = File(filesDir.absolutePath, "plugin.apk").absolutePath;
nativeLibDir = File(filesDir, "pluginlib").absolutePath;
dexOutPath = File(filesDir, "dexout").absolutePath;
pluginClassLoader = DexClassLoader(pluginPath, dexOutPath, nativeLibDir, this::class.java.classLoader);
}
private void extractPlugin() {
var inputStream = assets.open("plugin.apk");
File(filesDir.absolutePath, "plugin.apk").writeBytes(inputStream.readBytes());
}After obtaining the plugin ClassLoader, reflection can be used to invoke methods in the plugin.
val loadClass = pluginClassLoader.loadClass(activityName)
loadClass.getMethod("hello", null).invoke(loadClass)2. Plugin Resource Loading
Resources are packaged into the APK and referenced via the generated R class. To load resources from an uninstalled plugin APK, two key APIs are used:
PackageManager#getPackageArchiveInfo PackageManager#getResourcesForApplication PackageManager pm = getPackageManager();
PackageInfo pi = pm.getPackageArchiveInfo(pluginApkPath,
PackageManager.GET_ACTIVITIES | PackageManager.GET_META_DATA |
PackageManager.GET_SERVICES | PackageManager.GET_PROVIDERS |
PackageManager.GET_SIGNATURES);
pi.applicationInfo.sourceDir = pluginApkPath;
pi.applicationInfo.publicSourceDir = pluginApkPath;
Resources injectResources = null;
try {
injectResources = pm.getResourcesForApplication(pi.applicationInfo);
} catch (PackageManager.NameNotFoundException e) { }A custom PluginResources class merges host and plugin resources.
public class PluginResources extends Resources {
private Resources hostResources;
private Resources injectResources;
public PluginResources(Resources hostResources, Resources injectResources) {
super(injectResources.getAssets(), injectResources.getDisplayMetrics(), injectResources.getConfiguration());
this.hostResources = hostResources;
this.injectResources = injectResources;
}
@Override
public String getString(int id, Object... formatArgs) throws NotFoundException {
try {
return injectResources.getString(id, formatArgs);
} catch (NotFoundException e) {
return hostResources.getString(id, formatArgs);
}
}
}3. Four‑Component Communication
Understanding Activity launch processes (root vs. normal) is necessary. The AMS checks the manifest before launching an Activity.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.music.anna.pluginactivity">
<application>
<activity android:name=".StubActivity"/>
</application>
</manifest>Three common hooking methods are presented:
Method 1: Hook Instrumentation
public class InstrumentationProxy extends Instrumentation {
private Instrumentation mInstrumentation;
private PackageManager mPackageManager;
public InstrumentationProxy(Instrumentation instrumentation, PackageManager pm) {
mInstrumentation = instrumentation;
mPackageManager = pm;
}
public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options) {
List<ResolveInfo> infos = mPackageManager.queryIntentActivities(intent, PackageManager.MATCH_ALL);
if (infos == null || infos.size() == 0) {
intent.putExtra(HookHelper.TARGET_INTENT_NAME, intent.getComponent().getClassName());
intent.setClassName(who, "com.music.anna.pluginactivity.StubActivity");
}
try {
Method execMethod = Instrumentation.class.getDeclaredMethod("execStartActivity",
Context.class, IBinder.class, IBinder.class, Activity.class, Intent.class, int.class, Bundle.class);
return (ActivityResult) execMethod.invoke(mInstrumentation, who, contextThread, token,
target, intent, requestCode, options);
} catch (Exception e) { e.printStackTrace(); }
return null;
}
@Override
public Activity newActivity(ClassLoader cl, String className, Intent intent)
throws InstantiationException, IllegalAccessException, ClassNotFoundException {
String intentName = intent.getStringExtra(HookHelper.TARGET_INTENT_NAME);
if (!TextUtils.isEmpty(intentName)) {
return super.newActivity(cl, intentName, intent);
}
return super.newActivity(cl, className, intent);
}
}Method 2: Hook IActivityManager.startActivity and ActivityThread.mH.mCallback
Hook the AMS binder to replace the Intent with a stub, then restore the real Intent in the Handler callback.
public class HCallback implements Handler.Callback {
public static final int LAUNCH_ACTIVITY = 100;
Handler mHandler;
public HCallback(Handler handler) { mHandler = handler; }
@Override
public boolean handleMessage(Message msg) {
if (msg.what == LAUNCH_ACTIVITY) {
Object r = msg.obj;
try {
Intent intent = (Intent) FieldUtil.getField(r.getClass(), r, "intent");
Intent target = intent.getParcelableExtra(HookHelper.TARGET_INTENT);
intent.setComponent(target.getComponent());
} catch (Exception e) { e.printStackTrace(); }
}
mHandler.handleMessage(msg);
return true;
}
}Method 3: Hook ClassLoader (RePlugin approach)
RePlugin hooks two class loaders: RePluginClassLoader (extends PathClassLoader) for host classes and PluginDexClassLoader (extends DexClassLoader) for plugin classes.
@Override
protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException {
Class<?> pc = null;
try {
pc = super.loadClass(className, resolve);
if (pc != null) return pc;
} catch (ClassNotFoundException e) {
if (PluginDexClassLoaderPatch.need2LoadFromHost(className)) {
try { return loadClassFromHost(className, resolve); } catch (ClassNotFoundException ignored) {}
}
}
if (RePlugin.getConfig().isUseHostClassIfNotFound()) {
try { return loadClassFromHost(className, resolve); } catch (ClassNotFoundException ignored) {}
}
if (cnfException != null) throw cnfException;
return null;
}4. Runtime Container Technique (Proxy Activity)
A host APK can pre‑declare a placeholder ContainerActivity. At runtime it loads the plugin APK, creates a PluginClassLoader and PluginResources, and forwards lifecycle callbacks to the actual plugin Activity.
public class ContainerActivity extends Activity {
PluginActivity pluginActivity;
@Override
protected void onCreate(Bundle savedInstanceState) {
String pluginActivityName = getIntent().getString("pluginActivityName", "");
pluginActivity = PluginLoader.loadActivity(pluginActivityName, this);
if (pluginActivity == null) { super.onCreate(savedInstanceState); return; }
pluginActivity.onCreate();
}
@Override
protected void onResume() { if (pluginActivity == null) { super.onResume(); return; } pluginActivity.onResume(); }
@Override
protected void onPause() { if (pluginActivity == null) { super.onPause(); return; } pluginActivity.onPause(); }
// ... other lifecycle methods
}Popular Open‑Source Plugin Frameworks
Alibaba Atlas
Atlas provides component isolation, on‑demand bundle loading, remote bundles, and an interpretation‑first execution strategy.
Qihoo 360 RePlugin
RePlugin is a stable, full‑featured, placeholder‑based plugin solution supporting all four Android components, cross‑process communication, resource isolation, and works from API 9 onward.
asLody VirtualApp
VirtualApp uses a ContentProvider as the host execution environment and hooks the AMS to achieve plugin isolation.
Tencent Shadow
Shadow supports components, fragments, data binding, cross‑process services, custom themes, native library loading, and segmented plugin loading.
Choosing a plugin framework depends on project requirements and the specific features each framework offers.
Conclusion
This article covered the core techniques of Android pluginization: class loading, resource injection, component communication, various hooking methods, runtime container technology, and a comparison of major open‑source frameworks.
Understanding these mechanisms helps developers explore the inner workings of plugin systems used by large companies.
References
Android Pluginization Principle (Part 1) – Activity Pluginization
Atlas Official Documentation
Deep Dive into Android Pluginization
RePlugin Principle Introduction
Analysis and Implementation of Pluginization
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Sohu Tech Products
A knowledge-sharing platform for Sohu's technology products. As a leading Chinese internet brand with media, video, search, and gaming services and over 700 million users, Sohu continuously drives tech innovation and practice. We’ll share practical insights and tech news here.
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.
