發布時間:2024-01-24閱讀(13)
現在線上的BUG一直是令很多Android工程師所發愁的問題,可能就是那么幾行代碼,會讓自己所研發的APP損失慘重,所以,熱修復完美的解決了這些問題
下面就是我整理總結的一些熱修復知識點和大廠熱修復的一些相關資料
一、什么是熱修復?熱修復就是一個APP上線發布以后,發現自身存在很多BUG,想要修復這些BUG,但是如果重新推出一個版本、發布、再供用戶下載,那樣所用的時間就太久了,不利用戶體驗,所以熱修復就出來了,他可以在用戶所下載的APP里發布一個插件,他可以在不發布新版本的前提下,修復APP的BUG,這就叫熱修復。
二、熱修復的優勢
dexElements的數組
<pre spellcheck="false" lang="dart" cid="n259" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;"> /** * List of dex/resource (class path) elements. * Should be called pathElements, but the Facebook app uses reflection * to modify dexElements (http://b/7726934). */ private final Element[] dexElements;</pre>
熱修復就是利用dexElements的順序來做文章,當一個補丁的patch.dex放到了dexElements的第一位,那么當加載一個bug類時,發現在patch.dex中,則直接加載這個類,原來的bug類可能就被覆蓋了
看下PathClassLoader代碼
<pre spellcheck="false" lang="java" cid="n262" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">public class PathClassLoader extends BaseDexClassLoader { public PathClassLoader(String dexPath, ClassLoader parent) { super(dexPath, null, null, parent); } public PathClassLoader(String dexPath, String libraryPath, ClassLoader parent) { super(dexPath, null, libraryPath, parent); }} </pre>
DexClassLoader代碼
<pre spellcheck="false" lang="dart" cid="n264" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">public class DexClassLoader extends BaseDexClassLoader { public DexClassLoader(String dexPath, String optimizedDirectory, String libraryPath, ClassLoader parent) { super(dexPath, new File(optimizedDirectory), libraryPath, parent); }}</pre>
兩個ClassLoader就兩三行代碼,只是調用了父類的構造函數.
<pre spellcheck="false" lang="java" cid="n266" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">public class BaseDexClassLoader extends ClassLoader { private final DexPathList pathList; public BaseDexClassLoader(String dexPath, File optimizedDirectory, String libraryPath, ClassLoader parent) { super(parent); this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory); } @Override protected Class<?> findClass(String name) throws ClassNotFoundException { List<Throwable> suppressedExceptions = new ArrayList<Throwable>(); Class c = pathList.findClass(name, suppressedExceptions); if (c == null) { ClassNotFoundException cnfe = new ClassNotFoundException("Didnt find class "" name "" on path: " pathList); for (Throwable t : suppressedExceptions) { cnfe.addSuppressed(t); } throw cnfe; } return c; }</pre>
在BaseDexClassLoader 構造函數中創建一個DexPathList類的實例,這個DexPathList的構造函數會創建一個dexElements 數組
<pre spellcheck="false" lang="tsx" cid="n268" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">public DexPathList(ClassLoader definingContext, String dexPath, String libraryPath, File optimizedDirectory) { ... this.definingContext = definingContext; ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>(); //創建一個數組 this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory, suppressedExceptions); ... }</pre>
然后BaseDexClassLoader 重寫了findClass方法,調用了pathList.findClass,跳到DexPathList類中.
<pre spellcheck="false" lang="dart" cid="n270" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">/* package */final class DexPathList { ... public Class findClass(String name, List<Throwable> suppressed) { //遍歷該數組 for (Element element : dexElements) { //初始化DexFile DexFile dex = element.dexFile; if (dex != null) { //調用DexFile類的loadClassBinaryName方法返回Class實例 Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed); if (clazz != null) { return clazz; } } } return null; } ...} </pre>
會遍歷這個數組,然后初始化DexFile,如果DexFile不為空那么調用DexFile類的loadClassBinaryName方法返回Class實例,歸納上面的話就是:ClassLoader會遍歷這個數組,然后加載這個數組中的dex文件,而ClassLoader在加載到正確的類之后,就不會再去加載有Bug的那個類了,我們把這個正確的類放在Dex文件中,讓這個Dex文件排在dexElements數組前面即可。
四、常見的幾個熱修復框架的對比熱修復框架的種類繁多,按照公司團隊劃分主要有以下幾種:
類別 | 成員 |
阿里系 | AndFix、Dexposed、阿里百川、Sophix |
騰訊系 | 微信的Tinker、QQ空間的超級補丁、手機QQ的QFix |
知名公司 | 美團的Robust、餓了么的Amigo |
其他 | RocooFix、Nuwa、AnoleFix |
雖然熱修復框架很多,但熱修復框架的核心技術主要有三類,分別是 代碼修復、資源修復和動態鏈接庫修復,其中每個核心技術又有很多不同的技術方案,每個技術方案又有不同的實現,另外這些熱修復框架仍在不斷的更新迭代中,可見熱修復框架的技術實現是繁多可變的。
部分熱修復框架的對比如下表所示:
特性 | AndFix | Tinker/Amigo | QQ空間 | Robust/Aceso |
即時生效 | 是 | 否 | 否 | 是 |
方法替換 | 是 | 是 | 是 | 是 |
類替換 | 否 | 是 | 是 | 否 |
類結構修改 | 否 | 是 | 否 | 否 |
資源替換 | 否 | 是 | 是 | 否 |
so替換 | 否 | 是 | 否 | 否 |
支持gradle | 否 | 是 | 否 | 否 |
支持ART | 是 | 是 | 是 | 是 |
支持Android7.0 | 是 | 是 | 是 | 是 |
我們可以根據上表和具體業務來選擇合適的熱修復框架,當然上表的信息很難做到完全準確,因為部分的熱修復框架還在不斷更新迭代。
五、技術原理及特點5.1 阿里Dexposed -- native原理:
優點:
缺點:
原理:
優點:
缺點:
原理:
優點:
缺點:
原理:
優點:
缺點:
原理:
Tinker還支持資源和So包的更新,So補丁包使用BsDiff來生成,資源補丁包直接使用文件md5對比來生成,針對資源比較大的(默認大于100KB屬于大文件)會使用BsDiff來對文件生成差量補丁。
優點:
缺點:
Tinker已知問題:
Tinker性能痛點:
Andfix底層ArtMethod結構時采用內部變量一一替換,倒是這個各個廠商是會修改的,所以兼容性不好。
Sophix改變了一下思路,采用整體替換方法結構,忽略底層實現,從而解決兼容穩定性問題。
六、熱修復需要解決的難點熱修復不同于插件化,不需要考慮各種組件的生命周期,唯一需要考慮的就是如何能將問題的方法/類/資源/so 替換為補丁中的新方法/類/資源/so,其中最重要的是方法和類的替換,所以有不少熱修復框架只做了方法和類的替換,而沒有對資源和 so 進行處理
熱修復框架普遍存在一個問題: 雖然不用安裝新版本的安裝包同樣可以修復bug,但是如果本地下載好的補丁包被刪除了,那么之前bug就會重新!因為熱修復不是合拼生成新的apk,而是 動態加載修復bug的那部分代碼。換句話說修復bug的代碼是存放在補丁包里的,刪除補丁包,修復bug的代碼也就不存在了.之前bug也就重新出來了。
總結現在的熱修補的技術可以說是百花齊放了,很多大型的公司都有自己完整的熱修復技術框架,但是想要深入了解熱修復,就需要先去了解其中的一些機制,很多機制需要龐大的知識貯備才能進行深入理解,當然Android Framwork的實現細節是非常重要的
熱修復不是簡單的客戶端SDK,它還包含了安全機制和服務端的控制邏輯,整條鏈路也不是短時間可以快速完成的。所以需要我們深入了解才能更好的去理解。
有需要文中完整代碼的同學可以 私信發送 “底層源碼”即可 免費獲取
現在私信還可以獲得 更多《Android 學習筆記+源碼解析+面試視頻》
最后我想說:
對于程序員來說,要學習的知識內容、技術有太多太多,要想不被環境淘汰就只有不斷提升自己,從來都是我們去適應環境,而不是環境來適應我們
技術是無止境的,你需要對自己提交的每一行代碼、使用的每一個工具負責,不斷挖掘其底層原理,才能使自己的技術升華到更高的層面
Android 架構師之路還很漫長,與君共勉
歡迎分享轉載→http://www.avcorse.com/read-233589.html
下一篇:紅娘是哪一部作品中的人物
Copyright ? 2024 有趣生活 All Rights Reserve吉ICP備19000289號-5 TXT地圖HTML地圖XML地圖