熱修復設計之熱修復原理(三)

阿里P7移動互聯網架構師進階視頻(每日更新中)免費學習請點擊:https://space.bilibili.com/474380680
本篇文章將先從熱修復原理來介紹熱修復設計:java

Android 熱修復

在Android的熱修復中主要用來替換類,資源,so的過程;android

Java 虛擬機git

棧架構指令集的主要缺點是執行速度相對來講稍微慢一些;基於堆棧的機器須要更多指令,(內存)數組

Android 虛擬機緩存

而基於寄存器(硬件在CPU內部)的機器指令更長架構

速度: CPU - > 寄存器 -> 內存 -> 外存app

http://blog.csdn.net/ljtyzhr/article/details/39859659性能

Android 目前有2中虛擬機, Dalvik 和 ART 虛擬機;學習

Android 虛擬機和編譯加載順序

Android 熱修復其實主要是針對 Android 虛擬機加載類的一個過程,因此首先先咱們應該知道 Android 經常使用的虛擬機是 Dalvik 虛擬和 ART 虛擬機;優化

Android 4.0 以前是主要是的 Dalvik 虛擬機。 Android 4.4 以後開始支持 ART 虛擬機(可選), Android 5.0 以後就是 ART 虛擬機;

Android 4.0 --> Android 4.4 --> Android 5.0 ---> Android 7.0

Dalvik 虛擬機在 Android 2.2 的時候引入了 JIT (Just in time), 也就是一邊運行,一邊編譯成機器碼在運行;這種編譯成機器碼的過程在應用重啓的時候須要從新編譯成機器碼再運行,有點浪費性能(不是持久化),重複翻譯,編譯,運行;

在 Android 5.0 以後就使用徹底使用 ART 虛擬機;由於在 AOT (ahead of time) 的操做在安裝的時候把 dex 優化成odex; 在Android N (7) 以前是全量dex 優化成 odex, 這樣致使安裝 apk 的時候,或者系統更新重啓的時候很是耗時,特別的慢;

