從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給幹掉了。有興趣的同學可以看看鴻翔的這篇分析ofollow,noindex">Android 熱修復 Tinker 原始碼分析之DexDiff / DexPatch