Activity作為四大組件中最重要的組件,在Replugin中對它的支持的架構(gòu)設(shè)計(jì)也是最復(fù)雜的,所以本篇分析我們就來看看Activity的啟動流程。
以下這張圖簡要的畫出類Activity啟動的過程,當(dāng)然簡化了一些流程:
-
Pmbase
根據(jù)Intent
找到對應(yīng)的插件 - 分配坑位
Activity
,與插件中的Activity
建立一對一的關(guān)系并保存在PluginContainer
中 - 讓系統(tǒng)啟動坑位
Activity
,因?yàn)樗窃贛anifest中注冊過的 - Android系統(tǒng)會嘗試使用
RepluginClassLoader
加載坑位Activity
的Class
對象 -
RepluginClassLoader
通過建立的對應(yīng)關(guān)系找到插件Activity
,并使用PluginDexClassLoader
加載插件Activity
的Class對象并返回 - Android系統(tǒng)就使用這個(gè)插件中的
Activity
的Class對象來運(yùn)行生命周期函數(shù)
Android系統(tǒng)就是這樣被欺騙了!
啟動一個(gè)Activity的入口函數(shù)是
Replugin.startActivity()
,然后調(diào)用Factory.startActivityWithNoInjectCN
,再經(jīng)過PluginCommImpl.startActivivty()
,最終來到PluginLibraryInternalProxy.startActivity()
,這里將是真正開始工作的地方,會分為以下幾個(gè)步驟:
-
如果有必要,需要先下載插件
下載過程會通過回調(diào)讓用戶去實(shí)現(xiàn),比如顯示進(jìn)度,安裝等。
if (download) { if (PluginTable.getPluginInfo(plugin) == null) { if (isNeedToDownload(context, plugin)) { return RePlugin.getConfig().getCallbacks().onPluginNotExistsForActivity(context, plugin, intent, process); } } }
-
檢查插件狀態(tài)
如果插件狀態(tài)不正確,或者首次加載大插件,會通過回調(diào)讓用戶處理,用戶可以可以在回調(diào)里定制自己的行為,比如彈出提示框,加載進(jìn)度條等。
if (PluginStatusController.getStatus(plugin) < PluginStatusController.STATUS_OK) { return RePlugin.getConfig().getCallbacks().onPluginNotExistsForActivity(context, plugin, intent, process); } if (!RePlugin.isPluginDexExtracted(plugin)) { PluginDesc pd = PluginDesc.get(plugin); if (pd != null && pd.isLarge()) { return RePlugin.getConfig().getCallbacks().onLoadLargePluginForActivity(context, plugin, intent, process); } }
-
尋找坑位,啟動坑位Activity
調(diào)用在
PluginLibraryInternalProxy.startActivity()
中調(diào)用PluginCommImpl.loadPluginActivity
來尋找坑位Activity。請注意注釋中的分支C,如果是第一次去獲取信息,會首先去加載插件的Dex文件以及資源等,并創(chuàng)建PluginDexClassLoader。這個(gè)分支我們在后面來講解。
public ComponentName loadPluginActivity(Intent intent, String plugin, String activity, int process) { ActivityInfo ai = null; String container = null; PluginBinderInfo info = new PluginBinderInfo(PluginBinderInfo.ACTIVITY_REQUEST); try { ai = getActivityInfo(plugin, activity, intent); //分支C:獲取 ActivityInfo // 根據(jù) activity 的 processName,選擇進(jìn)程 ID 標(biāo)識 if (ai.processName != null) { process = PluginClientHelper.getProcessInt(ai.processName); } // 容器選擇(啟動目標(biāo)進(jìn)程,如果有必要的話,一般默認(rèn)會使用UI進(jìn)程) IPluginClient client = MP.startPluginProcess(plugin, process, info); ...... // 遠(yuǎn)程分配坑位 container = client.allocActivityContainer(plugin, process, ai.name, intent); } catch (Throwable e) { } PmBase.cleanIntentPluginParams(intent); ...... return new ComponentName(IPC.getPackageName(), container); }
來重點(diǎn)看看坑位分配,這是一個(gè)遠(yuǎn)程調(diào)用,調(diào)用了Persistent進(jìn)程中的
PluginProcessPer.allocActivityContainer
函數(shù),進(jìn)一步調(diào)用bindActivity
函數(shù)。final String bindActivity(String plugin, int process, String activity, Intent intent) { Plugin p = mPluginMgr.loadAppPlugin(plugin); //獲取插件對象 ...... ActivityInfo ai = p.mLoader.mComponents.getActivity(activity); //獲取ActivityInfo ...... String container; // 自定義進(jìn)程 if (ai.processName.contains(PluginProcessHost.PROCESS_PLUGIN_SUFFIX2)) { String processTail = PluginProcessHost.processTail(ai.processName); container = mACM.alloc2(ai, plugin, activity, process, intent, processTail); } else { container = mACM.alloc(ai, plugin, activity, process, intent); } ...... return container; }
這里
mACM.alloc2
調(diào)用allocLocked
函數(shù)真正的執(zhí)行了坑位分配的任務(wù)。這段代碼簡單明了,注意坑位找好以后會返回一個(gè)AcitivtyState
對象,這里面保存了坑位Activity
和真實(shí)要啟動的Activity
之間的對應(yīng)關(guān)系。并且這個(gè)對應(yīng)關(guān)系會被保存起來,在RepluginClassLoader
在加載類的時(shí)候會被拿出來使用,以獲取要運(yùn)行的Activity
的class
對象。private final ActivityState allocLocked(ActivityInfo ai, HashMap<String, ActivityState> map, String plugin, String activity, Intent intent) { // 首先找上一個(gè)活的,或者已經(jīng)注冊的,避免多個(gè)坑到同一個(gè)activity的映射 for (ActivityState state : map.values()) { if (state.isTarget(plugin, activity)) { return state; } } // 新分配:找空白的,第一個(gè) for (ActivityState state : map.values()) { if (state.state == STATE_NONE) { state.occupy(plugin, activity); return state; } } ActivityState found; // 重用:則找最老的那個(gè) found = null; for (ActivityState state : map.values()) { if (!state.hasRef()) { if (found == null) { found = state; } else if (state.timestamp < found.timestamp) { found = state; } } } if (found != null) { found.occupy(plugin, activity); return found; } // 強(qiáng)擠:最后一招,擠掉:最老的那個(gè) found = null; for (ActivityState state : map.values()) { if (found == null) { found = state; } else if (state.timestamp < found.timestamp) { found = state; } } if (found != null) { found.finishRefs(); found.occupy(plugin, activity); return found; } return null; }
坑位找到啦!
PluginLibraryInternalProxy.startActivity()
中開始啟動坑位Activity,就在分支D的位置,這個(gè)分支我們稍微延后一點(diǎn)來展開。public boolean startActivity(Context context, Intent intent, String plugin, String activity, int process, boolean download) { ...... ComponentName cn = mPluginMgr.mLocal.loadPluginActivity(intent, plugin, activity, process); // 找到坑位組件 ...... // 將Intent指向到“坑位” intent.setComponent(cn); ...... context.startActivity(intent); //分支D: 啟動坑位Activity return true; }
看到這里你一定會疑惑,難道這樣插件的Activity就啟動起來啦嗎?這啟動的明明就是一個(gè)坑位Activity???別著急,接著就是前面一直強(qiáng)調(diào)的唯一hook點(diǎn)發(fā)揮作用的時(shí)候啦?。?/p>
-
Dex的加載以及Activity的加載啟動
上面有一個(gè)分支C你還記得嗎?我們將它與Activity的加載流程放在一起來講,因?yàn)檫@兩者是緊密相關(guān)的。
先來看分支C。
-
PluginCommImpl.getActivityInfo
調(diào)用PmBase.loadAppPlugin
獲取插件對象,從下面注釋的分支可以看出,Replugin
是支持使用IntentFilter
來啟動組件的,完美支持原生特性,是不是很贊!public ActivityInfo getActivityInfo(String plugin, String activity, Intent intent){ Plugin p = mPluginMgr.loadAppPlugin(plugin); //獲取插件對象 ...... ActivityInfo ai = null; //activity 不為空時(shí),從插件聲明的 Activity 集合中查找 if (!TextUtils.isEmpty(activity)) { ai = p.mLoader.mComponents.getActivity(activity); } else { //activity 為空時(shí),根據(jù) Intent 匹配 ai = IntentMatcherHelper.getActivityInfo(mContext, plugin, intent); } return ai; }
-
PmBase.loadAppPlugin
會最終調(diào)用Plugin.loadLocked()
函數(shù),這個(gè)函數(shù)有兩個(gè)參數(shù),第一個(gè)是加載類型,一共有四種加載類型,在這里使用的是Plugin.LOAD_APP
,因?yàn)檫\(yùn)行插件需要所有的東西。第二個(gè)參數(shù)是是否使用緩存,通常情況下我們會現(xiàn)在緩存中查找插件信息,這樣會更快。只是如果大量插件加載到內(nèi)存會不會占用太多的內(nèi)存,感興趣的同學(xué)可以自己研究研究。這里如果第一次加載失敗,Replugin還會做一次重試,相關(guān)代碼幾乎相同,這里就省略了。
private boolean loadLocked(int load, boolean useCache) { // 這里先處理一下,如果cache命中,省了后面插件提?。ㄈ玑尫臞ar包等)操作,直接返回緩存數(shù)據(jù) if (useCache) { boolean result = loadByCache(load); if (result) { return true; } } ...... boolean rc = doLoad(logTag, context, parent, manager, load); // 真正的加載 if (rc) { try { // 至此,該插件已開始運(yùn)行 PluginManagerProxy.addToRunningPluginsNoThrows(mInfo.getName()); } catch (Throwable e) { } return true; } ...... File odex = mInfo.getDexFile(); if (odex.exists()) { odex.delete(); } rc = doLoad(logTag, context, parent, manager, load); ...... return true; }
-
Plugin.doLoad()
當(dāng)然就是來加載插件的Dex文件,資源,以及so文件等等。private final boolean doLoad(String tag, Context context, ClassLoader parent, PluginCommImpl manager, int load) { if (mLoader == null) { // 中間省略這一段代碼是釋放so文件,請自行閱讀代碼,代碼清晰簡單 ...... // 加載Dex,獲取組件信息 mLoader = new Loader(context, mInfo.getName(), mInfo.getPath(), this); if (!mLoader.loadDex(parent, load)) { return false; } // 在Persistent進(jìn)程中更新插件信息,設(shè)置插件為“使用過的” try { PluginManagerProxy.updateUsedIfNeeded(mInfo.getName(), true); } catch (RemoteException e) { } // 若需要加載Dex,則還同時(shí)需要初始化插件里的Entry對象 if (load == LOAD_APP) { // NOTE Entry對象是可以在任何線程中被調(diào)用到 if (!loadEntryLocked(manager)) { return false; } } } }
-
Loader.loadDex
函數(shù)會獲取Dex中的組件的信息,包括Manifest中的組件屬性,比如進(jìn)程屬性,TaskAffinity
屬性,注冊靜態(tài)廣播等等。但這里值得重點(diǎn)強(qiáng)調(diào)的是之前核心概念里提及的PluginDexClassLoader
終于出現(xiàn)并被初始化了。final boolean loadDex(ClassLoader parent, int load) { try { ...... // 這里省略了一些基本的加載動作 mClassLoader = Plugin.queryCachedClassLoader(mPath); if (mClassLoader == null) { ...... mClassLoader = RePlugin.getConfig().getCallbacks().createPluginClassLoader(mPath, out, soDir, parent); // 創(chuàng)建PluginDexClassLoader ...... } ...... mPkgContext = new PluginContext(mContext, android.R.style.Theme, mClassLoader, mPkgResources, mPluginName, this); // 創(chuàng)建插件的Context對象 } catch (Throwable e) { return false; } return true; }
到此為止,插件Dex的加載算是全部完成,下面還剩最后一步,我們的插件就算真正的啟動運(yùn)行起來了。
-
這里我們要接著前面啟動坑位Activity的地方接著講,要啟動
Activity
首先要去加載對應(yīng)的類,系統(tǒng)會調(diào)用Classloader
的loadClass
方法,這里就是調(diào)用Replugin提供的替代者RepluginClassLoader
的方法。接著又會調(diào)用PMF.loadClass
,其實(shí)就是調(diào)用Pmbase.loadClass
protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException { Class<?> c = null; c = PMF.loadClass(className, resolve); if (c != null) { return c; } try { c = mOrig.loadClass(className); // 如果上面沒有找到,那就在Host當(dāng)中找 return c; } catch (Throwable e) { } return super.loadClass(className, resolve); }
-
PmBase.loadClass
看起來代碼很多,對于Activity
來說,其實(shí)我們只需要關(guān)注下面這一小段即可,mClient
是PluginProcessPer
的實(shí)例,而PluginProcessPer
是IPluginClient
的實(shí)現(xiàn)類。final Class<?> loadClass(String className, boolean resolve) { ...... if (mContainerActivities.contains(className)) { Class<?> c = mClient.resolveActivityClass(className); if (c != null) { return c; } ...... } ...... }
-
這里先從
PluginContainers
的實(shí)例對象mACM中
去查找ActivityState
,對這個(gè)類還有印象嗎?它就是在分配坑位的時(shí)候,我們用來保存坑位組件與真實(shí)組件對應(yīng)關(guān)系的類。然后在緩存中找到插件名對應(yīng)的插件對象,因?yàn)樵诜峙淇游坏臅r(shí)候插件信息已經(jīng)加載過了,不需要重新加載。接著取出插件的ClassLoader
對象,這個(gè)對象正是加載插件時(shí)創(chuàng)建的PuginDexClassLoader
的實(shí)例了。然后利用插件的PuginDexClassLoader
對象來加載真實(shí)Activity的class對象。final Class<?> resolveActivityClass(String container) { String plugin = null; String activity = null; // 找到坑位Activity與真實(shí)Activity的對應(yīng)關(guān)系對象 PluginContainers.ActivityState state = mACM.lookupByContainer(container); ...... plugin = state.plugin; activity = state.activity; ...... Plugin p = mPluginMgr.loadAppPlugin(plugin); //通過插件名從緩存中加載Plugin對象 ...... ClassLoader cl = p.getClassLoader(); Class<?> c = null; try { c = cl.loadClass(activity); } catch (Throwable e) { } return c; }
找到插件Activity的類對象后,Android系統(tǒng)就開始運(yùn)行Activity的啟動流程了,這些事情由ActivityManagerService和ActivityThread負(fù)責(zé)。就這樣,Replugin用插件中的Activity替換了坑位Activity,我們的插件被運(yùn)行起來啦!!很巧妙的設(shè)計(jì)~
-
小結(jié)
以上的內(nèi)容就是一個(gè)插件Activity要運(yùn)行起來,Replugin的基本代碼流程,這里要說明一下,源碼中的邏輯遠(yuǎn)不止這么點(diǎn),如果你有興趣可以跟著這篇文章在源碼中過一遍,有很多不是那么復(fù)雜的邏輯這里并沒有講到,當(dāng)然也有一些重要的地方因?yàn)榇a并不復(fù)雜也沒有講到。
下一篇Replugin 全面解析(3) 會對插件的加載和運(yùn)行做更完整和詳細(xì)的講解!