日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > Android >内容正文

Android

Android 热修复原理

發布時間:2023/12/31 Android 17 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Android 热修复原理 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

轉載請注明鏈接:https://blog.csdn.net/feather_wch/article/details/87910364

Android 熱修復原理

版本號:2019/3/3-2:10


文章目錄

  • Android 熱修復原理
    • 思維導圖
    • 技術介紹(31題)
      • 熱修復基本概念
        • 三大優勢
        • 三大領域
        • 傳統框架實現方式
        • Sophix概覽
          • 優勢
          • 缺點
      • 代碼修復
        • 底層替換方案
          • 傳統方案
            • 類方法的增減
            • 類字段的增減
            • 不穩定性
          • 無視底層結構的替換方案
        • 類加載方案
          • 傳統方案的原理
          • Dex比較維度
          • Sophix的方案
            • 類插樁
        • 雙劍合璧
      • 資源修復
        • Instant Run
        • Sophix方案
          • 不修改AssetManager的引用處
          • 不必下發完整包
          • 不需要在運行時合成完整包
      • SO庫修復
    • 代碼熱修復
      • 底層熱替換原理(23題)
        • Andfix即時生效的原理
          • navtive層替換掉原方法
            • replaceMethod()
            • ArtMethod
          • 為什么替換ArtMethod的內容就能實現熱修復?
            • 解釋模式
            • AOT機器碼模式
        • ArtMethod的兼容性問題
          • ArtMethod的整體替換
            • ArtMethod的精確尺寸
            • 線性結構不能變
        • 訪問權限檢查
          • 方法調用時的權限檢查
          • 同包名下的權限問題
          • 反射調用非靜態方法
            • 靜態方法不會有該問題
            • 解決辦法
        • 即時生效的限制
      • 熱修復與Java(68題)
        • 內部類編譯
          • 靜態內部類和非靜態內部類的區別
          • 內部類和外部類的互相訪問
          • 熱部署(底層替換方案)
        • 匿名內部類編譯
          • 編譯期的命名規則
          • 熱部署方案
        • 域編譯
          • 靜態field初始化/靜態代碼塊
          • 非靜態field初始化/非靜態代碼塊
          • 熱部署方案
        • final static field編譯
          • static和final static修飾的區別
          • final static優化原理
          • 熱部署方案
        • 方法編譯
          • 混淆
          • 方法內聯
          • 方法裁剪
            • 代碼規范
          • 熱部署方案
        • switch case語句編譯
          • 熱部署方案: 反編譯
        • 泛型
          • 為什么需要泛型
            • Object實現泛型
            • 泛型
          • 類型擦除
          • 類型擦除和多態的沖突
            • bridge
          • 熱部署的方案
        • Lambda表達式編譯
          • 和匿名內部類的區別
            • invokedynamic
          • metafctory
          • Android虛擬機中的lambda
            • Jack
          • 熱部署方案
        • 訪問權限檢查
          • 類加載階段
          • 類校驗階段
      • 冷啟動類加載原理(26題)
        • 傳統實現方案
          • Tinker
        • 插樁實現
          • dexopt
            • odex
          • verifyAndOptimizeClass
          • dvmVerifyClass
          • dvmOptimizeClass
          • dvmResolveClass
            • 插樁
        • 插樁導致類加載性能差
          • 插樁具體性能影響
        • 避免插樁的手Q方案
        • ART下冷啟動實現
          • Dalvik和Art加載dex分解的區別
          • Art中的方案
            • Tinker方案的比較
        • odex和dex
        • 完整的方案
      • 多態對冷啟動類加載的影響(16題)
        • 多態
          • 方法多態性的實現
            • Virtual方法
            • invokeVirtual
          • field/static方法不具有多態
        • 冷啟動方案的限制
          • 類優化(dvmOptimizeClass)
        • 終極方案
          • 插樁方案的失敗
          • 非插樁手Q方案的失敗
          • 完整DEX方案
      • Dalvik中全量Dex方案(16題)
        • 冷啟動類加載修復
        • 新的全量Dex方案
          • multidex的原理
        • 對Application的處理
        • dvmOptResolveClass的問題
    • 資源熱修復技術(25題)
      • 普通的實現方式
        • Instant Run
          • AssetManager
      • 資源文件的格式
        • resources.arsc
          • ResChunk_header
          • 資源id
            • package id
            • type id
            • entry id
      • 運行時資源的解析
      • 資源修復方案
        • 傳統方案
        • 最佳方案
          • 新增資源和id偏移
          • 內容改變的資源
          • 刪除的資源
          • 對于type的影響
      • 優雅地替換AssetManager
    • SO庫熱修復技術(22題)
      • SO庫加載原理
        • 動態注冊
        • 靜態注冊
      • SO庫熱部署方案
        • 動態注冊native方法
          • Art
          • Dalvik
        • 靜態注冊native方法
      • SO庫冷部署方案
        • 接口調用替換方案
        • 反射注入方案
          • sdk23前后的區別
      • 機型對應的so庫
    • 問題匯總
    • 參考資料

思維導圖

技術介紹(31題)

熱修復基本概念

