dynamic-load-apk 核心原理分析(代理模式)
dynamic-load-apk简介
项目地址:https://github.com/singwhatiwanna/dynamic-load-apk
dynamic-load-apk是2014年底,任玉刚发布的一个Android插件化项目,这跟后续出现的很多插件化项目都不太一样。它没有Hook太多的系统底层方法,而是在应用层上,通过代理的方式实现的一种插件化框架。主要特点:
- plugin支持Activity、Service以及动态的BroadcastReceiver。
- 基本无反射调用
- 插件安装后仍可独立运行从而便于调试
- 支持plugin对host的调用
- 插件需要引入DL的一个jar包,遵循DL接口规范
为什么是核心原理分析不是源码分析?
dynamic-load-apk插件在项目实际应用中会有很多问题,并且支持组件少、对插件侵入严重,插件开发成本高等问题,所以在2015年该项目就已经停止更新。但作为国内第一个可参考的较完整的android插件化项目,他的核心原理我们还是要了解,有利于我们更全面的了解插件化方案。
总体设计
代码结构
一下为dynamic-load-apk框架的所有代码,对于一个插件化框架来说算是很少了
$RUG5PRJ.pngdynamic-load-apk主要分为四大模块:
- DLPluginManager插件管理模块,负责插件的加载、管理以及启动插件组件。
- Proxy代理组件模块,目前包括 DLProxyActivity(代理 Activity)、DLProxyFragmentActivity(代理 FragmentActivity)、DLProxyService(代理 Service)。
- Proxy Impl代理组件公用逻辑模块,负责构建、加载插件组件的管理器。这些 Proxy Impl 通过反射得到插件组件,然后将插件与 Proxy 组件建立关联,最后调用插件组件的 onCreate 函数进行启动。
- Base Plugin插件组件的基类模块,目前包括 DLBasePluginActivity(插件 Activity 的基类)、DLBasePluginFragmentActivity(插件 FragmentActivity 的基类)、DLBasePluginService(插件 Service 的基类)。
调用插件 Activity 的流程图
2_看图王.png其他组件调用流程类似:
(1) 首先通过 DLPluginManager 的 loadApk 函数加载插件,这步每个插件只需调用一次。
(2) 通过 DLPluginManager 的 startPluginActivity 函数启动代理 Activity.。
(3) 代理 Activity 启动过程中构建、启动插件 Activity。
loadApk 加载插件
加载插件源码:
public DLPluginPackage loadApk(final String dexPath, boolean hasSoLib) {
mFrom = DLConstants.FROM_EXTERNAL;
PackageInfo packageInfo = mContext.getPackageManager().getPackageArchiveInfo(dexPath,
PackageManager.GET_ACTIVITIES | PackageManager.GET_SERVICES);
if (packageInfo == null) {
return null;
}
// 得到插件信息封装类
DLPluginPackage pluginPackage = preparePluginEnv(packageInfo, dexPath);
// 如果有 .so 文件,则复制到 mNativeLibDir 目录
if (hasSoLib) {
copySoLib(dexPath);
}
return pluginPackage;
}
loadApk方法 主要做了两件事:
- 在 preparePluginEnv方法中把插件 packageInfo 封装成 pluginPackage ;
- eventInheritance 默认为false,支持事件继承:直接发送eventClass 事件。
- 复制 .so 文件到 mNativeLibDir 目录,主要流程就是在 SoLibManager 中利用 I/O 流复制文件。
进一步到preparePluginEnv(PackageInfo packageInfo, String dexPath) 方法:
private DLPluginPackage preparePluginEnv(PackageInfo packageInfo, String dexPath) {
// 先查看缓存中有没有该 pluginPackage
DLPluginPackage pluginPackage = mPackagesHolder.get(packageInfo.packageName);
if (pluginPackage != null) {
return pluginPackage;
}
// 创建 加载插件的ClassLoader
DexClassLoader dexClassLoader = createDexClassLoader(dexPath);
AssetManager assetManager = createAssetManager(dexPath);
// 得到插件 res 资源
Resources resources = createResources(assetManager);
// create pluginPackage
pluginPackage = new DLPluginPackage(dexClassLoader, resources, packageInfo);
mPackagesHolder.put(packageInfo.packageName, pluginPackage);
return pluginPackage;
}
preparePluginEnv方法主要做的事情:
- 创建了插件的 ClassLoader ,用于之后加载插件类。
- 创建插件的 resources 资源。插件的 res 资源访问主要通过 AssetManager 的 addAssetPath 方法来获取。需要注意的是,addAssetPath 方法是 @hide 的,需要反射来执行。
- 最后封装成一个 pluginPackage 对象返回。
startPluginActivityForResult启动插件
startPluginActivityForResult源码
public int startPluginActivityForResult(Context context, DLIntent dlIntent, int requestCode) {
// 判断是否宿主内部调用
if (mFrom == DLConstants.FROM_INTERNAL) {
dlIntent.setClassName(context, dlIntent.getPluginClass());
performStartActivityForResult(context, dlIntent, requestCode);
return DLPluginManager.START_RESULT_SUCCESS;
}
String packageName = dlIntent.getPluginPackage();
if (TextUtils.isEmpty(packageName)) {
throw new NullPointerException("disallow null packageName.");
}
// 得到插件信息
DLPluginPackage pluginPackage = mPackagesHolder.get(packageName);
if (pluginPackage == null) {
return START_RESULT_NO_PKG;
}
// 得到插件 Activity 的全类名
final String className = getPluginActivityFullPath(dlIntent, pluginPackage);
// 得到对应的 class
Class<?> clazz = loadPluginClass(pluginPackage.classLoader, className);
if (clazz == null) {
return START_RESULT_NO_CLASS;
}
// 根据插件 class 继承的是哪个基类,分别得到对应的代理类
// 若继承的是 DLBasePluginActivity ,得到的就是 DLProxyActivity 代理类
// 若继承的是 DLBasePluginFragmentActivity ,得到的就是 DLProxyFragmentActivity 代理类
Class<? extends Activity> activityClass = getProxyActivityClass(clazz);
if (activityClass == null) {
return START_RESULT_TYPE_ERROR;
}
// 把插件信息传入 Intent 中
dlIntent.putExtra(DLConstants.EXTRA_CLASS, className);
dlIntent.putExtra(DLConstants.EXTRA_PACKAGE, packageName);
// 这里启动的是上面得到的代理类 Activity
dlIntent.setClass(mContext, activityClass);
// 启动 Activity
performStartActivityForResult(context, dlIntent, requestCode);
return START_RESULT_SUCCESS;
}
startPluginActivityForResult方法里主要是获得插件主页 Activity 的clazz , 根据插件 class 继承的是哪个基类,分别得到对应的代理类
,并通过intent 启动的是代理的 Activity ,并不是我们插件的 Activity 。
DLProxyActivity绑定插件Acitivity并启动
DLProxyActivity 源码
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());
}
...
}
在ProxyActivity 的onCreate(Bundle savedInstanceState)方法 中调用了 impl.onCreate(getIntent()) , impl.onCreate(getIntent()) 的方法里
public void onCreate(Intent intent) {
// set the extra's class loader
intent.setExtrasClassLoader(DLConfigs.sPluginClassloader);
// 得到传过来的插件 Activity 包名和全类名
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;
// 得到要启动插件的 activityInfo,设置插件 Activity 的主题
initializeActivityInfo();
// 把 DLProxyActivity 的主题设置为插件 Activity 的主题
handleActivityInfo();
launchTargetActivity();
}
在 onCreate(Intent intent) 中得到了之前插件 Activity 相关的信息。然后把 DLProxyActivity 的主题设置为 PluginActivity 的主题。最后调用了 launchTargetActivity() ,把 PluginActivity 和 ProxyActivity 绑定在一起。
继续看捆绑方法launchTargetActivity() :
TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
protected void launchTargetActivity() {
try {
// 通过反射创建插件 Activity 的对象
Class<?> localClass = getClassLoader().loadClass(mClass);
Constructor<?> localConstructor = localClass.getConstructor(new Class[] {});
Object instance = localConstructor.newInstance(new Object[] {});
mPluginActivity = (DLPlugin) instance;
// 手动调用插件的 attach 方法,将ProxyActivity和PluginActivity绑定在一起
((DLAttachable) mProxyActivity).attach(mPluginActivity, mPluginManager);
mPluginActivity.attach(mProxyActivity, mPluginPackage);
Bundle bundle = new Bundle();
bundle.putInt(DLConstants.FROM, DLConstants.FROM_EXTERNAL);
// 手动调用插件的 onCreate 方法
mPluginActivity.onCreate(bundle);
} catch (Exception e) {
e.printStackTrace();
}
}
launchTargetActivity() 在方法中使用反射创建了插件 PluginActivity的对象,又因为插件 Activity 必须继承指定的基类DLBasePluginActivity,这些基类是实现了 DLPlugin 接口的。所以插件 Activity 可以强转为 DLPlugin 。DLPlugin 接口定义了一系列的 Activity 生命周期方法,之后手动回调了 attach 和 onCreate 方法将ProxyActivity和PluginActivity绑定在一起。代理 ProxyActivity 回调mRemoteActivity生命周期方法时,都调用了 DLPlugin 接口一致的生命周期方法,这样就实现了插件 PluginActivity具备了完整的生命周期。
至此,dynamic-load-apk的插件化实现的主要流程介绍完了,主要解决了资源加载问题和代理activity的生命周期管理问题。最后附上主要流程图:
4.png
网友评论