欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~
最近在工作中接触到了Android插件内的开发,发现自己这种技术还缺乏最基本的了解,以至于在一些基本问题上浪费不少时间,如插件Context和主工程Context的区别,权限必须在主工程申明等,因此花了点时间了解了一下插件的历史,并写了两个Demo作为总结。本文旨在通过两个实例直观的说明插件的实现原理以加深对插件内开发的理解,因此不会深入探讨背景和原理,代码也尽量专注于核心逻辑。
原理与背景
Android插件化从技术上来说就是如何启动未安装的apk(主要是四大组件)里面的类,主要问题涉及如何加载类、如何加载资源、如何管理组件生命周期。
类加载
Android对于外部的dex文件,主要通过DexClassLoader
类加载,因此,只需要给定插件的路径,就可以构造对应的类加载器:
private DexClassLoader createDexClassLoader(String apkPath) {
File dexOutputDir = mContext.getDir("dex", Context.MODE_PRIVATE);
DexClassLoader loader = new DexClassLoader(apkPath, dexOutputDir.getAbsolutePath(),
null, mContext.getClassLoader());
return loader;
}
资源加载
Android系统通过Resource对象加载资源,因此只需要添加资源(即apk文件)所在路径到AssetManager
中,即可实现对插件资源的访问。
AssetManager assetManager = AssetManager.class.newInstance();
Method addAssetPath = assetManager.getClass().getMethod(addAssetPathMethod, String.class);
addAssetPath.invoke(assetManager, apkPath);
Resources pluginRes = new Resources(assetManager,
mContext.getResources().getDisplayMetrics(),
mContext.getResources().getConfiguration());
pluginApk = new PluginApk(pluginRes);
pluginApk.classLoader = createDexClassLoader(apkPath);
生命周期
插件化中较为复杂的是对生命周期的管理,其中以Activity最为复杂。早期的dynamic-load-apk采用的是代理的方式,通过一个空壳Activity作为代理(Proxy),系统对该Activity的回调都会映射到插件Activity,如此便可以实现通过系统来管理插件的生命周期。这种方式十分直观,但是需要所有的插件Activity都继承这个用作代理的PluginActivity
(Demo中的命名),侵入性强,可结合后面的例子加深理解。因此,如何避免这种侵入性成了第二代插件化框架的目标,VirtualApk通过Hook少量系统类达到了这个目标,插件的开发和普通工程无异,接入成本极低。
了解了这些原理往往还不够,知识往往需要经过推导和实践才能变成自己的,因此,接下来我们结合这些原理来实现一个插件化框架,不考虑兼容性和健壮性,纯粹来实践上面提及的原理。
代理实现
首先建立一个PluginManager
类来实现插件的加载:
public class PluginManager {
static class PluginMgrHolder {
static PluginManager sManager = new PluginManager();
}
private static Context mContext;
Map<String, PluginApk> sMap = new HashMap<>();
public static PluginManager getInstance() {
return PluginMgrHolder.sManager;
}
public PluginApk getPluginApk(String packageName) {
return sMap.get(packageName);
}
public static void init(Context context) {
mContext = context.getApplicationContext();
}
public final void loadApk(String apkPath) {
PackageInfo packageInfo = queryPackageInfo(apkPath);
if (packageInfo == null || TextUtils.isEmpty(packageInfo.packageName)) {
return;
}
// check cache
PluginApk pluginApk = sMap.get(packageInfo.packageName);
if (pluginApk == null) {
pluginApk = createApk(apkPath);
if (pluginApk != null) {
pluginApk.packageInfo = packageInfo;
sMap.put(packageInfo.packageName, pluginApk);
} else {
throw new NullPointerException("PluginApk is null");
}
}
}
private PluginApk createApk(String apkPath) {
String addAssetPathMethod = "addAssetPath";
PluginApk pluginApk = null;
try {
AssetManager assetManager = AssetManager.class.newInstance();
Method addAssetPath = assetManager.getClass().getMethod(addAssetPathMethod, String.class);
addAssetPath.invoke(assetManager, apkPath);
Resources pluginRes = new Resources(assetManager,
mContext.getResources().getDisplayMetrics(),
mContext.getResources().getConfiguration());
pluginApk = new PluginApk(pluginRes);
pluginApk.classLoader = createDexClassLoader(apkPath);
} catch (Il