原理与背景
如何启动未安装的apk(四大组件)里的类。如何加载类、加载资源、管理组件生命周期。
类加载
外部的dex文件,通过DexClassLoader类加载。根据插件的apk路径,构造对应的类加载器:
/**
* 类加载:
* 外部dex文件,通过 DexClassLoader 类加载。
* @param apkPath apk路径
* @return DexClassLoader对象
*/
private DexClassLoader createDexClassLoader(String apkPath) {
File dexOutputDir = mContext.getDir("dex", Context.MODE_PRIVATE);
return new DexClassLoader(apkPath, dexOutputDir.getAbsolutePath(), null, mContext.getClassLoader());
}
资源加载
通过Resource对象加载资源,把插件apk里的资源加载到AssetManager中:
/**
*
* @param apkPath apk路径
* @return pluginApk
*/
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 (IllegalAccessException | InstantiationException | NoSuchMethodException | InvocationTargetException e) {
e.printStackTrace();
}
return pluginApk;
}
生命周期
早期dynamic-load-apk采用代理方式,通过空壳Activity作为代理(Proxy),系统对该Activity的回调都映射到插件Activity。插件Activity都继承这个PluginActivity,侵入性强。避免侵入性成了二代插件化框架目标。
代理实现
在plugin(module库library级别)中:
PluginManager类来实现插件的加载,通过Map集合管理所有插件apk。根据插件apk路径实现类加载、资源加载、启动插件activity等。
PluginApk类:是插件apk的实体映射类。
PluginActivity:所有插件都要继承这个类,这是一个壳子。继承了Activity的一些方法。
ProxyActivity:系统实际启动的类,必须在主app中注册(代理,启动的时候躲过主app对插件activity注册检查),系统通过该类触发对应方法。代理给LifeCircleController去具体实现。
LifeCircleController:具体实现类,系统实际启动的类。
在插件(pluginapp)
只需要继承plugin(library)中的PluginActivity就行:
MainActivity extends PluginActivity
我是直接生成plugin.apk(debug.apk方便我调试)。如果直接运行这个apk会包错
java.lang.NullPointerException: Attempt to invoke virtual method 'boolean android.content.res.Resources.getBoolean(int)'
但是不影响生成apk。把这个plugin.apk放到sdcard/plugins文件下。方便下面主app去加载。
在主工程(app)中:
注册:
<!--注册 躲过插件activity注册的检查 -->
<activity android:name="com.george.plugin.ProxyActivity" />
添加权限:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
启动插件pluginapp代码:根据路径和包名启动
public class MainActivity extends Activity {
public final static String PLUGIN_NAME = "plugin.apk";
public final static String PLUGIN_PACKAGE_NAME = "com.george.pluginapp";
public final static String PLUGIN_CLAZZ_NAME = "com.george.pluginapp.MainActivity";
private PluginManager mPluginManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.tv_content).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent();
intent.putExtra(Constants.PACKAGE_NAME, PLUGIN_PACKAGE_NAME);
intent.putExtra(Constants.PLUGIN_CLASS_NAME, PLUGIN_CLAZZ_NAME);
mPluginManager.startActivity(intent);
}
});
Utils.verifyStoragePermissions(this);
PluginManager.init(getApplicationContext());// 初始化插件加载器
mPluginManager = PluginManager.getInstance();
String pluginApkPath = Environment.getExternalStorageDirectory() + File.separator + "plugins" + File.separator + PLUGIN_NAME;// 插件apk路径
VLog.log("can read: " + Environment.getExternalStorageDirectory().canRead());
VLog.log(pluginApkPath);
mPluginManager.loadApk(pluginApkPath);// 插件apk加载进主app中
}
}
打开主app,点击跳转插件按钮,发现成功加载。
demo地址
网友评论