從ClassLoader到Android外掛化以及熱更新原理

kevinxie發表於2018-09-10

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

相關文章