Nuwa學習筆記

https://github.com/jasonross/Nuwa

Nuwa is a goddess in ancient Chinese mythology best known for repairing the pillar of heaven. 女媧是中國古代神話女神以補天而文明。

With this Nuwa project,you can also have the repairing power, fix your android applicaiton without have to publish a new APK to the appstore. 簡單描述:其實就是Android熱修復(ps:這裏不做熱修復的比對,純粹的學習筆記) 使用方法,github上描述的很清楚

詳細請參考Nuwa  懶得看英文的童鞋請看這裏基於Nuwa實現Android自動化HotFix

一.原理分析 首先我來先需要了解以下大Google的分包方案,multidex 不瞭解的童鞋請移步MultiDex安裝過程源碼分析 multidex安裝過程總結

將/data/app/apkName.apk路徑下解壓得到的classes2.dex, …, classesN.dex,依次寫入到/data/data/pkgName/code_cache/secondary-dexes/apkName.apk.classes2.zip等zip文件的classes.dex中,並返回這個zip列表。然後針對這個zip列表執行安裝過程,具體過程是,將這個要安裝的zip列表加入BaseDexClassLoader的pathList實例的dexElements數組中,其中會針對各dex文件進行dex2opt優化。一旦加入到了dexElements數組中,程序啓動的時候,ClassLoader會加載dexElements數組中的元素,從而實現multi dex的安裝。

其實簡要的概括就是把多個dex文件塞入到app的classloader之中,但是android dex拆包方案中的類是沒有重複的,如果classes.dex和classes1.dex中有重複的類,當用到這個重複的類的時候,系統會選擇哪個類進行加載呢? 讓我們來看看類加載的代碼:

public Class findClass(String name, List<Throwable> suppressed) {  
    for (Element element : dexElements) {  //每個Element就是一個dex文件
        DexFile dex = element.dexFile; 
                 if (dex != null) { 
            Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed); 
            if (clazz != null) { 
                return clazz;
                            }
               }
   }               
 if (dexElementsSuppressedExceptions != null) {      
        suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
    } 
    return null;
}

一個ClassLoader可以包含多個dex文件,每個dex文件是一個Element,多個dex文件排列成一個有序的數組dexElements,當找類的時候,會按順序遍歷dex文件,然後從當前遍歷的dex文件中找類,如果找類則返回,如果找不到從下一個dex文件繼續查找。 理論上,如果在不同的dex中有相同的類存在,那麼會優先選擇排在前面的dex文件的類,如下圖:

在此基礎上,我們構想了熱補丁的方案,把有問題的類打包到一個dex(patch.dex)中去,然後把這個dex插入到Elements的最前面,如下圖

1.動態加載補丁dex,並將補丁dex插入到dexElements最前面 2.要實現熱更新,需要熱更新的類要防止被打上ISPREVERIFIED標記,關於這個標記,請閱讀QQ空間的解決方案

二源碼探究 對於第一點,通過DexClassLoader對象,將補丁dex對象加載進來,再通過反射將補丁dex插入到dexElements最前面即可。(可以照搬multidex源碼) 而對於第二點,關鍵就是如何防止類被打上ISPREVERIFIED這個標記。

問題產生: 試驗一下,修改某個類,然後打包成dex,插入到classloader,當加載類的時候出現了(本例中是ActivityManager要被替換):

爲什麼會出現以上問題呢? 從log的意思上來講,ModuleManager引用了ActivityManager,但是發現這這兩個類所在的dex不在一起,其中: 1. ModuleManager在classes.dex中 2. ActivityManager在patch.dex中 結果發生了錯誤。 這裏有個問題,拆分dex的很多類都不是在同一個dex內的,怎麼沒有問題? 讓我們搜索一下拋出錯誤的代碼所在,嘿咻嘿咻,找到了一下代碼:

從代碼上來看,如果兩個相關聯的類在不同的dex中就會報錯,但是拆分dex沒有報錯這是爲什麼,原來這個校驗的前提是:

