在我看來(lái)重贺,插件化的核心目的就是將未安裝的apk代碼驾胆,在已經(jīng)安裝的apk中執(zhí)行渠啤,未安裝的apk就是插件剂买。
其實(shí)這個(gè)未安裝的apk也只是一個(gè)有固定格式能識(shí)別的文件而已惠爽,更廣一點(diǎn)其實(shí)所有的apk也都是有固定格式的文件而已,代碼的執(zhí)行靠的還是虛擬機(jī)運(yùn)行java代碼瞬哼,最開(kāi)始還是執(zhí)行main函數(shù)婚肆,只是在函數(shù)中,存在無(wú)限循環(huán)直到應(yīng)用退出而已坐慰。
上文只是個(gè)人目前知識(shí)的一個(gè)理解较性,如有不正確不恰當(dāng)?shù)牡胤秸?qǐng)多多指教。
本文中介紹如何使用占位的方式實(shí)現(xiàn)插件化结胀。
核心原理
首先需要明確一點(diǎn)两残,沒(méi)有安裝的apk不能執(zhí)行其代碼,主要原因有兩點(diǎn):
- 類(lèi)或資源沒(méi)有被加載
- 四大組件沒(méi)有運(yùn)行環(huán)境把跨,就是沒(méi)有上下文對(duì)象(context),沒(méi)有生命周期的
而如果我們使用classloader手動(dòng)加載類(lèi)文件沼死,使用AssetManager手動(dòng)加入資源文件着逐,然后再傳入一個(gè)對(duì)應(yīng)的上下文對(duì)象,那么缺少的東西就都有了,那么是不是就可以運(yùn)行了呢耸别?答案是肯定的健芭,這里講的占位式插件化就是利用這樣的原理。
而這里傳入的對(duì)應(yīng)的上下文和調(diào)用對(duì)應(yīng)的生命周期秀姐,就需要一個(gè)公用的組件來(lái)占位實(shí)現(xiàn)慈迈。
說(shuō)完原理,接著我們看看具體是怎么操作的省有。
實(shí)現(xiàn)
以實(shí)現(xiàn)打開(kāi)插件中Activity為例痒留。
首先需要定義一個(gè)Activity的標(biāo)準(zhǔn),定義需要使用到的生命周期的方法蠢沿,例如:
public interface IActivity {
void setHostActivity(Activity host);
void onCreate(Bundle savedInstanceState);
void onResume();
void onPause();
void onDestroy();
}
其中setHostActivity是把占位的代理Activity的實(shí)例傳給插件中定義的Activity伸头,因?yàn)椴寮袥](méi)有上下文環(huán)境,使用一切Activity相關(guān)的方法都應(yīng)該使用代理Activity的對(duì)應(yīng)方法舷蟀。
然后在插件中實(shí)現(xiàn)該接口并封裝到BaseActivity中恤磷,保存代理Activity的實(shí)例。只要是在插件中使用到Activity的方法時(shí)野宜,都在BaseActivity中重寫(xiě)并使用代理Activity的對(duì)應(yīng)方法扫步,這樣插件端的工作就基本完成了。
然后在代理Activity端匈子,需要先把插件包使用dexClassLoader和assetManager加載進(jìn)來(lái):
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();
}
}
然后在啟動(dòng)Activity其實(shí)就是啟動(dòng)代理Activity河胎,傳入需要打開(kāi)的插件Activity的全類(lèi)名
PackageManager packageManager = getPackageManager();
PackageInfo packageInfo = packageManager.getPackageArchiveInfo(pluginApkPath, PackageManager.GET_ACTIVITIES);
ActivityInfo activityInfo = packageInfo.activities[0];//拿的第一個(gè)Activity,真正使用時(shí)這個(gè)地方應(yīng)該去打開(kāi)指定的activity
Intent intent = new Intent(this, ProxyActivity.class);
intent.putExtra(ProxyActivity.KEY_CLASS_NAME, activityInfo.name);
startActivity(intent);
在ProxyActivity代理Activity中旬牲,重寫(xiě)
@Override
public ClassLoader getClassLoader() {
return PluginManager.get().getDexClassLoader();
}
@Override
public Resources getResources() {
return PluginManager.get().getPluginResource();
}
在onCreate方法中加載插件的class創(chuàng)建出插件activity對(duì)象仿粹,并在對(duì)應(yīng)的生命周期中都調(diào)用插件activity對(duì)象的對(duì)應(yīng)生命周期,當(dāng)然這里的這些生命周期需要在標(biāo)準(zhǔn)中定義好原茅。以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);
}
而在上文說(shuō)到的插件Activity中定義的BaseActivity中重寫(xiě)的方法都需要使用這個(gè)代理activity的實(shí)例吭历。例如:
@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);
}
這樣基本就可以實(shí)現(xiàn)了,需要注意的一點(diǎn)就是這個(gè)宿主Activity不要繼承自AppCompatActivity擂橘,目前還不知道原因?yàn)槭裁凑也坏絤DecorContentParent晌区,調(diào)用mDecorContentParent.setWindowCallback(this.getWindowCallback());會(huì)報(bào)空指針。只要繼承Activity就可以解決通贞。
還有其他的對(duì)于Service朗若,BroadcastReceiver也是同樣的方法,先占位昌罩,再啟動(dòng)即可哭懈。內(nèi)部的跳轉(zhuǎn)也調(diào)用宿主的跳轉(zhuǎn)方法,然后宿主中使用代理的Activity或者Service或者BroadcastReceiver來(lái)代替茎用。
任玉剛大神寫(xiě)的Apk動(dòng)態(tài)加載框架就是這個(gè)原理遣总。