因此在 Android N 後引入了 JIT 和 AOT 的混合模式; 能夠理解爲「全時段的編譯」(All-Of-the-Time compilation, 也叫 AOT;是否是發現有2個 AOT, 一開始我也很懵逼的;其實和Android 5.0 中的 AOT 是不太同樣的;還有要注意的是Android N 以後的 JIT 和 Davlik 虛擬機中的JIT是不同的,簡單理解就是高級版的JIT;這個高級版本的 JIT 過程會把處理後的odex緩存到 base.art (有些地方也叫 image ) 中;等下次app啓動的時候,就先直接把這個優化後的 base.art 加載到內存中;這樣就不會重複的 JIT了;

混合模式的理解

ART 初期是使用全量的 AOT (aheader of time) 變成機器碼(指令); 由於耗安裝時間和系統升級後的啓動時間;因此在 ART 的時候,先把dex 解釋成中間態的,不編譯成機器碼。在運行的時候,或者充電的時候,只編譯「熱代碼」

引用infoq 的一篇文章 http://www.infoq.com/cn/news/2016/04/android-n-aot-jit

Android N(7.x)開發者預覽版包含了一個混合模式的運行時。應用在安裝時不作編譯,而是解釋字節碼,因此能夠快速啓動。ART中有一種新的、更快的解釋器,經過一種新的JIT完成,可是這種JIT的信息不是持久化的。取而代之的是,代碼在執行期間被分析,分析結果保存起來。而後,當設備空轉和充電的時候,ART會執行鍼對「熱代碼」進行的基於分析的編譯,其餘代碼不作編譯。爲了獲得更優的代碼,ART採用了幾種技巧包括深度內聯。

對同一個應用能夠編譯數次,或者找到變「熱」的代碼路徑或者對已經編譯的代碼進行新的優化,這取決於分析器在隨後的執行中的分析數據。這個步驟仍被簡稱爲AOT,能夠理解爲「全時段的編譯」(All-Of-the-Time compilation)。

源碼類到機器執行的文件過程

主要過程: java - (dex, class) - opt/oat -- odex;

Davlik 虛擬機: java - dex - opt -- D 類型的 odex (優化事後的仍是須要翻譯); JIT

ART虛擬機: java -- dex -- oat -- A 類型的 odex (機器碼類型文件); AOT ( Ahead of time)

混合模式: java -- dex -- oat -- (D類型的 odex, base.art: 熱緩存,image) JIT(高級的JIT); AOT (All-Of-the-Time compilation)能夠理解爲「全時段的編譯」 ,profile

注意以上的 A 類型 odex 和 D 類型的 odex,只是用來代碼 2 種是不一樣的 odex,便於理解;

補丁包

補丁包主要有 class, 資源文件,so的改動;

  1. 資源文件方案;替換 AssertManager, ---> 加載 resources.arsc
  2. os 還不清楚,沒研究過, 可能使用classLoader 加載放入so 目錄
  3. 類 :底層替換和類加載器方案

本文主要訴說類加載器的方案;

類補丁生效原理

  1. 底層替換方案 (阿里系)AndFix,
  2. 類加器載方案 (騰訊系)QFix, tinker(粒度太細了), Sophix 使用類的替換(粒度大一點)

由於 Android 有 2 款虛擬機,因此應該針對 2 款虛擬機加載過程進行分析和處理;

Davlik 虛擬機的限制

Android 在編譯的時候有個 65536 (2的16次方)問題。受限於Dlv 虛擬機的限制;不僅是方法名有限制,其實字段也是有限制的;

Davlik Class resolved by unexpected DEX: 限制和處理方式

Davlik 類加載處理 源碼

手Q的方案

if (!fromUnverifiedConstant && // 條件3
            IS_CLASS_FLAG_SET(referrer, CLASS_ISPREVERIFIED)) // 條件1
        {
            ClassObject* resClassCheck = resClass;
            if (dvmIsArrayClass(resClassCheck))
                resClassCheck = resClassCheck->elementClass;
            if (referrer->pDvmDex != resClassCheck->pDvmDex &&
                resClassCheck->classLoader != NULL) // 條件2
            {
                ALOGW("Class resolved by unexpected DEX:"
                     " %s(%p):%p ref [%s] %s(%p):%p",
                    referrer->descriptor, referrer->classLoader,
                    referrer->pDvmDex,
                    resClass->descriptor, resClassCheck->descriptor,
                    resClassCheck->classLoader, resClassCheck->pDvmDex);
                ALOGW("(%s had used a different %s during pre-verification)",
                    referrer->descriptor, resClass->descriptor);
                dvmThrowIllegalAccessError(
                    "Class ref in pre-verified class resolved to unexpected "
                    "implementation");
                return NULL;
            }
        }

三個條件

CLASS_ISPREVERIFIED 這個標記被打上的原理是,只要一個類裏面和這個類的依賴都在一個dex中,那麼這些類就被打上 PREVERIFIED 的標記;

  1. 單獨有個[特殊的.dex]這個 dex 裏面只有一個類,且其餘的dex中的類都引用一下這個單獨 dex 裏面的類,那麼,項目中全部的類,就不會被打上 CLASS_ISPREVERIFIED 這個標記

    dex 數組: [ 1.dex, 2.dex, 3.dex, 特殊的.dex(插樁類)]
  2. referrer->pDvmDex != resClassCheck->pDvmDex

    補丁類和引用類應該在同一個dex裏面;

  3. fromUnverifiedConstant hook 使用 native 方式

類加載器的雙親委派加載機制

類收到了類加載請求,他首先不會嘗試本身去加載這個類,而是把這個請求委派給父類去完成,每個層次類加載器都是如此,所以全部的加載請求都應該傳送到啓動類加載其中,只有當父類加載器反饋本身沒法完成這個請求的時候(在它的加載路徑下沒有找到所需加載的Class),子類加載器纔會嘗試本身去加載

相關日誌

12-07 10:05:27.859 11471-11471/app.mj.com.ihour I/MyApp: getClassLoader() : dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/app.mj.com.ihour-2/base.apk"],nativeLibraryDirectories=[/data/app/app.mj.com.ihour-2/lib/arm64, /system/lib64, /vendor/lib64]]]
12-07 10:05:27.860 11471-11471/app.mj.com.ihour I/MyApp: getClassLoader().getParent(): java.lang.BootClassLoader@36e0624
12-07 10:05:27.860 11471-11471/app.mj.com.ihour I/MyApp: getClassLoader().getParent().p: null

原文連接:https://my.oschina.net/jiemachina/blog/1587762
阿里P7移動互聯網架構師進階視頻(每日更新中)免費學習請點擊:https://space.bilibili.com/474380680

相關文章
相關標籤/搜索