ClassLoader
關於ClassLoader,一看你就懂,超詳細java中的ClassLoader詳解,android的Classloader有些不同Android外掛化開發之動態載入基礎之ClassLoader工作機制
Android外掛化
最經換了工作,公司的專案比較龐大,很多地方都運用了外掛化,外掛化說簡單就是把部分功能進行打包成專門的apk、dex等檔案,當宿主app需要用到此功能的時候才去載入外掛;外掛不僅可以實現一些功能的熱插拔;以及不需要去安裝app,只是在使用到的情況下再去下載,這樣就減小宿主的apk的體積;還可以去通過更新外掛來完成功能的更新。外掛化技術已經比較成熟了,很多大公司的產品也都是使用外掛化開發,也有很多比較成熟的外掛化框架,例如DynamicAPK、RePlugin 、Small等等
外掛化的原理
android裡面有PathClassLoader以及DexClassLoader:
- PathClassLoader用於載入data/app下的dex、apk、class檔案,這個目錄就對應了我們安裝的一些應用
- DexClassLoader可以用來載入外部的一些dex、apk、class檔案
我們可以載入其他地方的dex、apk檔案了,並使用相應的class檔案
我們還需要獲取資原始檔
//建立AssetManager物件
AssetManager assets = new AssetManager();
//將apk路徑新增到AssetManager中
if (assets.addAssetPath(resDir) == 0){
return null;
}
//建立Resource物件
r = new Resources(assets, metrics, getConfiguration(), compInfo);
複製程式碼
只要將外掛apk的路徑加入到AssetManager中,便能夠實現對外掛資源的訪問。
具體實現時,由於AssetManager並不是一個public的類,需要通過反射去建立,並且部分Rom對建立的Resource類進行了修改,所以需要考慮不同Rom的相容性。
還有一個問題就是外掛的activity沒有進行註冊,我們在宿主中註冊一個空的Activity,專門用來載入外掛app中的activity,這個Activity叫ProxyActivity。我們還需要配置一下ProxyActivity
class ProxyActivity : AppCompatActivity() {
/**
* 要跳轉的activity的name
*/
private var className = ""
private var appInterface: AppInterface? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
/**
* step1:得到外掛app的activity的className
*/
className = intent.getStringExtra("className")
/**
* step2:通過反射拿到class,
* 但不能用以下方式
* classLoader.loadClass(className)
* Class.forName(className)
* 因為外掛app沒有被安裝!
* 這裡我們呼叫我們重寫過多classLoader
*/
var activityClass = classLoader.loadClass(className)
var constructor = activityClass.getConstructor()
var instance = constructor.newInstance()
appInterface = instance as?AppInterface
appInterface?.attach(this)
var bundle = Bundle()
appInterface?.onCreate(bundle)
}
override fun onStart() {
super.onStart()
appInterface?.onStart()
}
override fun onResume() {
super.onResume()
appInterface?.onResume()
}
override fun onDestroy() {
super.onDestroy()
appInterface?.onDestroy()
}
override fun getClassLoader(): ClassLoader {
//不用系統的ClassLoader,用dexClassLoader載入
return PluginManager.getInstance().getDexClassLoader() as? ClassLoader
?: super.getClassLoader()
}
override fun getResources(): Resources {
//不用系統的resources,自己實現一個resources
return PluginManager.getInstance().getResources() ?: super.getResources()
}
}
複製程式碼
還有在使用context的時候,需要使用ProxyActivity的context,因為外掛沒有上下文,需要依賴宿主的上下文
Android熱更新
熱修復也是比較熱門的技術,熱修復的框架也是有很多,阿里AndFix(native解決方案,不需要冷啟動)、QQ空間(Dex分包方案)、美團Robust (Instant Run 熱插拔原理)、微信Tinker,這些框架的原理各有不同。tinker就是通過ClassLoader來實現熱修復的原理。
QQ空間超級補丁,“超級補丁”很多情況下意味著補丁檔案很大,而將這樣一個大資料夾載入在記憶體中構建一個Element物件,插入到陣列最前端是需要耗費時間的,無疑會印象應用啟動的速度。因此Tinker 提出了另外一種思路,Tinker的思路是這樣的,通過修復好的class.dex 和原有的class.dex比較差生差量包補丁檔案patch.dex,在手機上這個patch.dex又會和原有的class.dex 合併生成新的檔案fix_class.dex,用這個新的fix_class.dex 整體替換原有的dexPathList的中的內容,可以說是從根本上把bug給幹掉了。有興趣的同學可以看看鴻翔的這篇分析Android 熱修復 Tinker 原始碼分析之DexDiff / DexPatch