跳到主要内容 Android 插件化框架 DynamicLoadApk 源码解析(下) | 极客日志
Java 大前端 java
Android 插件化框架 DynamicLoadApk 源码解析(下) Android 插件化框架 DynamicLoadApk 源码解析主要涵盖了插件信息实体、代理接口定义、代理与插件 Activity 基类实现、Intent 封装及 SO 库管理模块。通过 DLProxyActivity 模拟插件生命周期,利用 attach 机制绑定宿主与插件上下文,实现了资源加载与组件启动的隔离与统一。此外,源码还展示了基于 CPU 架构的 SO 库动态拷贝逻辑,为理解 Android 动态加载技术提供了底层实践参考。
Android 插件化框架 DynamicLoadApk 源码解析(下)
承接上一篇,继续分析 DynamicLoadApk 核心源码。本文重点梳理插件信息实体、代理接口定义、代理与插件 Activity 基类实现、Intent 封装及 SO 库管理模块。
4.2 DLPluginPackage public String packageName;
public String defaultActivity;
public DexClassLoader classLoader;
public AssetManager assetManager;
public Resources resources;
public PackageInfo packageInfo;
packageName:插件的包名。
defaultActivity:插件的 Launcher Main Activity。
classLoader:加载插件的 ClassLoader。
assetManager:加载插件资源的 AssetManager。
resources:利用 assetManager 中已经加载的资源创建的 Resources,代理组件中会从这个 Resources 中读取资源。
packageInfo:被 PackageManager 解析后的插件信息。
这些信息都会在 DLPluginManager#loadApk(…) 时初始化。
4.3 DLAttachable.java / DLServiceAttachable.java DLServiceAttachable 与 DLAttachable 类似,下面先分析 DLAttachable.java。
DLAttachable 是一个接口,主要作用是以统一所有不同类型的代理 Activity,如 DLProxyActivity、DLProxyFragmentActivity,方便作为同一接口统一处理。DLProxyActivity 和 DLProxyFragmentActivity 都实现了这个类。
public interface DLAttachable {
public void attach (DLPlugin proxyActivity, DLPluginManager pluginManager) ;
}
抽象函数,表示将插件 Activity 和代理 Activity 绑定在一起,其中的 proxyActivity 参数就是指插件 Activity。
同样 DLServiceAttachable 类似,作用是统一所有不同类型的代理 Service,实现插件 Service 和代理 Service 的绑定。虽然目前只有 DLProxyService。
public interface DLServiceAttachable {
public void attach (DLServicePlugin remoteService, DLPluginManager pluginManager) ;
}
4.4 DLPlugin.java / DLServicePlugin.java DLPlugin 与 DLServicePlugin 类似,下面先分析 DLPlugin.java。
DLPlugin 是一个接口,包含 Activity 生命周期、触摸、菜单等抽象函数。DLBase*Activity 都实现了这个类,这样插件的 Activity 间接实现了此类。主要作用是统一所有不同类型的插件 Activity,如 Activity、FragmentActivity,方便作为同一接口统一处理。
public interface DLPlugin {
public void onCreate (Bundle savedInstanceState) ;
public void onStart () ;
public void onRestart () ;
public void onActivityResult (int requestCode, int resultCode, Intent data) ;
public void onResume () ;
public void onPause () ;
public void onStop () ;
public void onDestroy () ;
public void attach (Activity proxyActivity, DLPluginPackage pluginPackage) ;
public void onSaveInstanceState (Bundle outState) ;
public void onNewIntent (Intent intent) ;
public void onRestoreInstanceState (Bundle savedInstanceState) ;
public boolean onTouchEvent (MotionEvent event) ;
public boolean onKeyUp (int keyCode, KeyEvent event) ;
public void onWindowAttributesChanged (LayoutParams params) ;
public void onWindowFocusChanged (boolean hasFocus) ;
public void onBackPressed () ;
public boolean onCreateOptionsMenu (Menu menu) ;
public boolean onOptionsItemSelected (MenuItem item) ;
}
同样 DLServicePlugin 主要作用是统一所有不同类型的插件 Service,方便作为统一接口统一处理,目前包含 Service 生命周期等抽象函数。
public interface DLServicePlugin {
public void onCreate () ;
public void onStart (Intent intent, int startId) ;
public int onStartCommand (Intent intent, int flags, int startId) ;
public void onDestroy () ;
public void onConfigurationChanged (Configuration newConfig) ;
public void onLowMemory () ;
public void onTrimMemory (int level) ;
public IBinder onBind (Intent intent) ;
public boolean onUnbind (Intent intent) ;
public void onRebind (Intent intent) ;
public void onTaskRemoved (Intent rootIntent) ;
public void attach (Service proxyService, DLPluginPackage pluginPackage) ;
}
4.5 DLProxyActivity.java / DLProxyFragmentActivity.java 代理 Activity,它们是在宿主 Manifest 中注册的组件,也是启动插件 Activity 时,真正被启动的 Activity。它们的内部会完成插件 Activity 的初始化和启动。
这两个类大同小异,所以这里只分析 DLProxyActivity。首先来看下它的成员变量。
(1). DLPlugin mRemoteActivity 表示真正需要启动的插件 Activity。这个属性名应该叫做 pluginActivity 更合适。上面我们已经介绍了,DLPlugin 是所有插件 Activity 都间接实现了的接口。接下来在代理 Activity 的生命周期、触摸、菜单等函数中我们都会同时调用 mRemoteActivity 的相关函数,模拟插件 Activity 的相关功能。
public class DLProxyActivity extends Activity implements DLAttachable {
protected DLPlugin mRemoteActivity;
private DLProxyImpl impl = new DLProxyImpl (this );
@Override
protected void onCreate (Bundle savedInstanceState) {
super .onCreate(savedInstanceState);
impl.onCreate(getIntent());
}
@Override
public void attach (DLPlugin remoteActivity, DLPluginManager pluginManager) {
mRemoteActivity = remoteActivity;
}
@Override
protected void onResume () {
mRemoteActivity.onResume();
super .onResume();
}
}
(2). DLProxyImpl impl 主要封装了插件 Activity 的公用逻辑,如初始化插件 Activity 并和代理 Activity 绑定、获取资源等。
DLProxyImpl 相当于把 DLProxyActivity 和 DLProxyFragmentActivity 的公共实现部分提出出来,核心逻辑位于下面介绍的 onCreate() 函数。
1) DLProxyImpl(Activity activity) 构造函数,参数为代理 Activity。
public DLProxyImpl (Activity activity) {
mProxyActivity = activity;
}
2) public void onCreate(Intent intent) onCreate 函数,会在代理 Activity onCreate 函数中被调用。
public void onCreate (Intent intent) {
intent.setExtrasClassLoader(DLConfigs.sPluginClassloader);
mPackageName = intent.getStringExtra(DLConstants.EXTRA_PACKAGE);
mClass = intent.getStringExtra(DLConstants.EXTRA_CLASS);
Log.d(TAG, "mClass=" + mClass + " mPackageName=" + mPackageName);
mPluginManager = DLPluginManager.getInstance(mProxyActivity);
mPluginPackage = mPluginManager.getPackage(mPackageName);
mAssetManager = mPluginPackage.assetManager;
mResources = mPluginPackage.resources;
initializeActivityInfo();
handleActivityInfo();
launchTargetActivity();
}
3) protected void launchTargetActivity() 加载待启动插件 Activity 完成初始化流程,并通过 DLPlugin 和 DLAttachable 接口的 attach 函数实现和代理 Activity 的双向绑定。
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
protected void launchTargetActivity () {
try {
Class<?> localClass = getClassLoader().loadClass(mClass);
Constructor<?> localConstructor = localClass.getConstructor(new Class [] {});
Object instance = localConstructor.newInstance(new Object [] {});
mPluginActivity = (DLPlugin) instance;
((DLAttachable) mProxyActivity).attach(mPluginActivity, mPluginManager);
Log.d(TAG, "instance = " + instance);
mPluginActivity.attach(mProxyActivity, mPluginPackage);
Bundle bundle = new Bundle ();
bundle.putInt(DLConstants.FROM, DLConstants.FROM_EXTERNAL);
mPluginActivity.onCreate(bundle);
} catch (Exception e) {
e.printStackTrace();
}
}
4) private void initializeActivityInfo() 获得待启动插件的 ActivityInfo,其中对插件 Activity 的主题做了一些处理。
private void initializeActivityInfo () {
PackageInfo packageInfo = mPluginPackage.packageInfo;
if ((packageInfo.activities != null ) && (packageInfo.activities.length > 0 )) {
if (mClass == null ) {
mClass = packageInfo.activities[0 ].name;
}
int defaultTheme = packageInfo.applicationInfo.theme;
for (ActivityInfo a : packageInfo.activities) {
if (a.name.equals(mClass)) {
mActivityInfo = a;
if (mActivityInfo.theme == 0 ) {
if (defaultTheme != 0 ) {
mActivityInfo.theme = defaultTheme;
} else {
if (Build.VERSION.SDK_INT >= 14 ) {
mActivityInfo.theme = android.R.style.Theme_DeviceDefault;
} else {
mActivityInfo.theme = android.R.style.Theme;
}
}
}
}
}
}
}
5) private void handleActivityInfo() 设置代理 Activity 的主题等信息。其他的 get* 函数都是获取一些插件相关信息,会被代理 Activity 调用。
private void handleActivityInfo () {
Log.d(TAG, "handleActivityInfo, theme=" + mActivityInfo.theme);
if (mActivityInfo.theme > 0 ) {
mProxyActivity.setTheme(mActivityInfo.theme);
}
Theme superTheme = mProxyActivity.getTheme();
mTheme = mResources.newTheme();
mTheme.setTo(superTheme);
try {
mTheme.applyStyle(mActivityInfo.theme, true );
} catch (Exception e) {
e.printStackTrace();
}
}
4.7 DLBasePluginActivity.java / DLBasePluginFragmentActivity.java 插件 Activity 基类,插件中的 Activity 都要继承 DLBasePluginActivity/DLBasePluginFragmentActivity 之一 (目前尚不支持 ActionBarActivity)。
主要作用是根据是否被代理,确定一些函数直接走父类逻辑还是代理 Activity 或是空逻辑。
DLBasePluginActivity 继承自 Activity,同时实现了 DLPlugin 接口。这两个类大同小异,所以这里只分析 DLBasePluginActivity。
protected Activity mProxyActivity;
protected Activity that;
protected DLPluginManager mPluginManager;
protected DLPluginPackage mPluginPackage;
mProxyActivity 为代理 Activity,通过 attach(…) 函数绑定。that 与 mProxyActivity 等同,只是为了和 this 指针区分,表示真实的 Context,这里真实指的是被代理情况下为代理 Activity,未被代理情况下等同于 this。
Attach 方法,设置代理的 Activity:
@Override
public void attach (Activity proxyActivity, DLPluginPackage pluginPackage) {
Log.d(TAG, "attach: proxyActivity= " + proxyActivity);
mProxyActivity = (Activity) proxyActivity;
that = mProxyActivity;
mPluginPackage = pluginPackage;
}
根据是否被代理,确定一些函数直接走父类逻辑还是代理 Activity 或是空逻辑。
public int startPluginActivityForResult (DLIntent dlIntent, int requestCode) {
if (mFrom == DLConstants.FROM_EXTERNAL) {
if (dlIntent.getPluginPackage() == null ) {
dlIntent.setPluginPackage(mPluginPackage.packageName);
}
}
return mPluginManager.startPluginActivityForResult(that, dlIntent, requestCode);
}
public int startPluginService (DLIntent dlIntent) {
if (mFrom == DLConstants.FROM_EXTERNAL) {
if (dlIntent.getPluginPackage() == null ) {
dlIntent.setPluginPackage(mPluginPackage.packageName);
}
}
return mPluginManager.startPluginService(that, dlIntent);
}
4.8 DLBasePluginService.java 插件 Service 基类,插件中的 Service 要继承这个基类,主要作用是根据是否被代理,确定一些函数直接走父类逻辑还是代理 Service 或是空逻辑。
PS:截止目前这个类还是不完善的,至少和 DLBasePluginActivity 对比,还不支持非代理的情况。
@Override
public void attach (Service proxyService, DLPluginPackage pluginPackage) {
LOG.d(TAG, TAG + " attach" );
mProxyService = proxyService;
mPluginPackage = pluginPackage;
that = mProxyService;
mFrom = DLConstants.FROM_EXTERNAL;
}
protected boolean isInternalCall () {
return mFrom == DLConstants.FROM_INTERNAL;
}
@Override
public IBinder onBind (Intent intent) {
LOG.d(TAG, TAG + " onBind" );
return null ;
}
@Override
public void onCreate () {
LOG.d(TAG, TAG + " onCreate" );
}
4.9 DLIntent.java 继承自 Intent,封装了待启动组件的 PackageName 和 ClassName。
public class DLIntent extends Intent {
private String mPluginPackage;
private String mPluginClass;
public DLIntent (String pluginPackage, String pluginClass) {
super ();
this .mPluginPackage = pluginPackage;
this .mPluginClass = pluginClass;
}
public String getPluginPackage () {
return mPluginPackage;
}
public void setPluginPackage (String pluginPackage) {
this .mPluginPackage = pluginPackage;
}
public String getPluginClass () {
return mPluginClass;
}
public void setPluginClass (Class<?> clazz) {
this .mPluginClass = clazz.getName();
}
@Override
public Intent putExtra (String name, Parcelable value) {
setupExtraClassLoader(value);
return super .putExtra(name, value);
}
private void setupExtraClassLoader (Object value) {
ClassLoader pluginLoader = value.getClass().getClassLoader();
DLConfigs.sPluginClassloader = pluginLoader;
setExtrasClassLoader(pluginLoader);
}
}
4.10 SoLibManager.java 调用 SoLibManager 拷贝 so 库到 Native Library 目录。
(1) copyPluginSoLib(Context context, String dexPath, String nativeLibDir) 函数中以 ZipFile 形式加载插件,循环读取其中的文件,如果为 .so 结尾文件、符合当前平台 CPU 类型且尚未拷贝过最新版,则新建 Runnable 拷贝 so 文件。
public void copyPluginSoLib (Context context, String dexPath, String nativeLibDir) {
String cpuName = getCpuName();
String cpuArchitect = getCpuArch(cpuName);
sNativeLibDir = nativeLibDir;
Log.d(TAG, "cpuArchitect: " + cpuArchitect);
long start = System.currentTimeMillis();
try {
ZipFile zipFile = new ZipFile (dexPath);
Enumeration<? extends ZipEntry > entries = zipFile.entries();
while (entries.hasMoreElements()) {
ZipEntry zipEntry = (ZipEntry) entries.nextElement();
if (zipEntry.isDirectory()) {
continue ;
}
String zipEntryName = zipEntry.getName();
if (zipEntryName.endsWith(".so" ) && zipEntryName.contains(cpuArchitect)) {
final long lastModify = zipEntry.getTime();
if (lastModify == DLConfigs.getSoLastModifiedTime(context, zipEntryName)) {
Log.d(TAG, "skip copying, the so lib is exist and not change: " + zipEntryName);
continue ;
}
mSoExecutor.execute(new CopySoTask (context, zipFile, zipEntry, lastModify));
}
}
} catch (IOException e) {
e.printStackTrace();
}
long end = System.currentTimeMillis();
Log.d(TAG, "### copy so time : " + (end - start) + " ms" );
}
private String getCpuName () {
try {
FileReader fr = new FileReader ("/proc/cpuinfo" );
BufferedReader br = new BufferedReader (fr);
String text = br.readLine();
br.close();
String[] array = text.split(":\\s+" , 2 );
if (array.length >= 2 ) {
return array[1 ];
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null ;
}
@SuppressLint("DefaultLocale")
private String getCpuArch (String cpuName) {
String cpuArchitect = DLConstants.CPU_ARMEABI;
if (cpuName.toLowerCase().contains("arm" )) {
cpuArchitect = DLConstants.CPU_ARMEABI;
} else if (cpuName.toLowerCase().contains("x86" )) {
cpuArchitect = DLConstants.CPU_X86;
} else if (cpuName.toLowerCase().contains("mips" )) {
cpuArchitect = DLConstants.CPU_MIPS;
}
return cpuArchitect;
}
4.11 DLUtils.java 这个类中大都是无用或是不该放在这里的函数,也许是大版本升级及维护人过多后对工具函数的维护不够所致。
至此,DynamicLoadApk 源码已解析完毕。
总结与展望 DynamicLoadApk 的核心设计思想在于代理模式(Proxy Pattern)与 双 ClassLoader 机制 的结合。
代理模式应用 :通过 DLProxyActivity 拦截宿主侧的 Activity 生命周期回调,并将其转发给真正的插件 Activity (DLPlugin)。这种设计使得插件无需修改自身代码即可运行在宿主环境中,实现了业务逻辑与宿主环境的解耦。
资源隔离与共享 :DLPluginPackage 负责管理插件的资源对象(AssetManager, Resources),代理 Activity 通过 impl.getResources() 获取插件资源,解决了资源冲突问题,同时保证了 UI 表现的一致性。
动态加载流程 :从 DLIntent 携带插件信息开始,经过 DLProxyImpl 解析目标类,利用反射实例化插件组件,最后通过 attach 方法建立双向引用链。这一流程确保了插件组件在运行时能够正确访问宿主的上下文和自身的资源。
原生库支持 :SoLibManager 提供了基于 CPU 架构的动态 SO 库拷贝机制,解决了插件中包含本地代码时的兼容性问题,扩展了插件化方案的能力边界。
尽管该方案在早期 Android 版本上表现良好,但随着 Android 系统安全策略的收紧(如限制隐式广播、限制跨进程通信权限等),插件化技术面临更多挑战。开发者在实际应用中需结合具体场景,评估安全性与兼容性,必要时可考虑引入更成熟的开源框架或转向组件化架构。
相关免费在线工具 Keycode 信息 查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
Escape 与 Native 编解码 JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
JavaScript / HTML 格式化 使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
JavaScript 压缩与混淆 Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online