
原理與背景
如何啟動未安裝的apk(四大組件)里的類。如何加載類、加載資源、管理組件生命周期。
類加載
外部的dex文件,通過DexClassLoader類加載。根據(jù)插件的apk路徑,構(gòu)造對應的類加載器:
/**
* 類加載:
* 外部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),系統(tǒng)對該Activity的回調(diào)都映射到插件Activity。插件Activity都繼承這個PluginActivity,侵入性強。避免侵入性成了二代插件化框架目標。
代理實現(xiàn)
在plugin(module庫library級別)中:
PluginManager類來實現(xiàn)插件的加載,通過Map集合管理所有插件apk。根據(jù)插件apk路徑實現(xiàn)類加載、資源加載、啟動插件activity等。
PluginApk類:是插件apk的實體映射類。
PluginActivity:所有插件都要繼承這個類,這是一個殼子。繼承了Activity的一些方法。
ProxyActivity:系統(tǒng)實際啟動的類,必須在主app中注冊(代理,啟動的時候躲過主app對插件activity注冊檢查),系統(tǒng)通過該類觸發(fā)對應方法。代理給LifeCircleController去具體實現(xiàn)。
LifeCircleController:具體實現(xiàn)類,系統(tǒng)實際啟動的類。
在插件(pluginapp)
只需要繼承plugin(library)中的PluginActivity就行:
MainActivity extends PluginActivity
我是直接生成plugin.apk(debug.apk方便我調(diào)試)。如果直接運行這個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" />
添加權(quán)限:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
啟動插件pluginapp代碼:根據(jù)路徑和包名啟動
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,點擊跳轉(zhuǎn)插件按鈕,發(fā)現(xiàn)成功加載。
demo地址