如果引用者(也就是ModuleManager)這個類被打上了CLASS_ISPREVERIFIED標誌 ,那麼就會進行dex的校驗。那麼這個標誌是什麼時候被打上去的? 讓我們在繼續搜索一下代碼,嘿咻嘿咻~~,在DexPrepare.cpp找到了一下代碼:

這段代碼是dex轉化成odex(dexopt)的代碼中的一段,我們知道當一個apk在安裝的時候,apk中的classes.dex會被虛擬機(dexopt)優化成odex文件,然後纔會拿去執行. 虛擬機在啓動的時候,會有許多的啓動參數,其中一項就是verify選項,當verify選項被打開的時候,上面doVerify變量爲true,那麼就會執行dvmVerifyClass進行類的校驗,如果dvmVerifyClass校驗類成功,那麼這個類會被打上CLASS_ISPREVERIFIED的標誌,那麼具體的校驗過程是什麼樣子的呢? 此代碼在DexVerify.cpp中,如下:

  1. 驗證clazz->directMethods方法,directMethods包含了以下方法:
  2. static方法
  3. private方法
  4. 構造函數
  5. clazz->virtualMethods
  6. 虛函數=override方法?

概括一下就是如果以上方法中直接引用到的類(第一層級關係,不會進行遞歸搜索)和clazz都在同一個dex中的話,那麼這個類就會被打上CLASS_ISPREVERIFIED標誌

所以爲了實現補丁方案,所以必須從這些方法中入手,防止類被打上CLASS_ISPREVERIFIED標誌 。 最終空間的方案是往所有類的構造函數裏面插入了一段代碼,代碼如下:

if (ClassVerifier.PREVENT_VERIFY) { System.out.println(AntilazyLoad.class); }

其中AntilazyLoad類會被打包成單獨的hack.dex,這樣當安裝apk的時候,classes.dex內的類都會引用一個在不相同dex中的AntilazyLoad類,這樣就防止了類被打上CLASS_ISPREVERIFIED的標誌了。只要沒被打上這個標誌的類都可以進行打補丁操作。 然後在應用啓動的時候加載進來.AntilazyLoad類所在的dex包必須被先加載進來,不然AntilazyLoad類會被標記爲不存在,即使後續加載了hack.dex包,那麼他也是不存在的,這樣屏幕就會出現茫茫多的類AntilazyLoad找不到的log。 所以Application作爲應用的入口不能插入這段代碼。(因爲載入hack.dex的代碼是在Application中onCreate中執行的,如果在Application的構造函數裏面插入了這段代碼,那麼就是在hack.dex加載之前就使用該類,該類一次找不到,會被永遠的打上找不到的標誌)。 其中: class ClassVerifier { public static boolean PREVENT_VERIFY = false;//false防止代碼被執行,提高性能 } 之所以選擇構造函數是因爲他不增加方法數,一個類即使沒有顯式的構造函數,也會有一個隱式的默認構造函數。 空間使用的是在字節碼插入代碼,而不是源代碼插入,使用的是javaassist庫來進行字節碼插入的。 隱 患

虛擬機在安裝期間爲類打上CLASS_ISPREVERIFIED標誌是爲了提高性能的,我們強制防止類被打上標誌是否會影響性能?這裏我們會做一下更加詳細的性能測試。 但是在大項目中拆分dex的問題已經比較嚴重,很多類都沒有被打上這個標誌。

如何打包補丁包:

1.空間在正式版本發佈的時候,會生成一份緩存文件,裏面記錄了所有class文件的md5。還有一份mapping混淆文件。 2.在後續的版本中使用-applymapping選項,應用正式版本的mapping文件,然後計算編譯完成後的class文件的md5和正式版本進行比較,把不相同的class文件打包成補丁包。 備註:該方案現在也應用到我們的編譯過程當中,編譯不需要重新打包dex,只需要把修改過的類的class文件打包成patch dex,然後放到sdcard下,那麼就會讓改變的代碼生效。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章