動態(tài)加載技術(shù)汉规,也叫插件化技術(shù)哭尝,在技術(shù)驅(qū)動型公司中扮演著相當(dāng)重要的角色,當(dāng)項(xiàng)目越來越龐大的時候依疼,需要通過插件化來減輕應(yīng)用的內(nèi)存和CPU占用痰腮,還可以實(shí)現(xiàn)熱插拔,即不在發(fā)布新版本的情況下更新某些模塊律罢。動態(tài)加載時一項(xiàng)很復(fù)雜的技術(shù)膀值。這主要介紹其中的三個基礎(chǔ)性問題。
- 開源插件化框架 DL误辑。傳送門
資源訪問
問題描述
宿主程序調(diào)起未安裝的插件apk沧踏,一個很大的問題就是資源如何訪問,具體來說就是插件中凡是以R開頭的資源都不能訪問了巾钉。這是因?yàn)樗拗鞒绦蛑胁]有插件的資源翘狱,所以通過R來加載插件的資源時行不通的,程序會拋出異常:無法找到某某id 對應(yīng)的資源砰苍。
解決思路分析
-
將插件中的資源在宿主程序中也預(yù)置一份潦匈,這雖然能解決問題阱高,但是就會產(chǎn)生一些弊端:
- 首先,這樣就需要宿主和插件同時持有一份相同的資源茬缩,增加了宿主apk的大谐嗑;
- 其次凰锡,在這種模式下未舟,每次發(fā)布一個插件都需要將資源復(fù)制到宿主程序中,這意味著沒發(fā)布一個插件都要更新一下宿主程序掂为,這就和插件化的思想相違背了裕膀。
- 因?yàn)椋寮哪康木褪且獪p小宿主程序apk包的大小菩掏,同時降低宿主程序的更新頻率并做到自由裝載模塊魂角。所以這一思路不可取,它限制了插件化的線上更新這一重要特性智绸。
-
首先將插件中的資源解壓出來野揪,然后通過文件流去讀取資源。
這樣做理論上可行的瞧栗,但是實(shí)際操作起來還是有很大的困難斯稳。
- 首先,不同資源有不用的文件流格式迹恐,比如圖片挣惰、XML等;
- 其次殴边,針對不同設(shè)備加載的資源可能是不一樣的的憎茂,如何選擇合適的資源也是一個需要解決的問題。
基于這兩點(diǎn)這種方法也是不可取的锤岸,因?yàn)閷?shí)現(xiàn)起來有較大的難度竖幔。
-
參考模仿系統(tǒng)加載訪問資源的方式
Activity的工作主要是通過 ContextImpl 來完成,Activity中有一個叫 mBase 的成員變量是偷,它的類型就是 ContextImpl拳氢。注意到Context中有兩個抽象方法,看起來是和資源有關(guān)的蛋铆,實(shí)際上Context 就是通過它們來獲取資源的馋评。這兩個抽象方法的真正實(shí)現(xiàn)是在 ContextImpl中,也就是說刺啦,只要實(shí)現(xiàn)了這兩個方法留特,就可以解決資源問題了。
public abstract AssetManager getAssets(); public abstract Resources getResources();
代碼實(shí)現(xiàn)
protected fun loadResources() {
try {
val assetManager = AssetManager::class.java.newInstance()
val addAssetPath = assetManager.javaClass.getMethod("addAssetPath", String::class.java)
addAssetPath.invoke(assetManager, mDexPath)
mAssetManager = assetManager
} catch (e: Exception) {
e.printStackTrace()
}
val superRes = super.getResources()
val mResources = Resources(mAssetManager, superRes.displayMetrics, superRes.configuration)
val mTheme = mResources.newTheme()
mTheme.setTo(super.getTheme())
}
從 loadResources() 的實(shí)現(xiàn)可以看出,加載資源的方法是通過反射磕秤,通過調(diào)用 AssetManager 中的 addAssetPath 方法乳乌,我們可以將一個 apk 中的資源加載到Resources 對象中,由于 addAssetPath 是隱藏 API 我們無法直接調(diào)用市咆,所以只能通過反射汉操。下面是它的聲明,通過注釋我們可以看出蒙兰,傳遞的路徑可以是zip 文件也可以是一個資源目錄磷瘤,而 apk 通過AssetManager 來創(chuàng)建一個新的Resources對象,通過這個對象我們就可以訪問插件apk中的資源了搜变,這樣一來問題就解決了采缚。
/**
* Add an additional set of assets to the asset manager. This can be
* either a directory or ZIP file. Not for use by applications. Returns
* the cookie of the added asset, or 0 on failure.
* {@hide}
*/
public final int addAssetPath(String path) {
return addAssetPathInternal(path, false);
}
然后在代理Activity 中實(shí)現(xiàn) getAssets() 和 getResources()
override fun getResources(): Resources {
return if (mResources == null) super.getResources() else mResources!!
}
override fun getAssets(): AssetManager {
return if (mAssetManager == null) super.getAssets() else mAssetManager!!
}
Activity生命周期的管理
- 反射方式
- 接口方式
反射方式
override fun onResume() {
super.onResume()
val onResume: Method? = mActivityLifecircleMethods.get("onResume")
try {
onResume?.invoke(mRemoteActivity, arrayOf(Any()))
} catch (e: Exception) {
e.printStackTrace()
}
}
缺點(diǎn):
- 反射代碼寫起來比較復(fù)雜
- 過多使用反射會影響性能
接口方式
interface DLPlugin{
fun onCreate()
fun onStart()
fun onResume
...
}
在代理Activity中調(diào)用即可
override fun onResume() {
mRemoteActivity.onResume()
super.onResume()
}
...