上一篇分析中我們分析了Replugin框架Host端的一些核心概念俗冻,還梳理了Activity
啟動(dòng)的流程,但是有兩個(gè)重要部分沒有提及或者詳細(xì)講述牍颈,那就是Plugin的加載過程迄薄,Plugin端的初始化,所以本篇會(huì)重點(diǎn)看看這兩個(gè)方面的內(nèi)容煮岁。
目錄
- Plugin加載的詳細(xì)過程
- Plugin端初始化
- Plugin中啟動(dòng)
Activity
Plugin加載的詳細(xì)過程
要講解這個(gè)過程讥蔽,我們得從Plugin.doLoad
函數(shù)開始,在上一篇分析中其實(shí)已經(jīng)提到過画机,只是當(dāng)時(shí)并沒有分析它的細(xì)節(jié)冶伞。這個(gè)函數(shù)做了三件事情:
- 釋放插件文件到相應(yīng)的目錄,比如so庫步氏,dex文件
- 加載dex文件响禽,比如組件信息,組件屬性,資源等
- 要運(yùn)行Plugin芋类,還需要初始化Plugin的運(yùn)行環(huán)境
釋放文件這一步很簡(jiǎn)單隆嗅,就是將APK包打開以后將相應(yīng)的文件放到不同的目錄中,有興趣的同學(xué)可以取跟以下代碼侯繁,這里就不贅述了胖喳。
來看看dex文件的加載,這是通過Loader.loadDex
函數(shù)實(shí)現(xiàn)的巫击。這個(gè)方法比較長(zhǎng)禀晓,我們拆開來看。
第一步坝锰,獲取PackageInfo
并緩存插件相關(guān)的信息粹懒,比如組件,組件屬性顷级,資源等凫乖,下次就可以直接從緩存中讀取。
if (mPackageInfo == null) {
// 通過PackageManager獲取PackageInfo
mPackageInfo = pm.getPackageArchiveInfo(mPath,
PackageManager.GET_ACTIVITIES | PackageManager.GET_SERVICES | PackageManager.GET_PROVIDERS | PackageManager.GET_RECEIVERS | PackageManager.GET_META_DATA);
......
// 添加針對(duì)SO庫的加載
PluginInfo pi = mPluginObj.mInfo;
File ld = pi.getNativeLibsDir();
mPackageInfo.applicationInfo.nativeLibraryDir = ld.getAbsolutePath();
// 緩存表: pkgName -> pluginName
synchronized (Plugin.PKG_NAME_2_PLUGIN_NAME) {
Plugin.PKG_NAME_2_PLUGIN_NAME.put(mPackageInfo.packageName, mPluginName);
}
// 緩存表: pluginName -> fileName
synchronized (Plugin.PLUGIN_NAME_2_FILENAME) {
Plugin.PLUGIN_NAME_2_FILENAME.put(mPluginName, mPath);
}
// 緩存表: fileName -> PackageInfo
synchronized (Plugin.FILENAME_2_PACKAGE_INFO) {
Plugin.FILENAME_2_PACKAGE_INFO.put(mPath, new WeakReference<PackageInfo>(mPackageInfo));
}
}
第二步弓颈,解析組件信息帽芽,注冊(cè)Plugin的Manifest中聲明的BroadcastReceiver。
if (mComponents == null) {
mComponents = new ComponentList(mPackageInfo, mPath, mPluginObj.mInfo);
// 動(dòng)態(tài)注冊(cè)插件中聲明的 receiver
regReceivers();
// 緩存表Components
synchronized (Plugin.FILENAME_2_COMPONENT_LIST) {
Plugin.FILENAME_2_COMPONENT_LIST.put(mPath, new WeakReference<>(mComponents));
}
......
}
這一步用到了ComponentList
類翔冀,在它的構(gòu)造函數(shù)中完成了對(duì)Plugin的Manifest文件的解析导街,調(diào)用的是Minifestparser
類。有興趣的同學(xué)可以跟一跟這里的代碼纤子,并不復(fù)雜搬瑰。讀取到Manifest文件以后,使用ManifestParser.INS.parse
函數(shù)調(diào)用XmlHandler
解析控硼,把解析以后的信息緩存到ManifestParser
中泽论。
public void parse(PluginInfo pli, String manifestStr) {
XmlHandler handler = parseManifest(manifestStr);
Map<String, List<IntentFilter>> activityFilterMap = new HashMap<>();
putToMap(mPluginActivityInfoMap, activityFilterMap, pli);
parseComponent(pli.getName(), activityFilterMap, handler.getActivities(), mActivityActionPluginsMap);
Map<String, List<IntentFilter>> serviceFilterMap = new HashMap<>();
putToMap(mPluginServiceInfoMap, serviceFilterMap, pli);
parseComponent(pli.getName(), serviceFilterMap, handler.getServices(), mServiceActionPluginsMap);
Map<String, List<IntentFilter>> receiverFilterMap = new HashMap<>();
putToMap(mPluginReceiverInfoMap, receiverFilterMap, pli);
parseComponent(pli.getName(), receiverFilterMap, handler.getReceivers(), null);
}
第三步,獲取Plugin的資源卡乾,先查找緩存翼悴,如果找不到就通過PackageManager
創(chuàng)建Resources
對(duì)象,但這里做了一個(gè)多余的賦值操作是為了修復(fù)一個(gè)BUG幔妨,不必在意鹦赎。最后將資源對(duì)象緩存下來。
mPkgResources = Plugin.queryCachedResources(mPath);
if (mPkgResources == null) {
try {
if (BuildConfig.DEBUG) {
// 如果是Debug模式的話误堡,防止與Instant Run沖突钙姊,資源重新New一個(gè)
Resources r = pm.getResourcesForApplication(mPackageInfo.applicationInfo);
mPkgResources = new Resources(r.getAssets(), r.getDisplayMetrics(), r.getConfiguration());
} else {
mPkgResources = pm.getResourcesForApplication(mPackageInfo.applicationInfo);
}
} catch (NameNotFoundException e) {
return false;
}
// 緩存Resources
synchronized (Plugin.FILENAME_2_RESOURCES) {
Plugin.FILENAME_2_RESOURCES.put(mPath, new WeakReference<>(mPkgResources));
}
}
第四步,注意咯埂伦,這里就是創(chuàng)建Plugin的PluginDexClassLoader
的地方煞额,將它緩存起來,不必每次都創(chuàng)建一個(gè)新的。
if (mClassLoader == null) {
String out = mPluginObj.mInfo.getDexParentDir().getPath();
......
String soDir = mPackageInfo.applicationInfo.nativeLibraryDir;
// 創(chuàng)建PluginDexClassLoader對(duì)象
mClassLoader = RePlugin.getConfig().getCallbacks().createPluginClassLoader(mPath, out, soDir, parent);
......
synchronized (Plugin.FILENAME_2_DEX) {
Plugin.FILENAME_2_DEX.put(mPath, new WeakReference<>(mClassLoader)); // 緩存ClassLoader
}
}
第五步膊毁, 為Plugin創(chuàng)建一個(gè)全局的PluginContext
胀莹,并用上面創(chuàng)建的ClassLoader
以及Resources
作為參數(shù)。而這個(gè)PluginContext
對(duì)象會(huì)被賦值給Plugin的Application
對(duì)象(后面會(huì)講到)婚温。其實(shí)每一個(gè)Plugin的Activity
都會(huì)創(chuàng)建一個(gè)PluginContext
對(duì)象描焰,并使用相同的ClassLoader
和Resources
,因此在Plugin中就可以加載相關(guān)的類和使用資源了栅螟,跟原生程序一樣荆秦。
mPkgContext = new PluginContext(mContext, android.R.style.Theme, mClassLoader, mPkgResources, mPluginName, this);
到這里,Plugin的加載就算是全部完成了力图!
Plugin端初始化
Dex文件加載完成以后步绸,要運(yùn)行Plugin還需要初始化Plugin的運(yùn)行環(huán)境相關(guān)的類,在Plugin.doLoad
的最后階段調(diào)用了loadEntryLocked
函數(shù)吃媒,這個(gè)函數(shù)負(fù)責(zé)初始化Plugin的運(yùn)行環(huán)境瓤介。
private boolean loadEntryLocked(PluginCommImpl manager) {
if (mDummyPlugin) {
......
} else {
......
} else if (mLoader.loadEntryMethod3()) { // 通過反射拿到Entry的create函數(shù)
if (!mLoader.invoke2(manager)) {
return false;
}
} else {
return false;
}
}
return true;
}
Loader.loadEntryMethod3
通過反射將Plugin中的Entry
類的create
函數(shù)對(duì)象得到并保存在mCreateMethod2
中。注意這里的mClassLoader
是插件的PluginDexClassLoader
赘那,所以才能得到插件中的類刑桑。
final boolean loadEntryMethod3() {
try {
String className = Factory.REPLUGIN_LIBRARY_ENTRY_PACKAGE_PREFIX + "." + Factory.PLUGIN_ENTRY_CLASS_NAME;
Class<?> c = mClassLoader.loadClass(className);
mCreateMethod2 = c.getDeclaredMethod(Factory.PLUGIN_ENTRY_EXPORT_METHOD_NAME, Factory.PLUGIN_ENTRY_EXPORT_METHOD2_PARAMS);
} catch (Throwable e) {
}
return mCreateMethod2 != null;
}
接著Loader.invoke2
函數(shù)會(huì)用反射的方式調(diào)用上面拿到的create
函數(shù)。
final boolean invoke2(PluginCommImpl x) {
try {
IBinder manager = null;
final ClassLoader classLoader = getClass().getClassLoader();
IBinder b = (IBinder) mCreateMethod2.invoke(null, mPkgContext, classLoader, manager); // 調(diào)用create函數(shù)
......
mBinderPlugin = new ProxyPlugin(b);
mPlugin = mBinderPlugin;
} catch (Throwable e) {
return false;
}
return true;
}
注意募舟,這里我們將會(huì)進(jìn)入到Plugin端的代碼中祠斧,來看看Entry.create
做了什么。這里有一個(gè)參數(shù)是ClassLoader
拱礁,這個(gè)ClassLoader
是Host中的RepluginClassLoader
琢锋,有了它,Plugin才能找到Host當(dāng)中的類并調(diào)用這些類的方法觅彰。
public static final IBinder create(Context context, ClassLoader cl, IBinder manager) {
RePluginFramework.init(cl); // 初始化一些類,這些類用于調(diào)用Host中類的方法
RePluginEnv.init(context, cl, manager);
return new IPlugin.Stub() {
@Override
public IBinder query(String name) throws RemoteException {
return RePluginServiceManager.getInstance().getService(name);
}
};
}
這里RepluginFramework.init
初始化了一些代理類钮热,這些代理類可以在Plugin中調(diào)用Host中的函數(shù)填抬。
private static boolean initLocked(ClassLoader cl) {
......
try {
RePluginInternal.ProxyRePluginInternalVar.initLocked(cl);
RePlugin.ProxyRePluginVar.initLocked(cl);
PluginLocalBroadcastManager.ProxyLocalBroadcastManagerVar.initLocked(cl);
PluginProviderClient.ProxyRePluginProviderClientVar.initLocked(cl);
PluginServiceClient.ProxyRePluginServiceClientVar.initLocked(cl);
IPC.ProxyIPCVar.initLocked(cl);
mHostInitialized = true;
} catch (final Throwable e) {
}
return mHostInitialized;
}
我們拿RePlugin.ProxyRePluginVar.initLocked
作為例子來講解。這里還是通過反射隧期,在Plugin中去獲取Host中Replugin
的相關(guān)方法并保存在一系列的MethodInvoker
對(duì)象中飒责,比如Replugin.install
,Replugin.preload
仆潮,Replugin.startActivity
等宏蛉。
static void initLocked(final ClassLoader classLoader) {
// 初始化Replugin的相關(guān)方法
final String rePlugin = "com.qihoo360.replugin.RePlugin";
install = new MethodInvoker(classLoader, rePlugin, "install", new Class<?>[]{String.class});
preload = new MethodInvoker(classLoader, rePlugin, "preload", new Class<?>[]{String.class});
......
startActivity = new MethodInvoker(classLoader, rePlugin, "startActivity", new Class<?>[]{Context.class, Intent.class});
startActivity2 = new MethodInvoker(classLoader, rePlugin, "startActivity", new Class<?>[]{Context.class, Intent.class, String.class, String.class});
......
}
}
當(dāng)我們?cè)赑lugin中使用Replugin.startActivity
時(shí),實(shí)際上是通過MethodInvoker.call
函數(shù)去執(zhí)行Host中Replugin
對(duì)應(yīng)的startActivity
函數(shù)性置。所以Plugin中的Replugin類的實(shí)現(xiàn)就只是一個(gè)空殼代理而已拾并,你可以看到它的實(shí)現(xiàn)如下:
public static boolean startActivity(Context context, Intent intent) {
if (!RePluginFramework.mHostInitialized) {
return false;
}
try { // 用反射的方式調(diào)用Host中Replugin的startActivity函數(shù)來啟動(dòng)Activity
Object obj = ProxyRePluginVar.startActivity.call(null, context, intent);
if (obj != null) {
return (Boolean) obj;
}
} catch (Exception e) {
if (LogDebug.LOG) {
e.printStackTrace();
}
}
return false;
}
看到這里,你就跟上一篇中 插件Activity啟動(dòng)流程 這一節(jié)連起來啦!
插件中啟動(dòng)Activity
Android中Activity
和Context
都有startActivity
函數(shù)嗅义,為了在插件中能正常啟動(dòng)Activity
屏歹,我們要將這些函數(shù)都屏蔽掉,轉(zhuǎn)而使用Replugin
提供的startActivity
之碗。
為什么一定要用Replugin提供startActivity方法呢蝙眶?因?yàn)槲覀儾寮慕M件在Host的Manifest中是沒有聲明的,
只能通過坑位來啟動(dòng)褪那,而啟動(dòng)坑位的動(dòng)作只能在Host中完成幽纷。
Replugin
是怎么做的呢?來看看吧博敬。
將官方Demo中的插件APK反編譯出來友浸,會(huì)發(fā)現(xiàn)所有繼承了Activity
的類都被強(qiáng)制修改成繼承PluginActivity
,這個(gè)工作是由replugin-plugin-gradle
在編譯階段完成的冶忱。
實(shí)際上在
replugin-plugin-lib
中的com.qihoo360.replugin.loader.a
包中尾菇,你可以找到更多的Activity關(guān)的類。
首先PluginActivity
重寫了startActivity
函數(shù)囚枪,并在其中通過反射調(diào)用Host中的Replugin.startActivity
函數(shù)派诬,這樣就能做到在插件中啟動(dòng)Activity
了。這里的反射調(diào)用指的就是調(diào)用前面講過的RePlugin.ProxyRePluginVar
類在初始化時(shí)得到的函數(shù)链沼。
public void startActivity(Intent intent) {
if (RePluginInternal.startActivity(this, intent)) {
return;
}
super.startActivity(intent);
}
然后默赂,PluginActivity
重寫了attachBaseContext
函數(shù),同樣在代理類中通過反射調(diào)用Host中Factory2.createActivityContext
函數(shù)創(chuàng)建一個(gè)PluginContext
對(duì)象括勺,并用這個(gè)對(duì)象替換掉原生的Context
對(duì)象缆八。
protected void attachBaseContext(Context newBase) {
newBase = RePluginInternal.createActivityContext(this, newBase); //創(chuàng)建PluginContext對(duì)象
super.attachBaseContext(newBase); //替換原生的Context
}
那么這個(gè)PluginContext
又做了什么呢?原來PluginContext
也重寫了startActivity
函數(shù)疾捍,并且在其中調(diào)用了Factory2.startActivity
函數(shù)奈辰,接著又會(huì)調(diào)用PluginLibraryInternalProxy.startActivity
。后面的事情如果讀過第一篇分析文章乱豆,你應(yīng)該都知道啦奖恰!
@Override
public void startActivity(Intent intent) {
if (!Factory2.startActivity(this, intent)) {
if (mContextInjector != null) {
mContextInjector.startActivityBefore(intent);
}
super.startActivity(intent);
if (mContextInjector != null) {
mContextInjector.startActivityAfter(intent);
}
}
}
到這里,你無論是使用getContext().startActivity
還是直接在Activity
中使用startActivity
都會(huì)走Replugin的啟動(dòng)流程宛裕。但是事情并沒有完瑟啃,還有一個(gè)地方需要修改,那就是Application
中的Context
揩尸。
PluginApplicationClient
為插件創(chuàng)建了Application
對(duì)象蛹屿,在PluginApplicationClient.callAttachBaseContext
函數(shù)中通過反射調(diào)用Applicaiton.attach
函數(shù),用一個(gè)PluginContext
對(duì)象替換掉原來的Context
對(duì)象岩榆。
這里的PluginContext對(duì)象就是在前面講到的Dex加載過程中創(chuàng)建的错负,作為Plugin的全局Context坟瓢。
public void callAttachBaseContext(Context c) {
try {
sAttachBaseContextMethod.setAccessible(true);
sAttachBaseContextMethod.invoke(mApplication, c); // 將PluginContext對(duì)象傳遞給Application對(duì)象
} catch (Throwable e) {
if (BuildConfig.DEBUG) {
e.printStackTrace();
}
}
}
OK,到這里我們?cè)诓寮芯湍苷5氖褂肦eplugin的啟動(dòng)流程在插件中啟動(dòng)Activity了JT芈獭!
注意
實(shí)際上在
Plugin
中調(diào)用getApplication
獲取到的是Host的Application
對(duì)象油航,因?yàn)閯e忘了我們是利用坑位原理運(yùn)行插件組件的崭庸,所以在插件中我們用到的都是Host的Application。但是萬一Plugin的Application中有客戶定制的任何動(dòng)作要完成呢谊囚?所以在加載 Plugin之后怕享,Host也會(huì)通過反射創(chuàng)建Plugin的Application對(duì)象,并反射調(diào)用它的attach
和create
函數(shù)镰踏。Plugin.load
final boolean load(int load, boolean useCache) { PluginInfo info = mInfo; boolean rc = loadLocked(load, useCache); //Plugin加載完成 if (load == LOAD_APP && rc) { callApp(); // 關(guān)于Plugin的Application的操作都在這里了 } ...... return rc; }
Plugin.callAppLocked
private void callAppLocked() { // 獲取并調(diào)用Application的幾個(gè)核心方法 if (!mDummyPlugin) { ...... mApplicationClient = PluginApplicationClient.getOrCreate( mInfo.getName(), mLoader.mClassLoader, mLoader.mComponents, mLoader.mPluginObj.mInfo); // 創(chuàng)建Plugin的Appication對(duì)象 if (mApplicationClient != null) { // 調(diào)用Application.attach mApplicationClient.callAttachBaseContext(mLoader.mPkgContext); mApplicationClient.callOnCreate(); //調(diào)用Application.onCreate } } else { } }
總結(jié):
通過這兩篇文章函筋,我們總結(jié)一下一個(gè)插件Activity
啟動(dòng)的流程大致是:
-
加載目標(biāo)
Activity
信息開始查找
Activity
信息 —> 找到對(duì)應(yīng)的Pugin
信息 —> 解壓APK文件 —> 加載Dex文件內(nèi)容 —> 創(chuàng)建Application
對(duì)象 —> 創(chuàng)建Entry
對(duì)象初始化Plugin環(huán)境 -
尋找坑位
查找坑位 —> 將目標(biāo)
Activity
與找到的坑位以ActivityState
的形式緩存在PluginContainer
s中 —> 啟動(dòng)坑位Activity
—> 系統(tǒng)調(diào)用RepluginClassLoader
—> 調(diào)用PluginDexClassLoader
加載坑位Activity
類 —> 通過PluginContainers
找到坑位對(duì)應(yīng)的目標(biāo)Activity
類 —> 系統(tǒng)調(diào)用PluginActivity
的attachBaseContext
函數(shù) —> 創(chuàng)建PluginContext
對(duì)象并替換 —> 目標(biāo)Activity
的正常啟動(dòng)流程(onCreate
,onStart
奠伪,onResume
.....)?
如果你已經(jīng)讀完了這兩篇分析文章跌帐,但還沒有將Replugin的里面如此多的類的關(guān)系理清楚,那么下面這張圖也許可以幫到你绊率。不過這張圖目前還不完整谨敛,只針對(duì)目前已經(jīng)講到的部分,后面會(huì)逐漸補(bǔ)全滤否。
綠色的部分是replugin-plugin-lib
脸狸,藍(lán)色和紅色部分都是是replugin-host-lib
包中的類,但是藍(lán)色部分是運(yùn)行在UI進(jìn)程中藐俺,而紅色部分是運(yùn)行在Persistent進(jìn)程中炊甲。
綠色部分和藍(lán)色部分之間的調(diào)用都是通過反射來實(shí)現(xiàn)的,所以用類虛線箭頭欲芹,同樣藍(lán)色部分和紅色部分是通過Binder進(jìn)程間通信機(jī)制來調(diào)用的卿啡,也用虛線箭頭表示。
下一篇Replugin 全面解析 (4)我們會(huì)講解Service以及進(jìn)程相關(guān)內(nèi)容菱父!