在我看來,插件化的核心目的就是將未安裝的apk代碼,在已經(jīng)安裝的apk中執(zhí)行,未安裝的apk就是插件。
其實這個未安裝的apk也只是一個有固定格式能識別的文件而已,更廣一點其實所有的apk也都是有固定格式的文件而已,代碼的執(zhí)行靠的還是虛擬機運行java代碼,最開始還是執(zhí)行main函數(shù),只是在函數(shù)中,存在無限循環(huán)直到應用退出而已。
上文只是個人目前知識的一個理解,如有不正確不恰當?shù)牡胤秸埗喽嘀附獭?/em>
本文中介紹如何使用占位的方式實現(xiàn)插件化。
核心原理
首先需要明確一點,沒有安裝的apk不能執(zhí)行其代碼,主要原因有兩點:
- 類或資源沒有被加載
- 四大組件沒有運行環(huán)境,就是沒有上下文對象(context),沒有生命周期的
而如果我們使用classloader手動加載類文件,使用AssetManager手動加入資源文件,然后再傳入一個對應的上下文對象,那么缺少的東西就都有了,那么是不是就可以運行了呢?答案是肯定的,這里講的占位式插件化就是利用這樣的原理。
而這里傳入的對應的上下文和調用對應的生命周期,就需要一個公用的組件來占位實現(xiàn)。
說完原理,接著我們看看具體是怎么操作的。
實現(xiàn)
以實現(xiàn)打開插件中Activity為例。
首先需要定義一個Activity的標準,定義需要使用到的生命周期的方法,例如:
public interface IActivity {
void setHostActivity(Activity host);
void onCreate(Bundle savedInstanceState);
void onResume();
void onPause();
void onDestroy();
}
其中setHostActivity是把占位的代理Activity的實例傳給插件中定義的Activity,因為插件中沒有上下文環(huán)境,使用一切Activity相關的方法都應該使用代理Activity的對應方法。
然后在插件中實現(xiàn)該接口并封裝到BaseActivity中,保存代理Activity的實例。只要是在插件中使用到Activity的方法時,都在BaseActivity中重寫并使用代理Activity的對應方法,這樣插件端的工作就基本完成了。
然后在代理Activity端,需要先把插件包使用dexClassLoader和assetManager加載進來:
public void loadPlugin(String path) {
try {
File pluginApk = new File(path);
if (!pluginApk.exists()) {
Log.d("ljw >>>", "loadPlugin: 插件包" + path + "不存在");
return;
}
File optimizedDirectory = context.getDir("optimizedDirectory", Context.MODE_PRIVATE);
dexClassLoader = new DexClassLoader(path, optimizedDirectory.getAbsolutePath(),
null, context.getClassLoader());
AssetManager assetManager = AssetManager.class.newInstance();
//private int addAssetPathInternal(String path, boolean overlay, boolean appAsLib) {
Method addAssetPathMethod = AssetManager.class.getDeclaredMethod("addAssetPath", String.class);
addAssetPathMethod.invoke(assetManager, path);
Resources resources = context.getResources();
pluginResource = new Resources(assetManager, resources.getDisplayMetrics(), resources.getConfiguration());
} catch (Exception e) {
e.printStackTrace();
}
}
然后在啟動Activity其實就是啟動代理Activity,傳入需要打開的插件Activity的全類名
PackageManager packageManager = getPackageManager();
PackageInfo packageInfo = packageManager.getPackageArchiveInfo(pluginApkPath, PackageManager.GET_ACTIVITIES);
ActivityInfo activityInfo = packageInfo.activities[0];//拿的第一個Activity,真正使用時這個地方應該去打開指定的activity
Intent intent = new Intent(this, ProxyActivity.class);
intent.putExtra(ProxyActivity.KEY_CLASS_NAME, activityInfo.name);
startActivity(intent);
在ProxyActivity代理Activity中,重寫
@Override
public ClassLoader getClassLoader() {
return PluginManager.get().getDexClassLoader();
}
@Override
public Resources getResources() {
return PluginManager.get().getPluginResource();
}
在onCreate方法中加載插件的class創(chuàng)建出插件activity對象,并在對應的生命周期中都調用插件activity對象的對應生命周期,當然這里的這些生命周期需要在標準中定義好。以onCreate為例:
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String className = getIntent().getStringExtra(KEY_CLASS_NAME);
iActivity = PluginManager.get().loadActivityClass(this, className);
if (iActivity == null) {
return;
}
iActivity.onCreate(savedInstanceState);
}
而在上文說到的插件Activity中定義的BaseActivity中重寫的方法都需要使用這個代理activity的實例。例如:
@Override
public void setContentView(int layoutResID) {
host.setContentView(layoutResID);
}
@Override
public <T extends View> T findViewById(int id) {
return host.findViewById(id);
}
@Override
public void startActivity(Intent intent) {
host.startActivity(intent);
}
這樣基本就可以實現(xiàn)了,需要注意的一點就是這個宿主Activity不要繼承自AppCompatActivity,目前還不知道原因為什么找不到mDecorContentParent,調用mDecorContentParent.setWindowCallback(this.getWindowCallback());會報空指針。只要繼承Activity就可以解決。
還有其他的對于Service,BroadcastReceiver也是同樣的方法,先占位,再啟動即可。內(nèi)部的跳轉也調用宿主的跳轉方法,然后宿主中使用代理的Activity或者Service或者BroadcastReceiver來代替。
任玉剛大神寫的Apk動態(tài)加載框架就是這個原理。