1、傳統BUG修復流程的弊端?

  • 重新發布版本代價太大
  • 用戶下載安裝成本太高
  • BUG修復不及時,用戶體驗差。
  • 2、對于這些弊端,有哪些合適的解決辦法?(or 有哪些方案能夠進行BUG的快速修復?)

    方案內容缺點
    Hybird方案將需要經常變更的業務邏輯通過H5進行獨立1. 有學習成本,需要對原有邏輯進行合理的抽象和轉換。 2. 對于無法轉為H5的代碼依舊無法修復
    插件化方案例如Atlas以及DroidPlugin方案1.移植成本高 2.需要學習插件化工具 3. 改造老代碼的功能量大
    熱修復APP直接從云端下拉補丁和更新

    三大優勢

    3、熱修復的3大優勢

  • 無需重新發版,實時高效熱修復。
  • 用戶無感知修復,無需下載新的應用,代價小。
  • 修復成功率高
  • 三大領域

    4、Android 熱修復的3大領域

  • 代碼修復
  • 資源修復
  • so修復
  • 傳統框架實現方式

    5、傳統熱修復框架的實現方式

    框架方案缺點
    Xposed手淘,底層結構替換方案,針對Dalvik虛擬機開發的Java Method Hook技術-Dexposed1.對于底層Dalvik結構過于依賴 2.無法繼續兼容ART虛擬機(Android 5.0起)
    Andfix支付寶,底層結構替換方案,做到了Dalvik和ART環境的全版本兼容。
    Hotfix阿里百川,Andfix升級版,業務邏輯解耦1.底層結構的替換方案``穩定性差 2.使用范圍限制多 3.不支持資源和so修復
    超級補丁技術QQ控件
    Tinker微信,
    Amigo餓了么,
    Robust美團,

    Sophix概覽

    6、Sophix的設計理念

  • 核心理念:非侵入性
  • 打包過程不會侵入到apk的build流程中。也不會增加任何AOP代碼,對開發者透明化。
  • 優勢

    7、Sophix框架的優勢

  • 支持代碼修復、資源修復、so修復
  • 集成非常簡單,沒有侵入性。
  • 缺點

    8、Sophix的缺點

  • 唯一缺點,是不支持四大組件的增加。但是支持四大組件的增加必然導致代碼侵入性過強。
  • 一般熱修復也使用于修復故障。而不是增加很多新功能。因此也不需要。
  • 可以通過增加Fragment,增加新功能。
  • 代碼修復

    9、代碼修復的兩大主要方案

  • 阿里系的底層替換方案。
  • 騰訊系的類加載方案。
  • 10、底層替換方案和類加載方案的優劣

    方案優點缺點
    底層替換1.時效性最好 2.加載輕快 3.立即見效限制很多
    類加載1.修復范圍廣 2.限制少1.時效性差,需要冷啟動才能見效

    底層替換方案

    傳統方案

    11、底層替換方案是什么?

  • 在已經加載了的類中直接替換掉原有的方法
  • 是在原有類的基礎上進行的修改,因此無法進行方法和字段的增減(這會破壞原有類的結構)
  • 該方案的底層替換具有不穩定性
  • 類方法的增減

    12、為什么底層替換方案無法增減原有類的方法?

  • 會導致該類和整個Dex的方法數變化
  • 方法數的變化會造成方法索引的變化,這樣訪問方法時,就無法正常所引導正確的方法。
  • 類字段的增減

    13、為什么底層替換方案無法增減原有類的字段?

  • 增加和減少了字段和增減方法一樣,會導致所有字段的索引發生變化。
  • 最嚴重的是, 在app運行時某個類突然增加了字段,而原先已經產生的該類的實例還是原來的結構(這是無法改變的),后續對這個老實例對象訪問新增字段是很致命的。
  • 不穩定性

    14、底層替代方案是如何實現的?

  • 無論是Dexposed、AndFix以及其他的Hook方案都是直接修改虛擬機方法的具體字段。
  • 例如修改Dalvik方法的jni函數指針、修改類的訪問權限、修改方法的訪問權限
  • 15、底層替代方案的不穩定性?

    1.這種依賴于具體字段的Hook方案,各個廠商會對源代碼進行改造,從而導致不匹配。

  • 例如Andfix里ArtMethod的結構是根據開源Android源碼中的結構寫死的。如果結構發生改變,就會導致替換機制出錯。
  • 無視底層結構的替換方案

    16、無視底層具體結構的替換方法

  • 忽略底層ArtMethod結構的差異
  • 所有Android版本都不需要區分
    即使Android版本不斷修改ArtMethod的成員,只要保證ArtMethod數據仍然是線性結構排序就沒問題
  • 類加載方案

    傳統方案的原理

    17、傳統類加載方案原理是什么?

  • app重新啟動后讓ClassLoader去加載新的類
  • 不重啟app,原來的類還在虛擬機中,就無法加載新的類。
  • 18、騰訊系三類加載方案的實現原理

  • QQ控件會侵入打包流程,增加無用信息,不優雅。
  • QFix方案,獲取底層虛擬機的函數,不夠穩定可靠,且無法新增public函數
  • 微信Tinker,完整的全量Dex加載。會對Dex內容非常精細的比較(方法和指令的維度),性能消耗嚴重。
  • Dex比較維度

    19、Dex的比較維度有三種

  • 方法和指令的維度: 粒度過細,性能差
  • bsbiff: 粒度粗糙
  • 類的維度: 粒度最合適,能夠達到時間和空間平衡的最佳效果
  • Sophix的方案

    20、Sophix的類加載方案

  • dex的比較維度:類的維度
  • 采用全量合成dex:
  • 利用Android原先的類查找和合成機制,快速合成新的全量Dex-不需要處理合成時方法數超過的問題,也不會破壞性重構dex的結構。
  • 重新排列包中dex的順序。虛擬機查找時優先找到classes.dex中的類,然后才是 classes2.dex、classes3.dex
  • 類插樁

    21、Sophix中的dex文件級別的類插樁方案

  • 將舊包和補丁包中的classes.dex的順序進行了重排
  • 讓系統自動實現類覆蓋的目的,大大減少合成補丁的開銷
  • 雙劍合璧

    22、兩個方案的合并

  • 底層替換方案和類加載方案合并使用
  • 補丁工具根據實際代碼變動情況:
    1. 小修改,在底層替代方案的適用范圍內:底層替代方案-即時生效
    1. 其余:類加載方案-即時性差
  • Sophix底層會判斷機型是否支持熱修復:如果機型底層虛擬機構造不支持,依舊走類加載修復
  • 資源修復

    23、熱修復的方案大部分都參考了Instant Run的實現

    Instant Run

    24、Instant Run中的資源熱修復的原理?

  • 構造一個新的AssetManager.
  • 反射調用addAssetPath,將這個完整的新資源包加入到新AssetManager中。
  • 找到所有引用舊AssetManager的地方,通過反射,將引用處替換為新AssetManager-該Manager包含所有新資源
  • 25、Instant Run的資源熱修復主要工作都是在處理兼容性和查找到AssetManager引用處,替換邏輯很簡單。

    Sophix方案

    26、Sophix的資源修復方案

  • 構造一個package id = 0x66的資源包,包含兩種資源:1.新增資源 2.原有內容發生改變的資源
  • 直接在原有AssetManager中addAssetPath0x66資源包,不和已經加載的0x7f沖突。不再需要去找到所有引用AssetManager的地方
  • Android 4.4及以下:需要在原有的AssetManager對象上進行析構和重構。保證addAssetPath生效。Android 5.0開始,addAssetPath(0x66資源包)會直接加載和解析資源。
  • 27、Sophix資源修復方案的優勢

  • 不修改AssetManager的引用處,替換更快更安全(對比Instant Run以及所有copycat的實現)
  • 不必下發完整包,補丁包只包含改動的資源(對比Instant Run、Amigo等方式的實現)
  • 不需要在運行時合成完整包。不占用運行時資源。(對比Tinker的實現)
  • 不修改AssetManager的引用處

    28、不修改AssetManager的引用處

    直接在原有的AssetManager對象上進行析構和重構。不再需要去替換所有舊AssetManager的引用

    不必下發完整包

    29、不必下發完整包

  • 構造一個package id = 0x66的資源包,包含新增資源和原有內容發生改變的資源
  • 直接在原有AssetManager中addAssetPath0x66資源包,會優先找到0x66資源包中的資源
  • 不需要在運行時合成完整包

    30、不需要在運行時合成完整包

  • 采用dex文件級別的類插樁方案
  • 重新排列包中dex的順序。虛擬機查找時優先找到classes.dex中的類,然后才是 classes2.dex、classes3.dex。系統自動實現類覆蓋。
  • SO庫修復

    31、SO庫修復的原理

  • 本質是對native方法的修復和替換
  • 采用類似類修復的反射注入方式,把補丁so庫的路徑插入到nativeLibraryDirectories數組的最前方,這樣加載so庫的時候是補丁so庫
  • 該方案在啟動期間,反射注入補丁so庫,而不是其他方案手動替換系統的System.load()來實現替換目的
  • 代碼熱修復

    底層熱替換原理(23題)

    Andfix即時生效的原理

    navtive層替換掉原方法

    1、Andfix的即時生效原理

  • Andfix即時生效,不需要重新啟動,但是也有使用限制(不能增減方法和字段,只能替換掉原方法)。
  • 方法:在已經加載的類中,直接在navtive層替換掉原方法,
  • replaceMethod()

    2、AndFix的核心:replaceMethod()

  • 獲取到原有方法的Method對象,并且替換為新方法dest
  • 根據虛擬機類型是art還是dalvik,調用對應替換的方法(art/dalvik_replaceMethod)。
  • Android 4.4以下是dalvik, 4.4及以上是ART虛擬機
  • @AndFix /src/com/alipay/enuler/andfix/AndFix.java // src = 原有方法 // dest = 新方法 private static native void replaceMethod(Method src, Method dest);@AndFix /jni/andfix.cpp static void replacMethod(JNIEnv* env, jclass clazz, jobject src, jobject dest){is(isArt){art_replaceMethod(env, src, dest);}else{dalvik_replaceMethod(env, src, dest);} }@AndFix /jni/art/art_method_replace.cpp extern void art_replaceMethod(JNIEnv* env, jobject src, jobject dest){if(apilevel > 23){replace_7_0(env, src, dest);}else if(apilevel > 22){replace_6_0(env, src, dest);}else if(apilevel > 21){replace_5_1(env, src, dest);}else if(apilevel > 19){replace_5_0(env, src, dest);}else{replace_4_4(env, src, dest);} }

    3、Android 6.0為例解析替換函數:replace_6_0

  • 每個Java方法在art中都一個對應的ArtMethod
  • ArtMethod記錄著Java方法的所有信息:所屬類、訪問權限、代碼執行地址等等。
  • 利用ArtMethod指針對所有成員進行修改。
  • 這樣后續調用該Java方法就會走到新的方法實現中
  • @AndFix /jni/art/art_method_replace_6_0.cpp void replace_6_0(JNIEnv* env, jobject src, jobject dest){/**===========================================* 1、通過Method對象得到Java函數在底層對應的ArtMethod的真實地址* 1. 通過`FromReflectedMethod()`獲得Method對象對應的ArtMethod的真實起始地址。* 2. 利用ArtMethod指針對所有成員進行修改。*=============================================*/art::mirror::ArtMethod* srcMeth = (art::mirror::ArtMethod*)env->FromReflectedMethod(src);art::mirror::ArtMethod* destMeth = (art::mirror::ArtMethod*)env->FromReflectedMethod(dest);/**===========================================* 2、將原方法的ArtMethod內部所有信息都替換為dest ArtMethod的內容* 1. 所屬類* 2. 訪問權限* 3. 代碼執行地址* ......*=============================================*/srcMeth->declaring_class_ = destMeth->declaring_class_;srcMeth->method_index_ = destMeth->method_index_;// xxx }
    ArtMethod

    4、ArtMethod是什么?

    ArtMethod記錄著Java方法的所有信息:所屬類、訪問權限、代碼執行地址等等。

    5、字段declaring_class就是方法所屬的類

  • 類Student的test()方法的declaring_class就是Student.class
  • 為什么替換ArtMethod的內容就能實現熱修復?

    6、為什么替換了原Java方法對應的ArtMethod的內容就能實現熱修復?虛擬機調用方法的原理?

    • Android6.0,art虛擬機中ArtMethod的結構如下:包含方法的執行入口
    @art /runtime/art_method.hclass ArtMethod FINAL{// 1、方法執行的入口void* entry_point_from_interpreter_;void* entry_point_from_quick_compiled_code_; }
    • Java代碼在Android中被編譯為Dex Code,art中可以采用解釋模式或者AOT機器碼模式執行
    • 解釋模式: 執行方法時,取出ArtMethod的entry_point_from_interpreter_的方法執行入口地址,跳轉過去執行。
    • AOT機器碼模式: 執行方法時,取出ArtMethod的entry_point_from_quick_compiled_code_的方法執行入口地址,跳轉過去執行。
    • 簡單的替換entry_point_*字段表明的入口地址,不能夠實現方法的替換。
      • 因為運行期間還會用到ArtMethod里面的其他成員字段
    • 即使是AOT機器碼模式,編譯出的AOT機器碼的執行構成,依舊會有對ArtMethod很多成員字段的依賴
    • 結論:只有替換掉所有原ArtMethod中的成員字段,在所有執行到舊方法的地方,才能完整獲取到所有新方法的信息: 執行入口、所屬class、方法索引號、所屬dex信息等,完美地去跳轉到新方法。
    解釋模式

    7、什么是解釋模式執行

  • 取出 DEX Code 逐條解釋執行。
  • AOT機器碼模式

    8、說什么是AOT機器碼模式

  • 預先編譯好Dex code對應的機器碼,運行時直接運行機器碼
  • ArtMethod的兼容性問題

    9、AndFix等Hook方案采取的native替換的方法都具有不穩定性

  • 使用的ArtMethod結構完全根據Android源碼中ArtMethod的結構寫死的。
  • 一些廠商修改了ArtMethod的內容和結構就會導致熱修復失效---兼容性很差
  • ArtMethod的整體替換

    10、native替換方法的兼容性的解決辦法

  • 原native替換方法是替換ArtMethod的所有成員,因此需要依賴具體結構。
  • 解決辦法:不構造出ArtMethod具體的成員字段,將ArtMethod進行整體替換
  • memcpy(srcMeth, destMeth, sizeof(ArtMethod));
    ArtMethod的精確尺寸

    11、整體替換ArtMethod的核心在于如何精確計算出sizeof(ArtMethod)

  • 該整體替換ArtMethod的方案,在于如果ArtMethod的size計算有偏差,會導致:部分成員沒有替換、替換區域超出了邊界
  • 應用開發者無法知道具體Andorid設備的系統里ArtMethod的尺寸
  • 通過class_linker.cc源碼中LoadClassMembers()->AllocArtMethodArray()中可以知道ArtMethod Array(數組)的ArtMethod是緊密相連的。通過相鄰兩個ArtMethod的起始地址的差值就是ArtMethod的精準大小
  • 類方法分為Direct方法和Virtual方法,各自有各自的ArtMethod數組
    • direct方法: static方法和所有不可繼承的對象方法
    • virtual方法: 所有可以繼承的對象方法
  • 12、借助ArtMethod緊密相連的特性,如何精準計算出ArtMethod的大小?

  • 構造一個輔助的類,并具有兩個空方法:
    • f1()、f2()都是static方法,都屬于direct ArtMethod Array
    • NativeStructsModel中只有這兩個方法,因此肯定是相鄰的
  • // f1()、f2()都是`static`方法,都屬于 public class NativeStructsModel{final public static void f1(){}final public static void f2(){} }
  • 在JNI層計算出f1()和f2()地址的差值。
  • size_t firstMid = (size_t) env->GetStaticMethodId(nativeStructModelClazz, "f1", "()V");size_t secondMid = (size_t) env->GetStaticMethodId(nativeStructModelClazz, "f2", "()V");// 第二個方法起始地址 - 第一個方法起始地址size_t methodSize = secondMid - firstMid;
  • 該Size就可以直接作為ArtMethod的尺寸
  • // memcpy(srcMeth, destMeth, sizeof(ArtMethod));// 替換為:memcpy(srcMeth, destMeth, methodSize);
    線性結構不能變

    13、利用技巧獲取到ArtMethod尺寸的優缺點

  • 優勢:對于所有Android版本都不需要區分
  • 注意點:只要ArtMethod數組依舊是線性結構,無論ArtMethod的成員如何改變,都完美兼容。
  • ArtMethod數組的線性結構會被修改的可能性極低!
  • 訪問權限檢查

    方法調用時的權限檢查

    14、只替換ArtMethod的內容,被替換的方法有權限訪問該類的其他private方法嗎?

  • 可以
  • 在dex2oat生成AOT機器碼時已經做過檢查和優化,因此機器碼中不存在權限檢查
  • 例如下面:即使func()方法偷梁換柱為其他方法,依舊可以調用private的func()
  • public class Demo{Demo(){func();}private void func(){} }
    同包名下的權限問題

    15、補丁中的類在訪問同包名下的類時,會出現訪問權限異常:

  • 具有類com.patch.demo.BaseBug和com.path.demo.MyClass是同一個包com.patch.demo下面的。
  • 此時替換了com.patch.demo.BaseBug的方法test,因為該方法的ArtMethod被完全替換,因此指向的是新的補丁類。
  • 該補丁包中的BaseBug是補丁包的Classloader加載的,和原先的包不是同一個Classloader,判定為不同包。BaseBug.test()中訪問MyClass類,會導致提示無法訪問com.path.demo.MyClass。
  • 校驗邏輯在虛擬機代碼的Class::IsInSamePackage中:會要求Classloader必須相同
  • 16、只需要設置new Class的Classloader為old Class的Classloader就可以解決該問題:

  • 不需要在JNI層處理底層的結構
  • 只需要通過反射進行設置
  • // 1. 獲取classloader的FieldField classLoaderField = Class.class.getDecalredField("classLoader");// 2. 允許訪問權限classLoaderField.setAccessible(true);// 3. 將新類的classloader設置為舊類的classloaderclassLoaderField.set(newClass, oldClass.getClassLoader());
    反射調用非靜態方法

    17、非靜態方法被熱替換后,再反射調用該方法,會拋出異常。

    1-下面會報錯: 新BaseBug的test()傳入舊BaseBug,不匹配就會報錯。

    // BaseBug的test()方法已經被熱替換// ...// 1、該對象bb是原始的BaseBug類對象BaseBug bb = new BaseBug();// 2、該test()是補丁包中BaseBug的test()方法Method testMeth = BaseBug.class.getDeclaredMethod("test");// 3、新BaseBug的test()傳入就BaseBug,導致報錯testMeth.invoke(bb);

    2- invoke()->InvokeMethod()->VerifyObjectIsClass(): 會檢測Method.invoke()參數傳入的目標對象(舊類的對象),是否是方法對應的ArtMethod所屬的Class(新類)。

    // object = 舊類的對象bb // C = ArtMethod的declaring_class = 新類 inline bool VerifyObjectIsClass(Object object, Class* c){if(UNLIKELY(!object->InstanceOf(c))){// 報錯return flase;}// xxx }
    靜態方法不會有該問題

    18、靜態方法為什么不會有該問題?

    是在類的級別直接調用的,不會接受對象實例作為參數,也不會有該方面的檢查。

    解決辦法

    19、非靜態方法被熱替換后,再反射調用該非靜態方法,會拋出異常。解決辦法是:

    冷啟動機制

    即時生效的限制

    20、即時生效這種運行期間修改底層結構的方案具有的限制有哪些?

  • 只能支持方法的替換:已存在類的方法增/減和字段增/減都不適用
  • 反射調用非靜態方法會拋出異常
  • 21、哪些場景是支持的?

  • 方法的替換
  • 新增一個完整的,原先包里不存在的新類
  • 22、優點

  • 一旦符合使用條件,性能極佳,補丁小,加載迅速
  • 23、不滿足即時生效的場景該如何如何處理?

  • 冷啟動修復
  • 熱修復與Java(68題)

    內部類編譯

    1、外部類有個方法,將其修改為訪問內部類的某方法,會導致補丁包新增一個方法。

    2、內部類在編譯期會被編譯為跟外部類一樣的頂級類

    靜態內部類和非靜態內部類的區別

    3、靜態內部類和非靜態內部類的區別

  • 靜態內部類不持有外部類的引用
  • 非靜態內部類會持有外部類的引用
  • 例如: handler的實現需要采用靜態內部類,避免OOM
  • 4、非靜態內部類編譯時會增加字段this用于持有外部類的引用

    5、持不持有外部類引用,都不影響熱部署。

    都是一個頂級類,新增一個頂級類,不影響熱部署

    內部類和外部類的互相訪問

    6、內部類和外部類都是頂級類,是否就表示對方private的內容無法被訪問到?

  • 外部類需要訪問內部類的private 域/方法,編譯期間會為內部類生成access&**相關方法。
    • 外部類就能訪問內部類的private內容
  • 內部類需要訪問外部類的private 屬性/方法,編譯期間會為外部類生成access&**相關方法。
    • 內部類就能訪問外部類的private內容
  • 熱部署(底層替換方案)

    7、補丁前的test()沒有訪問內部類的private屬性/方法, 補丁后的test()訪問了內部類的private屬性/方法,會導致無法使用熱部署/底層替換方案

  • 會新增access&**相關方法,按照限制,在原有類中增加方法,因此無法熱部署
  • 只要避免生成access&**相關方法,就能走熱部署。
  • 8、如何避免編譯器自動生成access&**相關方法

  • 如果一個外部類有內部類:
  • 把外部類所有private屬性/方法的訪問權限更改為其他權限(public、protected、default)
  • 把內部類所有private屬性/方法的訪問權限更改為其他權限(public、protected、default)
  • 匿名內部類編譯

    9、匿名內部類在避免新增access&**方法的基礎上,依舊新增了一個內部類和新增了method方法

  • 熱部署允許新增一個類
  • 熱部署不允許新增方法
  • 編譯期的命名規則

    10、匿名內部類的名字格式是外部類&+數字

    下例中:Thread的匿名內部類,編譯期的名字為:Demo&1

    public class Demo{public static void test(){new Thread(){// xxx}.start();}}

    此時有兩個頂級類

    11、原有的匿名內部類前插入新的匿名內部類會導致混亂

  • 下例中:有兩個匿名內部類
  • Demo&1 — Callback.OnClickListener
  • Demo&2 — Thread
  • 補丁會比較新的Demo&1和舊的Demo&1, 然而這兩者完全不同。
  • 會新增OnClick()方法 — 影響熱部署(Demo&1中增加了新方法,刪減了舊方法)
  • 會新增一個匿名內部類 — 不影響,新增類沒事(Demo&2)
  • public class Demo{public static void test(){// 新增一個內部類new Callback.OnClickListener{public void onClick(){// xxx}}new Thread(){// xxx}.start();}}
    熱部署方案

    12、在新增/減少匿名內部類時,如何支持熱部署方案?

  • 唯一情況:增加的匿名內部類必須插入到外部內末尾
  • 其余情況:無解,補丁工具無法區分。
  • 域編譯

    靜態field初始化/靜態代碼塊

    13、熱部署不支持clinit的修復

  • 熱部署不支持method/field的新增
  • 熱部署不支持clinit的修復
  • 14、clinit在Dalvik虛擬機中類加載的時,進行類初始化時調用。

    15、靜態field初始化和靜態代碼塊會被編譯到clinit方法中

    該方法由編譯器自動合成

    16、靜態field初始化和靜態代碼塊在clinit中的順序取決于代碼中出現的先后順序

    17、最常見的三種會去加載類的情況

  • new一個類對象(new-instance指令)
  • 調用類的靜態方法(invoke-static指令)
  • 獲取類的靜態field的值(sget指令)
  • 18、類沒有被加載過時, 加載的流程

  • dvmResolveClass()
  • dvmLinkClass()
  • dvmInitClass(): 先對父類進行初始化,再調用本類的clinit()
  • 非靜態field初始化/非靜態代碼塊

    19、非靜態field初始化/非靜態代碼塊會被編譯到init無參構造函數中,順序和源碼中一致

    20、構造函數會自動編譯成init方法

    熱部署方案

    21、任何靜態field初始化和靜態代碼塊的變更都會編譯到clinit中,無法熱部署,只能冷啟動(處于類加載的初始化期間)

    22、非靜態field初始化和非靜態代碼塊的變更都會編譯到init中,只被當作一個普通方法的變更,對熱部署無影響(普通的方法)

    final static field編譯

    23、final static修飾的field編譯時是否會編譯到clinit中?

  • 作為靜態域,應該都被編譯到clinit中,但是并不完全正確
  • 修飾的基本類型/String常量類型,不會編譯到clinit中
  • 24、下例中類中的field哪些會被編譯到clinit方法中?哪些不會?

    public class Demo{static Object o1 = new Object(); // √final static Object o2 = new Object(); // √static int i1 = 1; // √final static int i2 = 2;// ×不會final static String s1 = new String("new String"); // √final static String s2 = "常量"; // ×不會 }
  • finalt static修飾的基本類型和String常量類型不會編譯到clinit中
  • 25、final static修飾的基本類型/String常量類型是在哪里初始化的?

  • 類加載初始化的dvmInitClass在執行clinit之前,調用initSFields對static域設置默認值。
  • initSFields設置默認值的目標包括靜態域的所有引用類型/基本類型/String常量類型,但是基本類型/String常量類型在后面的clinit中就不會設置了
  • static和final static修飾的區別

    26、static和final static修飾的區別

  • final static修飾的原始類型和String類型(非引用類型)的field,不會編譯到clinit中,會提前在類初始化執行的initSField中進行初始化賦值。
  • final static修飾的引用類型和static修飾的所有類型,仍然在clinit中初始化
  • final static優化原理

    27、對于常量使用final static修飾就能達到優化效果?

    錯誤!

    • 只有final static修飾的原始類型和tring類型常量才能得到優化。

    28、final static進行優化的原理

  • 可以優化的情況中:要訪問該常量通過const/4指令實現,該指令非常簡單
  • 不可優化的情況中:訪問這些field,通過sget指令。內部包含解析,解析類等操作,屬于重操作。
  • 29、final對于final static修飾的引用類型的唯一作用就是避免該field被修改

    熱部署方案

    30、final static修飾的field如何進行熱部署?

  • 可以熱部署:
  • 基本類型: 引用該基本類型的地方都會被立即數替換
  • String常量:所有引用該常量的地方都被常量池索引id替換
  • 熱部署中將所有引用到該final static field的方法都進行替換,走熱部署沒問題。
  • 不可熱部署:
  • final static修飾的引用類型都被翻譯到clinit中,不會熱部署。
  • 方法編譯

    混淆

    31、混淆可能導致方法內聯和裁剪,而導致method的增減

    方法內聯

    32、哪些場景會導致方法內聯?

  • 方法沒有被其他任何地方引用
  • 方法足夠簡單,例如只有一行,會在任何調用該方法的地方用該方法的實現進行替換
  • 方法只有一個地方引用到,會在調用處用實現進行替換
  • 33、方法內聯為什么會導致方法的增減?以及導致熱部署失效?

  • 原Class中具有一個test()方法,因為內聯,所以編譯后不再有test()方法
  • 新Ckass中,因為不滿足內聯的條件導致tets()不被內聯,因此多出來test()方法
  • 前后對比,因為新增方法導致不能熱部署,只能冷啟動
  • 反過來方法內聯也會導致方法的減少
  • 方法裁剪

    34、方法裁剪

  • test(context)方法中由于context參數沒有被使用到,因此混淆任務會先生成裁剪過后無參的test()方法,然后再進行混淆。
  • 如果新代碼中,正好使用了參數,不會導致方法裁剪,因此會新增一個具有參數的test(context)方法
  • 方法裁剪導致方法增減,導致不嗯呢剛熱部署
  • 35、如何避免方法裁剪?

  • 保證所有參數被使用,或者進行特殊處理:
  • public void test(Context context){if(Boolean.FALSE.booleanValue()){context.getApplicationContext();} }
    代碼規范
    熱部署方案

    36、如何避免混淆時的方法內聯和方法裁剪導致熱部署失效的問題?

    混淆配置文件中加上配置項-dontoptimize就可以關閉方法的裁剪和內聯

    37、混淆庫的預編譯會拖累打包速度,Android虛擬機有自己的一套代碼校驗邏輯

    需要加上配置項-dontpreverify

    switch case語句編譯

    38、資源修復方案中需要對新舊ID進行替換,但是switch case中的id不會被替換

    39、switch case 語句編譯實例中解析編譯規則

    1-第一個方法較為連續。第二個方法不連續.
    2-第一個testContinue()方法中,因為1、3、5連續,使用指令packed-switch,會影響熱部署
    3-第二個testNotContinue()中,1、3、10不連續,使用指令sparse-switch

    public void testContinue(){int temp = 2;int result = 0;switch (temp){case 1:result = 1;break;case 3:result = 1;break;case 5:result = 1;break;} }public void testNotContinue(){int temp = 2;int result = 0;switch (temp){case 1:result = 1;break;case 3:result = 1;break;case 10:result = 1;break;} }
    熱部署方案: 反編譯

    40、為什么資源id替換不完全?如何解決?

  • 資源id肯定是const final static變量,導致switch case被翻譯成packed-switch指令
  • 采用方案:反編譯(強行替換指令) -> 資源id替換 -> 重新編譯
  • 修改反編譯流程: 遇到packed-switch指令就強轉為sparse-switch指令;:pswitch_N等標簽指令強轉為:sswitch_N指令
  • 資源ID的暴力替換
  • 重新編譯為Dex
  • 泛型

    41、泛型可能會導致method的新增

    為什么需要泛型

    42、Java中的泛型完全在編譯器中實現

  • 由編譯器執行類型檢查和類型推斷
  • 然后生成普通的無泛型的字節碼。泛型知識為了保證類型安全。
  • 這種技術就是擦除(erasure)
  • 43、Java的泛型為什么要采用擦除技術來實現?

  • 泛型從Java5才引入
  • 通過擴展虛擬機指令集來支持泛型是不可以的,也會導致升級JVM具有很多障礙
  • Object實現泛型

    44、Object實現泛型

    public class ObjectGeneric {private Object obj;public void setValue(Object value){obj = value;}public Object getValue(){return obj;}public static void main(String args[]){ObjectGeneric generic = new ObjectGeneric();generic.setValue(true);// 1、獲取數值boolean bool = (boolean) generic.getValue();// 2、獲取到Int值int n = (int) generic.getValue();} }
  • 上面1和2在編譯期間都不會報錯,因為符合Java語法。
  • 但是在實際運行中,2會出現java.lang.ClassCastException的異常:
  • Exception in thread "main" java.lang.ClassCastException: java.base/java.lang.Boolean cannot be cast to java.base/java.lang.Integerat ObjectGeneric.main(ObjectGeneric.java:16)

    45、Java5泛型提出之前采用Object實現該效果,但是會導致編譯器無法檢測出類型不匹配的問題

    泛型在編譯時就進行類型安全檢測

    泛型

    46、泛型會在編譯期間進行檢查, 實例:

    public class ObjectGeneric<T> {private T obj;public void setValue(T value){obj = value;}public T getValue(){return obj;}public static void main(String args[]){ObjectGeneric<Boolean> generic = new ObjectGeneric();generic.setValue(true);// 1、獲取數值boolean bool = (boolean) generic.getValue();// 2、獲取到Int值// int n = (int) generic.getValue();} }

    情況2獲取到Int值,會報錯。

    類型擦除

    47、下面的例子中的類型擦除:

  • 方法setValue(T value)會被處理為setValue(Object value), 因此編寫一個方法為setValue(Object value)會報錯!
  • 泛型設置類具體類型<Integer>本質在字節碼中生成的還是Object類型的參數,只是利用這個進行了類型檢查。
  • public class ObjectGeneric<T> {private T obj;public void setValue(T value){obj = value;}// 報錯:Error:(9, 17) java: 名稱沖突: setValue(java.lang.Object)和setValue(T)具有相同public void setValue(Object value){obj = value;}}
    類型擦除和多態的沖突

    48、類型擦除會導致本來是想重寫,結果變成了重載

  • setValue(T value)在字節碼上是setValue(Object obj)
  • 結果setValue(Integer value)是對父類setValue(Object obj)的重載
  • 然而需要的效果是setValue(Integer value)是對父類setValue(T obj)的重寫
  • public class ObjectGeneric<T> {private T obj;public void setValue(T value){obj = value;}public T getValue(){return obj;}class B extends ObjectGeneric<Integer>{private Integer n;public void setValue(Integer value) {n = value;}} }

    49、使用 @Override能實現重寫

    class B extends ObjectGeneric<Integer>{private Integer n;@Overridepublic void setValue(Integer value) {n = value;}}
    bridge

    50、編譯器會自動合成bridge方法來實現重寫的效果

    編譯器自動生成一個setValue(Object Value)來重寫父類的該方法

    class B extends ObjectGeneric<Integer>{private Integer n;// public void setValue(Object obj){ // // xxx // }@Overridepublic void setValue(Integer value) {n = value;}// 自動生成 // public void setValue(Object value){ // n = value; // }}

    51、虛擬機是通過參數類型+返回類型來確定一個方法,和Java語言規則不同。

    該方法用于解決泛型中類型擦除和多態的沖突問題

    52、泛型的隱形類型轉換,編譯器會自動加上check-cast類型轉換

    不需要程序員進行顯式地類型轉換,而是自動進行類型轉換。

    public static void main(String args[]){ObjectGeneric<Boolean> generic = new ObjectGeneric();generic.setValue(true);// 1、獲取數值boolean bool = generic.getValue();}
    熱部署的方案

    53、泛型對熱部署的影響

  • 類型擦除的過程中可能會新增bridge方法,導致熱部署失敗
  • 另一方面泛型方法內部會生成一個dalvik/annotation/Signature的系統注解,方法邏輯沒出現變化,但是該方法的注解發生了變化。補丁工具進行判斷會走熱部署進行修復,然而并沒有什么意義(方法邏輯沒有變化,根本不需要修復)
  • Lambda表達式編譯

    54、Lambda表達式簡介

  • Java 7 才引入的一種表達式
  • 類似匿名內部類,卻有巨大的區別
  • 會導致方法的增減,影響熱部署
  • 55、函數式接口的兩大特征

  • 是一個街口
  • 具有唯一的一個抽象方法
  • 典型的函數式接口Runnable和Comparator
  • 和匿名內部類的區別

    56、Lambda表達式和匿名內部類的區別?

  • 關鍵字this:
  • 匿名內部類的this指向匿名類
  • lambda表達式的this指向包圍lambda表達式的類
  • 編譯方式:
  • 編譯器將匿名內部類編譯成新類,名稱為外部類名+&number
  • 編譯器將lambda表達式編譯成類的私有方法,使用Java7的invokedynamic字節碼指令進行動態綁定該方法
  • invokedynamic

    57、實例解析lanmbda表達式

  • 編譯期間會自動生成私有靜態的lambda$test$ + number(參數類型)的方法
  • invokedynamic執行lambda表達式
  • 相比于匿名內部類,不會生成外部類名 + & + number的新類。
  • public class Test{public static void test(){new Thread( ()->{// xxx}).start();} }
    metafctory

    58、invokedynamic指令簡介

  • java7新增,用于支持動態語言:允許方法調用可以在運行時指定類和方法,不需要編譯時確定。
  • 每個invokedynamic指令出現的位置被稱為動態調用點
  • invokedynamic指令后會跟著一個指向常量池的調用點限定符(#3, #6)
  • 調用點限定符會被解析為一個動態調用點
  • invokedynamic指令最終會去執行java.lang.invoke.LambdaMetafactory類的靜態方法: metafctory(),該方法會在運行時聲稱一個實現函數式接口的具體類
  • 該具體類-例如: Test$$Lambda$1.java 會調用私有靜態方法: lambda$test$ + number(參數類型),執行lambda表達式的邏輯
  • final class Test$$Lambda$1 implements Runnable{@Hiddenpublic void run(){// 去執行自動生成的lambda表達式相關的方法,該方法內部就是自定義的邏輯Test.lambda$test$0();} }
    Android虛擬機中的lambda

    59、Android虛擬機下是如何解釋lambda表達式的?

  • android虛擬機首先通過javac把源代碼.java編譯成.class,在通過dx工具優化成適合移動設備的dex字節碼文件
  • android中如果要使用java8語言特性,需要使用新的jack工具鏈來替代老的工具鏈進行編譯
  • Jack會將.java文件編譯成.jack文件,最后直接編譯成.dex文件(Dalvik字節碼文件)
  • 60、構建Android Dalvik可執行文件可使用的兩種工具鏈對比

  • 舊版javac工具鏈
    javac(.java -> .class)->dx(.class ->. dex)
  • 新版Jack工具鏈
    Jack(.java -> .class -> .dex)
  • Jack

    61、Jack是什么?

    Java Android Compiler Kit

    62、Jack工具鏈中處理lambda的異同

  • 相同點:
    • 編譯期間都會為外部類合成一個static輔助方法,內部邏輯就是lambda表達式的內容
  • 不同點:
  • 老版本中通過invokedynamic指令執行lambda; Jack的.dex中執行lambda表達式和普通方法調用沒有區別
  • 老版本是在運行中生成新類;Jack是在編譯期間生成新類
  • 熱部署方案

    63、Lambda表達式會導致熱部署失效的原因

  • 方法的增減: 新增一個lambda表達式,會導致外部類新增一個輔助方法
  • 順序混亂: 合成類的命名規則 = “外部類雷鳴 + Lambda + Lambda所在方法的簽名 + LambdaImpl + 出現的序號”,和匿名內部類一樣的問題
  • 64、不增減lambda表達式,不改變lambda表達式的順序,只是更改Lambda原有內部邏輯,能否走熱部署?

    在一定情況下,依舊會出問題,不能走熱部署:

  • 如果lambda表達式訪問外部類非靜態的field和method
  • 編譯期間在.dex文件中會自動生成新的輔助類(Test$$Lambda$1.java), 該類沒有持有外部類的引用
  • 為了訪問非靜態的field和method,會導致需要持有外部類的引用,從而增加一個字段來持有
  • 輔助類的field的增減導致無法熱部署
  • final class Test$$Lambda$1 implements Runnable{@Hiddenpublic void run(){// 去執行自動生成的lambda表達式相關的方法,該方法內部就是自定義的邏輯Test.lambda$test$0();} }

    65、Lambda表達式對熱部署影響的總結

  • 增加/減少一個lambda表達式會導致類方法的錯亂。熱部署失敗!
  • 修改一個原有lambda表達式,因為可能訪問/取消訪問外部類的非靜態field和method的情況,可能導致輔助類的field的增加/減少。熱部署失敗!
  • 調整原有lambda表達式的順序,會導致類方法的錯亂。熱部署失敗!
  • 訪問權限檢查

    66、一個類的加載必須經歷resolve、link、init三個階段

    類加載階段

    67、類加載階段中對父類和當前類實現的接口的權限檢查主要在link階段

  • 如果當前類、實現的接口、父類是非public的,并且加載兩者的classLoader不一樣的情況,直接return
  • 代碼熱修復方案是基于新classLoader的,類加載階段就會報錯
  • 類校驗階段

    68、如果補丁類中存在非public類的訪問、非public方法的調用、非public field的調用都會導致失敗

  • 這些錯誤在補丁加載階段是檢測不出來的,補丁會被視作正常加載
  • 直到運行階段,會直接crash
  • 冷啟動類加載原理(26題)

    1、冷啟動方案的作用?

  • 熱部署有很多限制
  • 在超出限制的情況下,再通過冷啟動進行補充,使得熱修復一定能成功。
  • 傳統實現方案

    Tinker

    2、Tinker如何實現冷啟動的?

  • 提供Dex差量包,并整體替換Dex的方案。
  • 通過差量的方式生成patch.dex(補丁dex文件),然后將patch.dex和應用的classes.dex合并成一個完整的dex
  • 加載新dex文件得到dexFile對象并以此構造出Element對象,然后整體替換掉舊的dex Elements數組
  • 3、Tinker方案的優點

  • 自研dex差異算法,補丁包小,不影響類加載性能。
  • 4、Tinker方案的缺點

    dex合并,在VM Heap上消耗內存,容易OOM,導致dex合并失敗

    5、Tinker如何避免OOM導致的dex合并失敗的問題?

  • 可以在jni層面進行dex的合并,從而避免OOM導致dex合并失敗
  • 但是JNI層實現比較復雜。
  • 插樁實現

    6、如果僅僅把補丁類打入補丁包中而不做任何處理會出什么問題?該問題是啥意思?

  • 運行時類加載的時候會異常退出
  • dexopt
    odex

    7、加載一個dex文件到本地內存的流程

  • 如果不存在odex文件,首先會執行dexopt
  • dexopt的入口在davilk/opt/OptMain.cpp的main方法
  • 最后調用verifyAndOptimizeClass進行真正的verify(驗證)和optimize(優化)操作
  • 8、dexopt的流程

    verifyAndOptimizeClass

    9、Apk第一次安裝時的流程

  • 對原Dex執行dexopt->執行到verifyAndOptimizeClass()
  • 會先進行類校驗-dvmVerifyClass(): 校驗成功,則所有類都會打上CLASS_ISPREVERIFIED標志
  • 接著執行類優化-dvmOptimizeClass(),并且打上CLASS_ISOPTIMIZED標志
  • dvmVerifyClass

    10、dvmVerifyClass()方法的作用

  • 類校驗,目的是: 防止類被篡改校驗類的合法性
  • 會對類的每個方法進行校驗,類的所有方法中直接引用的類和當前類都在同一個dex中:return true
  • dvmOptimizeClass

    11、dvmOptimizeClass()方法的作用

  • 類優化,將部分指令優化成虛擬機的內部指令
  • 例如: 方法調用指令
    1. invoke-*指令變成了invoke-*-quick指令
    1. quick指令直接從vtable表中取,該表是類的所有方法的表(包括繼承的方法),加快了方法的執行速度
  • dvmResolveClass

    12、加載階段中為什么會出現dvmThrowllegalAccessError(運行時異常)?

  • 原Dex中的類B中的某個方法引用到補丁包中的類A
  • 執行到該方法時,會嘗試解析類A:
  • 類B具有CLASS_ISPREVERIFIED標志
  • 然后判斷類A和類B所屬的dex,因為不同,拋出異常dvmThrowllegalAccessError
  • 13、為什么原Dex類B能引用到補丁類A的方法?明明沒打補丁前,都不知道有這個補丁類A?

    補丁類A作為補丁,說明原包中肯定有一個原始類A

    插樁

    14、如何解決dvmThrowllegalAccessError問題?

  • 構造一個單獨沒啥用的幫助類放到一個單獨的Dex中
  • 原Dex中所有類的構造函數都引用這個類
  • 這里需要侵入dex打包流程,利用.class字節碼修改技術,在所有.class文件的構造函數中引用該幫助類
  • 在加載Dex文件時,會走dexopt流程,在dvmVerifyClass校驗時,校驗失敗(類B的所有方法中引用到的類-幫助類,和類B不在一個Dex中)。原dex中所有類沒有CLASS_ISPREVERIFIED標志。并且后續流程也不走,不會打上CLASS_ISOPTIMIZED
  • 因此引用到補丁類A時,解析類A,不會進入CLASS_ISPREVERIFIED標志的后續判斷,也不會拋出異常dvmThrowllegalAccessError
  • 插樁導致類加載性能差

    15、插樁為什么會導致類加載的效率很低?

  • 類的加載需要三個階段:dvmResolveClass->dvmLinkClass->dvmInitClass
  • 如果類因為插樁沒有打上CLASS_ISPREVERIFIED和CLASS_ISOPTIMIZED標志,在類的初始化階段,還會重新進行類的verify(驗證)和optimize(優化)
  • 原來驗證和優化操作只有在第一次apk安裝執行dexopt時,才會進行。結果如今每次進行類加載時,都會重復處理,過多的類加載同時進行,性能消耗會更大。
  • 插樁具體性能影響

    16、插樁技術對性能影響的具體測試數據

  • 整體上有8~9倍的性能差距
  • 應用啟動上,容易導致白屏。
  • 不插樁插樁
    加載700個類84ms685ms
    啟動應用耗時4934ms7240ms

    避免插樁的手Q方案

    17、手Q方案中避免插樁的思路是什么?

  • 避免在dvmResolveClass中走校驗dex一致性的流程.
  • 也就是提前將補丁類加入到數組中,讓其能直接返回補丁類
  • void dvmResolve(){ClassObject patchClass = null;// 1、提前將patch類加入到數組中,讓patchClass!=null。patchClass = dvmDexGetResolved(xxx);if(patchClass != null){// 2、只要這里拿到了patchClass,就可以直接返回。return patchClass;}// 3、檢查dex的一致性// xxx// throw dvmThrowIllegalAccessError}

    18、手Q方案的缺陷?

  • 在dexopt后進行繞過的,dexopt會改變原先的很多邏輯
  • odex層面的優化會寫死字段和方法的訪問偏移,就會導致嚴重的BUG
  • ART下冷啟動實現

    Dalvik和Art加載dex分解的區別

    19、Dalvik在嘗試加載一個壓縮文件的時候只會把classes.dex文件加載到內存中

  • 如果壓縮文件中有多個dex文件,除了classes.dex文件,其他的dex文件都會被無視
  • 20、Art支持壓縮文件中包含多個dex的加載問題

  • 會優先加載classes.dex文件
  • 然后在按順序加載classes2.dex、classes3.dex文件
  • 如果多個dex中有同一個類,只有第一個出現的類才會被加載,不會重復加載
  • Art中的方案

    21、Art中進行冷啟動的方案

  • 把補丁dex文件命名為classes.dex
  • 原apk中的dex依次命名為classes(2,3,4...).dex,并一起打包為一個壓縮文件。
  • 再通過DexFile.loadDex()得到DexFile對象,并將其整個替換舊的dexElements數組即可
  • 22、Art冷啟動方案的注意點

  • 補丁dex必須命名為classes.dex
  • loadDex得到的新DexFile必須完全替換掉dexElements數組,而不是插入
  • Tinker方案的比較

    23、Tinker的冷啟動方案和Sophix新方案的比較圖

    odex和dex

    24、虛擬機真正執行的是dex文件嗎?

  • DexFile.loadDex()會嘗試將dex文件解析并加載到native內存中
  • 如果native內存中不存在dex對應的odex,Dalvik和Art分別通過dexopt、dexoat得到一個優化后的odex
  • VM真正執行的是odex還不是dex
  • 25、patch不定的安全性如何保證?

  • 對補丁包進行簽名校驗,能保證補丁包不被篡改。
  • 但是虛擬機執行的是odex文件,而不是dex文件,還需要對odex文件進行md5完整性校驗,防止odex被篡改。
  • 完整的方案

    26、Dalvik和Art中完美兼容的冷啟動方案

  • 代碼采用同一套,不會根據Dalvik和Art分開處理。
  • Dalvik: 采用自行研發的全量Dex方案
  • Art:本身支持多Dex加載,只需要改名即可。
  • 多態對冷啟動類加載的影響(16題)

    多態

    1、多態是如何實現的?(利用的是什么技術?)

  • 實現多態的技術是動態綁定
  • 動態綁定是指,在執行期間判斷所引用對象的實際類型,根據實際類型調用對應方法
  • 2、field和靜態方法不具備多態性

    Field如下, static方法同理:

    class A{String name = "SuperClass"; }public class B extends A{String name = "B"; }A obj = new B(); System.out.println(obj.name); // name = "SuperClass"

    3、非靜態非private方法才具有多態性

    方法多態性的實現

    4、方法多態性的實現流程:

    People p = new Man(); p.talk(); // table為非靜態非private的方法
  • p.talk();通過指令invokeVirtual執行
  • 調用p的方法talk(),會拿到該talk()在父類People的vtable中的索引(methodIndex)
  • 然后在子類Man的vtable[methodIndex]中得到虛方法talk,并且執行。
  • 構成了多態
  • Virtual方法

    5、Virtual方法是什么?

  • Virtual方法就是當前類和繼承自父類的所有方法中,為public/protected/default的方法
  • 6、類加載時會創建vtable

  • new B()時會加載類B:
  • 方法調用鏈:devmResolveClass -> dvmLinkClass -> createVtable()
  • createVtable(): 創建vtable,存放當前類所有Virtual方法
  • 7、createVtable的流程

  • 復制父類的vtable到子類的vtable
  • 遍歷子類的virtual方法集合:
  • 方法原型一致,表明是重寫父類方法,在相同索引處,用子類方法覆蓋原有父類的方法
  • 方法原型不一致,將子類該方法添加到vtable末尾
  • invokeVirtual
    field/static方法不具有多態

    8、為什么field/static方法不具有多態性?

  • iget/invoke-static(虛擬機指令)是直接在引用類型中查找,而不是從實際類型中查找
  • 如果找不到,再去父類中遞歸查找
  • 冷啟動方案的限制

    9、如果新增了一個public/protected/default方法會出現什么情況?

    class A{// 新增一個方法method1void method1{// 打印method1}void method2{// 打印method2} }public class Demo{public static void test_addMethod(){A obj = new A();obj.method2();} }
  • 打補丁前: 調用方法-method2
  • 打補丁后:調用方法-method1
  • 類優化(dvmOptimizeClass)

    10、類優化階段時對虛方法調用的影響

  • dex文件第一次加載時,會執行dexopt: verify + optimize
  • 類優化階段時,會將invoke-virtual指令替換為invoke-virtual-quick指令
  • 11、invoke-virtual-quick指令為什么會提高方法的執行效率?

  • -quick指令后面跟著該方法在類vtable中的索引值
  • 會直接從類的vtable中取出方法,加快執行效率
  • 節省拿到索引值的流程
  • 12、invoke-virtual指令的方法調用的流程?

  • 多了在引用類型的vtable中的索引的步驟
  • 然后才到子類的vtable中取出方法
  • 13、為什么新增了public/protected/default方法會出現方法調用錯亂?

    上例分析:

  • obj.method2()對應的-quick指令保存的索引值是0,對應vtable[0]
  • 補丁前: vtable[0] = method2
  • 補丁后: vtable[0] = method1, vtable[1] = method2
  • 最終導致方法調用錯亂。
  • 終極方案

    插樁方案的失敗

    14、插樁方案為什么不能采用?

  • 通過Art和Dalvik的冷啟動方案,能對補丁類進行加載,但是在運行時類加載的時候會出現dvmThrowllegalAccessError異常
  • 采用插樁方案能處理該問題,但是性能極差
  • 非插樁手Q方案的失敗

    15、手Q的非插樁方案的為什么不能采用?

  • 通過非插樁的方法來繞過dex一致性檢查,雖然不會拋出異常.
  • 但是在多態的情況下因為dexopt的優化導致方法調用錯亂。
  • 完整DEX方案

    16、需要采用類似Tinker的完整Dex方案

  • google開源的dexmerge方案能將補丁dex和原dex合并成一個完整的dex
  • 會出現多dex下方法數超過65535的異常
  • dexmerge占用內存,且內存不足時有可能會失敗。
  • 在移動端需要合成完整的Dex,實現較為復雜。
  • Dalvik中全量Dex方案(16題)

    冷啟動類加載修復

    1、Android的冷啟動類加載方案是如何實現的?

  • 把新dex插入到ClassLoader索引路徑的最前面
  • 在load一個class時,優先加載補丁中的類。
  • 2、遇到的pre-verify問題

  • 一個類中直接引用到的所有非系統類都和該類在同一個dex中,該類會被打上CLASS_ISPREVERIFIED標志
  • 具體判定代碼在虛擬機中的verifyAndOptimizeClass函數
  • 3、騰訊三大熱修復方案如何解決CLASS_ISPREVERIFIED導致的異常問題?

    方案缺點
    QQ空間插樁。在每個類中都插入來自于一個特殊dex的hack.class,讓所有類都無法滿足pre-verified條件性能差
    Tinker合成全量的Dex文件,所有class都在一個dex中,消除class重復的問題。從dex的方法和指令的維度進行全量合成,比較粒度過細,實現復雜,性能消耗嚴重。
    QFix非插樁。利用虛擬機底層方法,繞過pre-verify檢查1. 不能增加public函數

    新的全量Dex方案

    4、全量Dex方案

  • 將原本基線包的dex里面去除掉補丁包中也有的class
  • 補丁 + 去除補丁類的基線包 = 新app中所有類
  • 不變的class需要用到補丁類的時候,自動地去找補丁dex
  • 新補丁類需要用到不變的class時,直接去基線包dex中尋找
  • 這樣沒用到補丁類的基線包class,繼續通過dexopt進行處理,最大的保證了效果
  • 5、全量Dex方案的核心在于:如何在基線包的dex文件中去除掉補丁包中的所有類

  • 從Dex Head中獲取到dex的各個重要屬性
  • 對于需要移除Class,不需要將其所有信息都從dex移除,只需要移除定義的入口即可
  • 不需要刪除Class具體內容
  • 6、如何找到某個dex的所有類定義?虛擬機在dexopt過程中是如何找到的?

  • dexopt的verifyAndOptimizeClass()中通過dexGetClassDef()找到的類的定義(DexClassDef *)
  • 內部是pHeader->classDefsOff偏移處開始,依次線性排列。
  • 7、如何從基線包中刪除目標類的定義的入口

  • 直接找到pHeader->classDefsOff偏移處,遍歷所有DexClassDef
  • 如果類名包含在補丁中,就將該dexGetClassDef移除
  • 8、Sophix是如何處理 CLASS_ISPREVERIFIED 問題的?

  • 補丁dex文件在補丁壓縮包中,名稱為classes.dex,會將該dex加載到dexElements數組中
  • 原apk的所有dex文件,都會被Dalvik生成DexFile加載到dexElements數組中
  • 這樣所有類,都可以從所有dex中的某一個dex中找到。
  • loadDex加載刪除了補丁類的原apk的dex文件時,會重新dexopt生成odex文件(CLASS_ISPREVERIFIED)標志只有滿足條件的才會打上。
  • 當原apk中的類引用到補丁類時,因為沒有CLASS_ISPREVERIFIED標志,不會出現dex一致性檢查而拋出異常的情況。
  • 9、實例解析該全量Dex方案如何解決異常問題

  • 錯誤場景:直接將補丁打入補丁包,不做額外處理
  • 原本APK的dex中有類A、類B
  • 現在類B有一個補丁類B
  • 單純將補丁類打入補丁包時,此時APK的dex中有類A、類B,補丁包中有補丁類B
  • 程序運行時會對Apk中的類A和類B,進行校驗和優化,類A引用了類B,且兩者位于同一個dex,給類A打上已經校驗的標志
  • 后續進行了處理,讓類A引用類B時,能指向補丁類B。
  • 類A引用類B時,根據類A的特殊標志,將類A和補丁類B的Dex進行校驗,dex不同,拋出異常。
  • Sophix場景:
  • 原本APK中dex有類A、類B
  • 打補丁后,原本APK的Dex中只有類A,補丁包中有補丁類B
  • Apk重新運行時,dexopt校驗,類A引用補丁類B,但是兩者不在同一個dex中。校驗失敗
  • 后續運行時,類A引用補丁類B,類A不具有特殊標志,不走檢查dex一致性的流程,直接走重新校驗和優化。
  • multidex的原理

    10、multidex的原理

  • 將一個apk中所有類拆分到classes.dex、classes2.dex、classes3.dex...
  • 然后將dex文件都加載進去,在運行時遇到本dex不存在的類,可以到其他dex中找
  • 對Application的處理

    11、Application的處理

  • Application必然是加載在原來的老dex里面。
  • 加載補丁后,如果Application類使用其他在新dex里的類,由于不在一個dex中,application如果被打傷了CLASS_ISPREVERIFIED標志,就會拋出異常
    • java.lang.IllegalAccessError: Class ref in pre-verified class resolved to unexpected implementation
  • 12、如何處理Application的pre-verified標志問題?

  • 將其標志位進行清除。
  • 在JNI層清除:類的標志位于ClassObject的accessFlags成員中
  • claszzObj->accessFlags &= ~CLASS_ISPREVERIFIED;

    dvmOptResolveClass的問題

    13、如果入口Application沒有pre-verified,會有更嚴重的問題

  • Dalvik虛擬機發現某個類沒有pre-verified,會在初始化類的時候,進行Verify操作
  • 會掃描類中使用到的所有類,并執行dvmOptResolveClass()
  • 會在Application類初始化的時候,補丁還沒加載,提前加載到原始Dex中的類
  • 補丁加載完畢后,已經加載的類如果用到新dex中的類,遇到pre-verified就會報錯
  • 14、問題原因

  • 無法把補丁加載提前到dvmOptResolveClass之前,也就是比入口Application初始化更早的時期。
  • 常見于多dex的情形,存在多dex時,無法保證Application用到的類和其在同一個dex中
  • 15、多dex情況下如何解決該問題

  • 方法1:將Application用到的非系統類都和Application同一個dex里。保證具有pre-verified標志,補丁加載完畢后,再清除標志。
  • 方法2:
    1. 將Application中除了熱修復框架的代碼,都放到其他單獨類中,讓Application不直接調用非系統類。讓其處于同一個dex。
    2. 保險起見,Application用反射訪問這個單獨類中,讓Application和其他類徹底隔絕。
  • 16、Android multi-dex機制對方法1的支持

  • multi-dex機制會自動將Application用到的類都打包到主dex中
  • 只要把熱修復初始化放在attachContext最前面,就OK。
  • 資源熱修復技術(25題)

    普通的實現方式

    Instant Run

    1、Instant Run中的資源熱修復的原理?

  • 構造一個新的AssetManager.
  • 反射調用addAssetPath,將這個完整的新資源包加入到新AssetManager中。
  • 找到所有引用舊AssetManager的地方,通過反射,將引用處替換為新AssetManager-該Manager包含所有新資源
  • 2、Instant Run的資源熱修復主要工作都是在處理兼容性和查找到AssetManager引用處,替換邏輯很簡單。

    3、AssetManager是什么?

  • Android中有所資源包都通過AssetManager的addAssetPath()將資源路徑添加進去。
  • Java層的AssetManager只是一個包裝。底部navtive層由C++ AssetManager。
  • 4、addAssetPath的解析流程

    1. 通過傳入的`資源包`路徑,得到其中的`resources.arsc`1. 解析`resources.arsc`的格式1. 存放在底層AssetManager的`mResources`成員中
    AssetManager

    5、AssetManager的結構

  • 一個Android進程只包含一個ResTable
  • ResTable具有成員變量mPackageGroups: 包含所有解析過的資源包
  • 任何一個資源包中都包含resource.arsc: 記錄所有資源的id分配情況和所有資源中的字符串
  • 底層AssetManager就是解析該resource.arsc,將解析后的信息存儲到mPackageGroup中
  • class AssetManager : public AAssetManager{mutable ResTable* mResoucres; } class ResTable{Vector<PackageGroup*> mPackageGroups; }

    資源文件的格式

    resources.arsc

    6、resources.arsc文件是什么?

  • 實際上由一個個ResChunk拼接起來
  • 從文件頭開始,每個ResChunk的頭部都是一個ResChunk_header結構,表明了ResChunk的大小和數據類型
  • ResChunk_header

    7、resources.arsc的解析流程

  • 通過ResChunk_header的type成員判斷出其實際類型,采用相應方法進行解析
  • 解析完畢后,通過size成員,從ResChunk + size得到下一個ResChunk的起始位置
  • 依次解析完整個文件的數據內容
  • 資源id

    8、resources.arsc中包含若干個package

  • package中包含所有資源信息
  • 資源信息指:
  • 資源名稱
  • 資源ID
  • 9、默認情況下有aapt工具打包出來的包只有一個package

    10、資源id的是一個32位數字,可以通過aapt工具解析可以看到。

  • 十六進制表示: 0xPPTTEEEE, 如: 0x7f 04 0019
  • PP是package id,如:0x7f
  • TT是類型 id,如:0x04
  • EEEE是資源項id,如:0x0019
  • package id

    11、package id是什么?

  • 表明了是哪個資源包,如0x7f就是id = 0x7f的資源包
  • type id

    12、type id是什么?

  • 表明資源的具體類型。
  • 依賴于Type String Pool-類型字符串池中具體的內容
  • 例如池中依次是attr、drawable、mipmap、layout
  • 0x04就表示是layout布局類型的資源
  • entry id

    13、 entry id是什么?

  • 資源項id,表明在 0x7f的資源包中,類型為0x04(layout)中,第0x0019的資源項
  • 運行時資源的解析

    14、默認的apk的資源包的package id是多少?

  • 由Android SDK編譯出的apk,會經過aapt工具進行打包的,其package id就是0x7f
  • 15、系統資源包是什么?

  • 系統的資源包,就是framework-res.jar
  • package id = 0x01
  • 16、資源包重復加載導致的問題

  • app啟動時,系統會構建AssetManager并且將0x01和0x07的資源包添加進去
  • 如果通過AssetManager.addAssetPath()添加補丁包的資源,會導致0x07資源包添加兩次,會導致的問題:
  • Android 5.0開始,添加不會有問題,會默默將后來的包添加到原來的資源的同一個PackageGroup下面。讀取時,會發現補丁包中新增的資源會生效。修改原app的資源不會生效。
  • Android 4.4及以下,addAssetPath直接將補丁包的路徑添加到mAssetPath中,但不會進行加載解析,補丁包里面的資源會完全不生效。
  • 資源修復方案

    傳統方案

    17、市面上的傳統的方案

  • 對資源做差量包,運行時合成完整包再加載。
  • 運行時多了合成的操作,耗時,占用內存
  • 類似Instant Run的方案。
  • 修改aapt,在打包時對補丁包資源進行重新編號。對于aapt等SDK工具包的修改,不利于日后的升級。
  • 最佳方案

    18、最佳方案

  • 構造一個package id = 0x66的資源包,包含兩種資源:1.新增資源 2.原有內容發生改變的資源
  • 直接在原有AssetManager中addAssetPath0x66資源包,不和已經加載的0x7f沖突
  • 直接在原有的AssetManager對象上進行析構和重構。不再需要去找到所有引用AssetManager的地方
  • 新增資源和id偏移

    19、新增資源導致id的偏移

  • 原來具有資源 0x7f020001(A圖片)、0x7f020002(B圖片)
  • 新增資源后是,0x7f020001(A圖片)、0x7f020002(新圖片)、0x7f020003(B圖片)—新增資源的插入位置是隨機的,跟appt有關。
  • 因為新增的資源是在0x66資源包中,打包工具需要更正id為:
    1. 原資源保持不變: 0x7f020001(A圖片)、0x7f020002(B圖片)
    1. 新增資源: 0x66020001(新圖片)
  • 內容改變的資源

    20、原有內容改變的資源需要代碼熱修復的配合

  • 原來引用資源:setContentView(0x7f030000)
  • 引用修改后的資源: setContentView(0x66020000)
  • 需要將代碼中引用該id的方法進行修改,通過代碼熱部署修改引用的id
  • 刪除的資源

    21、刪除的資源如何處理

  • 不需要處理
  • 新代碼中沒有引用,自然用不到該資源。
  • 對于type的影響

    22、對補丁包中的Type String Pool需要進行修正

  • 原來池中有: attr(0x01)、drawable(0x02)
  • 因為attr的資源沒有變動,所以補丁包中只有: drawable
  • 刪除池中的attr,保證0x01能引用到drawable: attr(0x01)
  • 優雅地替換AssetManager

    23、在Android 5.0開始,不需要替換AssetManager

    只需要在AssetManager中add進入0x66的資源包即可

    24、Android 4.4及以下的版本,需要替換AssetManager

  • 這些版本調用AssetManager.addAssetPath不會加載資源,只會添加到mAssetPath中,不會解析資源包。
  • 但是不需要和Instant Run一樣構造新的AssetManager,并且進行各種兼容和反射工作。
  • 25、利用AssetManager的析構和構造方法,實現資源的真正加載。

  • 先反射調用AssetManager的析構方法: 將Java層的AssetManager置為空殼(null)
  • 反射調用構造方法,調用addAssetPath()添加所有資源包: 系統會自動加載解析所有add過的資源包。
  • 對mStringBlocks置空并且重新賦值:該成員記錄了所有加載過的資源包的String pool,不進行重構會導致崩潰。
  • SO庫熱修復技術(22題)

    SO庫加載原理

    1、Java API提供兩個接口來加載SO庫

  • System.loadLibrary(String libName)
    1. 參數-SO庫名稱,位于apk壓縮文件的libs目錄
    1. 最后復制到apk安裝目錄下
  • System.load(String pathName)
    1. 參數-so庫在磁盤中的完整路徑
    1. 加載一個自定義外部so庫文件
  • 2、JNI編程中,native方法分為動態注冊和靜態注冊兩種

    動態注冊

    3、動態注冊的native方法

  • 必須實現JNI_OnLoad方法
  • 需要實現一個JNINativeMethod[]數組
  • 動態注冊的native方法映射通過加載so庫過程中調用JNI_OnLoad方法調用完成
  • 靜態注冊

    4、靜態注冊的native方法

  • 必須是Java + 類完整路徑 + 方法名的格式
  • 靜態注冊的native方法映射是在該native方法第一次執行時完成。前提該so庫已經load過
  • SO庫熱部署方案

    動態注冊native方法

    5、動態注冊的native方法實時生效的方案?

  • 該方法每調用一次JNI_OnLoad方法就會重新進行一次映射
  • 先加載原來的so庫,再加載補丁so庫,就能映射為補丁中的新方法
  • Art

    6、ART中能做到實時生效(熱部署)嗎?

    可以

    Dalvik

    7、Dalvik中能做到實時生效(熱部署)嗎?

  • 不能
  • 第二次load補丁so庫,依舊執行的是原來so庫的JNI_OnLoad()方法
  • 8、為什么Dalvik加載補丁so庫,執行的是原始so庫的load方法?

  • 下面兩個方法可能有問題:
  • dlopen: 返回動態鏈接庫的句柄
  • dlsym: 通過dlopen得到的句柄,來查找一個symbol
  • dlopen具有bug:
  • Dalvik中通過so的name去solist中查找,因為加載原始so庫時,該列表中已經存儲,直接返回原始so庫的句柄
  • 9、Dalvik的Bug如何規避?

  • 對補丁so庫進行改名
  • 原始so庫路徑為: /data/data/…/files/libnative-lib.so
  • 補丁so庫路徑改為:/data/data/…/files/libnative-lib-時間戳.so
  • 通過改名,添加時間戳,保證name是全局唯一,這樣能正確得到動態鏈接庫的句柄
  • 靜態注冊native方法

    10、靜態注冊native方法的映射都是在native方法的第一次執行時完成的映射。如果native方法在加載補丁so庫前已經執行過了。會出現問題。

    11、如果保證靜態注冊native方法能夠熱部署?

  • JNI API提供了解注冊的接口
  • 把目標類的所有native方法都解注冊,無論是動態注冊還是靜態注冊的,后面都需要重新映射
  • env->UnregisterNative(claze);

    12、經過解注冊處理后,熱部署也不一定會成功

  • 補丁so庫是否能成功加載,取決于在hashtable中的位置
  • 如果順序是:補丁so庫、原始so庫。則熱部署修復成功。
  • 如果順序是: 原始so庫、補丁so庫。則失敗。
  • SO庫冷部署方案

    接口調用替換方案

    13、使用sdk提供的接口替換System加載so庫的接口

  • 用SOPatchManager.loadLibrary()替代System.loadLibrary(),優先加載sdk指定目錄下的補丁so
  • SOPatchManager.loadLibrary()的加載策略如下:
  • 如果存在補丁so庫,直接加載。
  • 如果不存在補丁so庫,調用System.loadLibrary加載apk目錄下的so庫
  • 14、替換方案的優點

  • 不需要對不同sdk版本進行兼容,因為都有System.loadLibrary這個接口
  • 15、替換方案的缺點

  • 調用System接口的地方都需要替換為sdk的接口
  • 如果是已經混淆好的第三方庫的so庫,無法進行接口替換。
  • 反射注入方案

    16、System.loadLibrary(“native-lib”)的底層原理

  • 本質是在nativeLibraryDirectories數組中遍歷
  • 17、反射注入方案

  • 將補丁so庫的路徑,插入到nativeLibraryDirectories數組的最前面
  • 就能達到加載so庫時,直接加載補丁so庫的目的。
  • sdk23前后的區別

    18、sdk < 23時,只需要將補丁so庫的路徑,插入到nativeLibraryDirectories數組的最前面

    19、sdk >= 23時,需要用補丁so庫路徑來構建Element對象,然后插入到nativeLibraryPathElements數組的最前面

    20、反射注入方案的優缺點

  • 優點: 不具有侵入性
  • 缺點: 需要針對sdk進行適配
  • 機型對應的so庫

    21、so庫具有多種cpu架構的so文件

  • 如armeabi、arm64-v8a、x86
  • 難點在于如何根據機型,選擇對應的so庫文件
  • 22、如何選擇機型最合適的primaryCpuAbi so文件?

  • sdk>=21,反射拿到ApplicationInfo對象的primaryCpuAbi即可
  • sdk<21,不支持64位,直接把Build.CPU_ABI、Build.CPU_ABI2作為primaryCpuAbi即可
  • 問題匯總

    參考資料

  • 混淆庫官方文檔
  • Android 新一代編譯 toolchain Jack & Jill 簡介
  • 官方文檔Compiling with Jack
  • gradle中如何使用Lambda
  • Android動態鏈接庫加載原理及HotFix方案介紹
  • 總結

    以上是生活随笔為你收集整理的Android 热修复原理的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。