夜雨聆风学习资料网

ARTICLE · 1044348

Android 插件化技术解析

Android 插件化技术解析
最近各厂25-27届秋招/实习/社招,如何打消信息差?有一个能及时同步校招信息的群才是关键!前程无忧校招实习内推群,大中小厂信息都会同步,特别是国企央企的岗位,扫下方二维码获取。都是免费的。

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
360
Hook AMS + ClassLoader
无需 Stub Activity,组件无需改写
VirtualAPK
滴滴
Hook + 资源合并
支持四大组件,插件开发无感知
Shadow
腾讯
代理 Activity + 组合式
稳定性好,AndroidX 支持完善
DroidPlugin
社区
深度 Hook AMS
最早期的方案,已基本停更
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);}}

六、稳定性与兼容性

常见坑点

问题
原因
解决方案
Android 9+ 反射限制
非 SDK 接口限制
使用 HiddenApiBypass 或规避反射
Android 10+ 共享库限制
useSharedLibs
 被废弃
改用独立 ClassLoader 方案
SELinux 导致 so 加载失败
安全策略限制
将 so 放到应用私有目录
多进程插件冲突
插件状态未隔离
每个进程独立管理插件生命周期
内存泄漏
插件 ClassLoader 未释放
提供插件卸载接口,清理所有引用

插件卸载(释放内存)

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 的引用链时才能释放// 需要确保没有静态变量持有插件中的类}

七、选型建议

场景
推荐方案
需要加载完整 Activity
Shadow(腾讯)或 RePlugin(360)
只需加载 View / 业务逻辑
自定义 DexClassLoader 即可
追求稳定性
Shadow,社区活跃,Android 高版本适配好
追求开发体验
RePlugin,插件无需改写代码
新项目
考虑用动态下发模块(如 Play Feature Delivery)替代

总结: 插件化的本质是在运行时构建一个"迷你 Android 环境",接管组件生命周期、资源加载和类加载。随着 Android 系统对非 SDK 接口的限制越来越严格,插件化的维护成本在上升。对于新项目,建议优先考虑 Android App Bundle + 动态功能模块(Dynamic Feature Module)作为替代方案,除非有强需求必须绕过应用市场分发。

2026-09-20 14:01

小红书,腾讯,快手等互联网内推加下方微信,互联网内推求职神器

长按扫描下方二维码加内推好伯乐的微信

朋友圈同步最新校招/实习信息

拉你进入求职信息共享群

大家一起互通信息!

相关学习资料