ARTICLE · 1044348
Android 插件化技术解析

Android 插件化技术详解
一、什么是插件化
Android 插件化是一种动态加载技术,允许在不重新安装 APK 的情况下,将功能模块(插件)动态加载到宿主(Host)应用中运行。核心思想是"小 App 大 App"——把独立的功能模块拆分成 APK,运行时按需加载。
与热修复(HotFix)的区别:
热修复:修复已有功能的 Bug,补丁包不能包含新功能 插件化:加载全新的功能模块,可以包含 Activity、Service 等四大组件
二、为什么需要插件化
三、核心技术挑战
1. 类加载(Class Loading)
Android 使用 DexClassLoader / PathClassLoader 加载 dex 文件:
code.java
// 加载插件 APK 中的类DexClassLoader loader =newDexClassLoader( pluginApkPath,// 插件 APK 路径 context.getDir("dex", MODE_PRIVATE).getAbsolutePath(),// dex 优化输出目录null,// native library 搜索路径 context.getClassLoader()// 父 ClassLoader(宿主));Class<?> clazz = loader.loadClass("com.plugin.MainActivity");双亲委派模型的利用:
插件 ClassLoader 先尝试自己加载 找不到时委托给父 ClassLoader(宿主)加载 实现插件与宿主共享公共依赖,同时隔离各自代码
2. 资源加载(Resource Loading)
插件中的资源(布局、图片、字符串等)需要通过反射注入到宿主的 AssetManager 中:
code.java
// 反射创建 AssetManager 并添加插件资源路径AssetManager assetManager = AssetManager.class.newInstance();Method addAssetPath = AssetManager.class.getMethod("addAssetPath", String.class);addAssetPath.invoke(assetManager, pluginApkPath);// 用新的 AssetManager 创建 ResourcesResources pluginResources =newResources( assetManager, context.getResources().getDisplayMetrics(), context.getResources().getConfiguration());3. 组件生命周期管理
这是插件化最难的部分。Android 四大组件的生命周期由系统(AMS)管理,而插件中的组件没有在 AndroidManifest.xml 中注册,系统无法感知。
Activity 生命周期的两种解决方案:
方案 A:Hook 方式(反射 + 动态代理)
Hook Instrumentation的newActivity方法在系统创建 Activity 时,替换为插件中的 Activity 实例
code.java
// Hook InstrumentationClass<?> activityThreadClass = Class.forName("android.app.ActivityThread");Object currentActivityThread = activityThreadClass.getDeclaredMethod("currentActivityThread").invoke(null);Field mInstrumentationField = activityThreadClass.getDeclaredField("mInstrumentation");mInstrumentationField.setAccessible(true);Instrumentation originalInstrumentation =(Instrumentation) mInstrumentationField.get(currentActivityThread);// 用代理替换Instrumentation proxyInstrumentation =newProxyInstrumentation(originalInstrumentation, pluginClassLoader);mInstrumentationField.set(currentActivityThread, proxyInstrumentation);方案 B:代理 Activity(Stub Activity)
在宿主 Manifest 中注册一个"空壳" Activity 插件 Activity 的生命周期由代理 Activity 转发
code.java
publicclassProxyActivityextends Activity {privatePluginActivity pluginActivity;@OverrideprotectedvoidonCreate(Bundle savedInstanceState){ super.onCreate(savedInstanceState);// 加载插件 ActivityString className =getIntent().getStringExtra("plugin_activity"); pluginActivity =(PluginActivity) pluginClassLoader.loadClass(className).newInstance();// 注入上下文 pluginActivity.attach(this, pluginResources);// 转发生命周期 pluginActivity.onCreate(savedInstanceState);}@OverrideprotectedvoidonResume(){ super.onResume(); pluginActivity.onResume();// 转发}}4. 插件与宿主的通信
code.java
// 定义插件与宿主的通信接口publicinterfaceIPlugin{voidonCreate(Context context);voidonStart();voidonResume();voidonPause();voidonDestroy();// 获取插件提供的 ViewViewgetView(Context context);}四、主流插件化框架对比
| RePlugin | |||
| VirtualAPK | |||
| Shadow | |||
| DroidPlugin | |||
| ARouter |
Shadow 框架示例(腾讯,目前最活跃)
code.groovy
// 宿主 build.gradledependencies { implementation 'com.tencent.shadow.core:runtime:2.2.1' implementation 'com.tencent.shadow.core:loader:2.2.1'}// 插件 build.gradleapply plugin: 'com.tencent.shadow.plugin'code.java
// 宿主中启动插件PluginManager.getInstance().loadPlugin(pluginApkPath);Intent intent =newIntent();intent.setClassName("com.plugin","com.plugin.MainActivity");PluginManager.getInstance().startActivity(context, intent);五、关键技术细节
1. 资源冲突处理
插件和宿主可能存在资源 ID 冲突(R.drawable.icon 指向不同资源):
code.java
// 方案一:修改插件 aapt 参数,分配不同的资源 ID 段aaptOptions {// 给插件分配 0x71 开头的资源 ID additionalParameters '--package-id','0x71'}// 方案二:运行时动态替换 ResourcespublicclassPluginResourcesextends Resources {@OverridepublicDrawablegetDrawable(int id,Theme theme)throwsNotFoundException{// 将插件资源 ID 映射到实际路径String resName = pluginResTable.getResourceName(id);returnloadDrawableFromPlugin(resName);}}2. So 库加载
插件中的 native 库需要特殊处理:
code.java
// 将插件中的 so 文件解压到指定目录File soDir =newFile(context.getDir("plugin_so", MODE_PRIVATE), pluginName);ZipUtil.unzipSoFromApk(pluginApkPath, soDir);// 设置 so 搜索路径System.loadLibrary("plugin_native_lib", soDir.getAbsolutePath());3. ContentProvider 插件化
code.java
// 在宿主 Manifest 中注册代理 Provider<provider android:name=".PluginContentProviderProxy" android:authorities="com.host.plugin.provider" android:exported="false"/>// 代理 Provider 内部路由到插件 ProviderpublicclassPluginContentProviderProxyextends ContentProvider {@OverridepublicCursorquery(Uri uri,...){String pluginId =parsePluginId(uri);ContentProvider pluginProvider =getPluginProvider(pluginId);return pluginProvider.query(stripPluginPrefix(uri),...);}}4. 广播与服务插件化
code.java
// 静态广播代理注册publicclassPluginBroadcastProxyextends BroadcastReceiver {privateMap<String,BroadcastReceiver> pluginReceivers =newHashMap<>();@OverridepublicvoidonReceive(Context context,Intent intent){String action = intent.getAction();BroadcastReceiver receiver = pluginReceivers.get(action);if(receiver !=null){ receiver.onReceive(context, intent);}}}// Service 代理(类似 Activity 代理模式)publicclassProxyServiceextends Service {privateService pluginService;@OverridepublicintonStartCommand(Intent intent,int flags,int startId){String className = intent.getStringExtra("plugin_service"); pluginService =(Service) pluginClassLoader.loadClass(className).newInstance(); pluginService.onCreate();return pluginService.onStartCommand(intent, flags, startId);}}六、稳定性与兼容性
常见坑点
HiddenApiBypass 或规避反射 | ||
useSharedLibs | ||
插件卸载(释放内存)
code.java
publicvoidunloadPlugin(String pluginId){// 1. 停止插件中所有组件stopPluginActivities(pluginId);stopPluginServices(pluginId);unregisterPluginReceivers(pluginId);// 2. 清理资源引用 pluginResourcesMap.remove(pluginId);// 3. 释放 ClassLoader(关键!)DexClassLoader loader = pluginClassLoaderMap.remove(pluginId);// ClassLoader 没有被 GC 的引用链时才能释放// 需要确保没有静态变量持有插件中的类}七、选型建议
总结: 插件化的本质是在运行时构建一个"迷你 Android 环境",接管组件生命周期、资源加载和类加载。随着 Android 系统对非 SDK 接口的限制越来越严格,插件化的维护成本在上升。对于新项目,建议优先考虑 Android App Bundle + 动态功能模块(Dynamic Feature Module)作为替代方案,除非有强需求必须绕过应用市场分发。
2026-09-20 14:01
长按扫描下方二维码加内推好伯乐的微信
朋友圈同步最新校招/实习信息
拉你进入求职信息共享群
大家一起互通信息!