阿里巴巴對Android熱修復技術已經進行了長達多年的探索。git
最開始,是手淘基於Xposed進行了改進,產生了針對Android Dalvik虛擬機運行時的Java Method Hook技術,Dexposed。但這個方案因爲對底層Dalvik結構過於依賴,最終沒法繼續兼容Android5.0之後ART虛擬機,所以做罷。github
後來支付寶提出了新的熱修復方案Andfix。Andfix一樣是一種底層結構替換的方案,也達到了運行時生效即時修復的效果,而且重要的是,作到了Dalvik和ART環境的全版本兼容。阿里百川結合手淘在實際工程中使用Andfix的經驗,對相關業務邏輯解耦後,推出了阿里百川Hotfix方案,並獲得了良好的反響。編程
此時的百川Hotfix已是一個很不錯的產品了,對於基本的代碼修復需求均可以解決,安全性和易用性都作的比較好。然而,它所依賴的基石,Andfix自己,是有侷限性的。且不說其底層固定結構的替換方案穩定性很差,其使用範圍也存在着諸多限制,雖然能夠經過改造代碼繞過限制來達到相同的修復目的,但這種方式既不優雅也不方便。而更大的問題是,Andfix只提供了代碼層面的修復,對於資源和so的修復都還未能實現。數組
再看一下同期的其餘熱修復方案,此時的熱修復技術可謂是百花齊放,微信的Tinker、QQ空間的Nuwa、餓了麼的Amigo、美團的Robust等等,各個熱修復方案爭相發佈,都聲稱本身能夠作到全方位全功能的熱修復。不過他們各自有自身的侷限性,或者不夠穩定,或者補丁過大,或者效率低下,或者使用起來過於繁瑣,大部分技術上看起來彷佛可行,但實際體驗並很差。而在咱們看來,有不少技術細節可以作得更加完美。安全
終於在2017年6月11日,手淘技術團隊聯合阿里雲正式發佈了新一代Android移動熱修復方案——Sophix。微信
Sophix的橫空出世,將會打破各家熱修復技術紛爭的局面。咱們能夠滿懷信心地說,在Android熱修復的三大領域:代碼修復、資源修復、so修復方面,以及方案的安全性和易用性方面,Sophix都作到了業界領先。app
Sophix的核心設計理念,就是非侵入性。框架
咱們的打包過程不會侵入到apk的build流程中。咱們所須要的,只有已經生成完畢的新舊apk,而至於apk是如何生成的——是Android Studio打包出來的、仍是Eclipse打包出來的、或者是自定義的打包流程,咱們一概不關心。在生成補丁的過程當中間既不會改變任何打包組件,也不插入任何AOP代碼,咱們極力作到了——不添加任何超出開發者預期的代碼,以免多餘的熱修復代碼給開發者帶來困擾。函數
在Sophix中,惟一須要的就是初始化和請求補丁兩行代碼,甚至連入口Application類咱們都不作任何修改,這樣就給了開發者最大的透明度和自由度。咱們甚至從新開發了打包工具,使得補丁工具操做圖形界面化,這種所見即所得的補丁生成方式也是阿里熱修復獨家的。所以,Sophix的接入成本也是目前市面上全部方案裏最低的。工具
這種非侵入式熱更新理念,是咱們在設計過程當中從用戶使用角度進行了深刻思考而提煉出的核心思想。
這裏的用戶,指的天然是廣大的開發者。對於開發者而言,熱修復應該是一個與業務無關的SDK組件,在整個開發過程當中感知不到它的存在。最理想的狀況,就是開發者拿過來兩個apk,一個是已經安裝在手機上的apk,另外一個是將要發佈出去的apk。咱們直接經過工具,就能夠根據這兩個apk生成補丁,而後把這個補丁下發給已經安裝的舊app上,就能夠直接加載,使舊app重生爲新的app。而這個加載了補丁包新app,在功能和使用上,將會和直接安裝新apk別無二致。
至於Sophix這個名字,是來源於Sophic(明智的)+ FIX,一個更明智的熱修復方案。
下面的這張表格,從幾個熱修復最重要的維度,把Sophix和另外兩個主要商業化熱修復方案進行了比較。
方案對比 | Sophix | Tinker | Amigo |
---|---|---|---|
DEX修復 | 同時支持即時生效和冷啓動修復 | 冷啓動修復 | 冷啓動修復 |
資源更新 | 差量包,不用合成 | 差量包,須要合成 | 全量包,不用合成 |
SO庫更新 | 插樁實現,開發透明 | 替換接口,開發不透明 | 插樁實現,開發透明 |
性能損耗 | 低,僅冷啓動狀況下有些損耗 | 高,有合成操做 | 低,全量替換 |
四大組件 | 不能新增 | 不能新增 | 能新增 |
生成補丁 | 直接選擇已經編好的新舊包在本地生成 | 編譯新包時設置基線包 | 上傳完整新包到服務端 |
補丁大小 | 小 | 小 | 大 |
接入成本 | 傻瓜式接入 | 複雜 | 通常 |
Android版本 | 所有支持 | 所有支持 | 所有支持 |
安全機制 | 加密傳輸及簽名校驗 | 加密傳輸及簽名校驗 | 加密傳輸及簽名校驗 |
服務端支持 | 支持服務端控制 | 支持服務端控制 | 支持服務端控制 |
能夠看到,Sophix在各個指標上全面佔優。而其中惟一支持不完善的地方就是四大組件,四大組件能夠修改代碼,可是沒法作到新增。這是由於若是要新增四大組件,必須在AndroidManifest裏面預先插入代理組件,而且儘量聲明全部權限,而這麼作就會給原先的app添加不少臃腫的代碼,對app運行流程的侵入性很強,因此,本着對開發者透明與代碼極簡的原則,咱們沒有作這種多餘的處理。
直接看錶格的話,其中有些技術細節可能還看不太明朗,那麼接下來,我將從各個角度,深度解讀Sophix的技術優點以及它與同類技術的差異。
Sophix的誕生,起初是對原先的阿里百川Hotfix 1.X版本進行升級衍進。
原先百川Hotfix服務端的整套請求控制流程,以及安全檢查這部分,是與熱修復功能相對分離的,所以咱們依舊保留了這部分的邏輯。
而本來的熱修復方案,主要限制在於Andfix自己,咱們最開始也是從突破原先修復限制入手,但願可以基於原先的Andfix代碼作一些必要的改進。然而最終發現,Andfix自身限制幾乎是沒法繞過的,在運行時對原有類的結構是已經固化在內存中的,它的一些動態屬性和很難進行擴展。而且因爲Android系統的碎片化,廠商的虛擬機底層結構都不是肯定的,所以直接基於原先機制進行擴展的風險很大。
因此咱們繞開了具體的技術實現細節,直接從修復的原理入手,對原先的代碼修復技術進行深挖和改良。
回顧爲期九個多月的探索與開發,這其中無處不體現着咱們對易用性和優雅性的極致追求,在技術先進性與易用性上咱們達到了完美的平衡。因此,當咱們再回頭看目前市面上的其餘熱修復技術,真的有一種「曾經滄海難爲水」的感受。
代碼修復有兩大主要方案,一種是阿里系的底層替換方案,另外一種是騰訊系的類加載方案。
這兩類方案各有優劣:
底層替換方案是在已經加載了的類中直接替換掉原有方法,是在原來類的基礎上進行修改的。於是沒法實現對與原有類進行方法和字段的增減,由於這樣將破壞原有類的結構。
一旦補丁類中出現了方法的增長和減小,就會致使這個類以及整個Dex的方法數的變化。方法數的變化伴隨着方法索引的變化,這樣在訪問方法時就沒法正常地索引到正確的方法了。若是字段發生了增長和減小,和方法變化的狀況同樣,全部字段的索引都會發生變化。而且更嚴重的問題是,若是在程序運行中間某個類忽然增長了一個字段,那麼對於原先已經產生的這個類的實例,它們仍是原來的結構,這是沒法改變的。而新方法使用到這些老的實例對象時,訪問新增字段就會產生不可預期的結果。
這是這類方案的固有限制,而底層替換方案最爲人詬病的地方,在於底層替換的不穩定性。
傳統的底層替換方式,不管是Dexposed、Andfix或者其餘安全界的Hook方案,都是直接依賴修改虛擬機方法實體的具體字段。例如,改Dalvik方法的jni函數指針、改類或方法的訪問權限等等。這樣就帶來一個很嚴重的問題,因爲Android是開源的,各個手機廠商均可以對代碼進行改造,而Andfix裏ArtMethod的結構是根據公開的Android源碼中的結構寫死的。若是某個廠商對這個ArtMethod結構體進行了修改,就和原先開源代碼裏的結構不一致,那麼在這個修改過了的設備上,通用性的替換機制就會出問題。這即是不穩定的根源。
而咱們也對代碼的底層替換原理從新進行了深刻思考,從克服其限制和兼容性入手,以一種更加優雅的替換思路,實現了即時生效的代碼熱修復。咱們實現的是一種無視底層具體結構的替換方式,
也就是把原先這樣的逐一替換
變成了這樣的總體替換
這麼一來,咱們不只解決了兼容性問題,而且因爲忽略了底層ArtMethod結構的差別,對於全部的Android版本都再也不須要區分,代碼量大大減小。即便之後的Android版本不斷修改ArtMethod的成員,只要保證ArtMethod數組還是以線性結構排列,就能直接適用於未來的Android 8.0、9.0等新版本,無需再針對新的系統版本進行適配了。事實也證實確實如此,當咱們拿到Google剛發不久的Android O(8.0)開發者預覽版的系統時,hotfix demo直接就能順利地加載補丁跑起來了,咱們並無作任何適配工做,魯棒性極好。
具體技術細節,能夠看這篇文章:Android熱修復升級探索——追尋極致的代碼熱替換
類加載方案的原理是在app從新啓動後讓Classloader去加載新的類。由於在app運行到一半的時候,全部須要發生變動的類已經被加載過了,在Android上是沒法對一個類進行卸載的。若是不重啓,原來的類還在虛擬機中,就沒法加載新類。所以,只有在下次重啓的時候,在還沒走到業務邏輯以前搶先加載補丁中的新類,這樣後續訪問這個類時,就會Resolve爲新類。從而達到熱修復的目的。
再來看看騰訊系三大類加載方案的實現原理。QQ空間方案會侵入打包流程,而且爲了hack添加一些無用的信息,實現起來很不優雅。而QFix的方案,須要獲取底層虛擬機的函數,不夠穩定可靠,而且有個比較大的問題是沒法新增public函數。
微信的Tinker方案是完整的全量dex加載,而且可謂是將補丁合成作到了極致,然而咱們發現,精密的武器並不是適用於全部戰場。Tinker的合成方案,是從dex的方法和指令維度進行全量合成,整個過程都是本身研發的。雖然能夠很大地節省空間,但因爲對dex內容的比較粒度過細,實現較爲複雜,性能消耗比較嚴重。實際上,dex的大小佔整個apk的比例是比較低的,一個app裏面的dex文件大小並非主要部分,而佔空間大的主要仍是資源文件。所以,Tinker方案的時空代價轉換的性價比不高。
其實,dex比較的最佳粒度,應該是在類的維度。它既不像方法和指令維度那樣的細微,也不像bsbiff比較那般的粗糙。在類的維度,能夠達到時間和空間平衡的最佳效果。基於這個準則,咱們另闢蹊徑,實現了一種徹底不一樣的全量dex替換方案。
咱們採用的也是全量合成dex的技術,這個技術是從手淘插件化框架Atlas汲取的。咱們會直接利用Android原先的類查找和合成機制,快速合成新的全量dex。這麼一來,咱們既不須要處理合成時方法數超過的狀況,對於dex的結構也不用進行破壞性重構。
從圖中能夠看到,咱們從新編排了包中dex的順序。這樣,在虛擬機查找類的時候,會優先找到classes.dex中的類,而後纔是classes2.dex、classes3.dex,也能夠看作是dex文件級別的類插樁方案。這個方式十分巧妙,它對舊包與補丁包中classes.dex的順序進行了打破與重組,最終使得系統能夠天然地識別到這個順序,以實現類覆蓋的目的。這將會大大減小合成補丁的開銷。
既然底層替換方案和類加載方案各有其優勢,把他們聯合起來不是最好的選擇嗎?Sophix的代碼修復體系正是同時涵蓋了這兩種方案。兩種方案的結合,能夠實現優點互補,徹底兼顧的做用,能夠靈活地根據實際狀況自動切換。
這兩種方案咱們都進行了重大的改進,而且從補丁生成到應用的各個環節都進行了研究,使得兩者能很好地整合在一塊兒。在補丁生成階段,補丁工具會根據實際代碼變更狀況進行自動選擇,針對小修改,在底層替換方案限制範圍內的,就直接採用底層替換修復嗎,這樣能夠作到代碼修復即時生效。而對於代碼修改超出底層替換限制的,會使用類加載替換,這樣雖然及時性沒那麼好,但總歸能夠達到熱修復的目的。
另外,運行時階段,Sophix還會再判斷所運行的機型是否支持熱修復,這樣即便補丁支持熱修復,但因爲機型底層虛擬機構造不支持,仍是會走類加載修復,從而達到最好的兼容性。
目前市面上的不少資源熱修復方案基本上都是參考了Instant Run的實現。實際上,Instant Run的推出正是推進此次熱修復浪潮的主因,各家熱修復方案,在代碼、資源等方面的實現,很大程度上地參考了Instant Run的代碼,而資源修復方案正是被拿來用到最多的地方。
簡要說來,Instant Run中的資源熱修復分爲兩步:
咱們發現,其實大量代碼都是在處理兼容性問題和找到全部AssetManager的引用處,真正的替換的邏輯其實很簡單。
咱們的方案沒有直接使用Instant Run的技術,而是另闢蹊徑,構造了一個package id爲0x66的資源包,這個包裏只包含改變了的資源項,而後直接在原有AssetManager中addAssetPath這個包就能夠了。因爲補丁包的package id爲0x66,不與目前已經加載的0x7f衝突,所以直接加入到已有的AssetManager中就能夠直接使用了。補丁包裏面的資源,只包含原有包裏面沒有而新的包裏面有的新增資源,以及原有內容發生了改變的資源。而且,咱們採用了更加優雅的替換方式,直接在原有的AssetManager對象上進行析構和重構,這樣全部原先對AssetManager對象的引用是沒有發生改變的,因此就不須要像Instant Run那樣進行繁瑣的修改了。
能夠說,咱們的資源修復方案,優越性超過了Google官方的Instant Run方案。整個資源替換的方案優點在於:
因此,咱們不要被所謂的「官方實現」束縛住手腳,其實Instant Run的開發團隊和Android framework的開發團隊並非同一個團隊,他們對於Android系統機制的理解未必十分深刻。只要你認真研讀系統代碼,實現一個比官方更好的方案絕非難事。因此我想說的是,要想實現技術方案的突破,首先就須要破除所謂「權威」的觀念。
資源修復的更多技術細節,可經過這篇文章一探究竟:Android熱修復升級探索——資源更新之新思路
so庫的修復本質上是對native方法的修復和替換。
咱們知道JNI編程中,native方法能夠經過動態註冊和靜態註冊兩種方式進行。動態註冊的native方法必須實現JNI_OnLoad
方法,同時實現一個JNINativeMethod[]
數組,靜態註冊的native方法必須是Java+類完整路徑+方法名
的格式。
動態註冊的native方法映射經過加載so庫過程當中調用JNI_OnLoad方法調用完成,靜態註冊的native方法映射是在該native方法第一次執行的時候才完成映射,固然前提是該so庫已經load過。
咱們採用的是相似類修復反射注入方式。把補丁so庫的路徑插入到nativeLibraryDirectories數組的最前面,就可以達到加載so庫的時候是補丁so庫,而不是原來so庫的目錄,從而達到修復的目的。
採用這種方案,徹底由Sophix在啓動期間反射注入patch中的so庫。對開發者依然是透明的。不用像某些其餘方案須要手動替換系統的System.load來實現替換目的。
熱修復是一個與業務徹底無關的模塊,開發者若是要本身實現一套可靠的熱修復框架,將花費大量時間和精力。雖然市面上已經有不少開源的熱修復實現,然而其中的不少坑,每每要踩過才知道,等你把這些坑一一踩過以後,可能大量的用戶已經對你失去信心。因此,依靠一個穩定可靠、並且簡單實用的商業版本,反而能使各方面的成本降到最低。而且,熱修復並非簡單的客戶端SDK,它還包含了安全機制和服務端的控制邏輯,這整條鏈路也不是短期內能夠快速完成的。
仍是那句老話,專業是事交給專業的人去作。開發者應該把更多時間精力放到本身的核心業務之中。
Sophix提供了一套更加完美的客戶端服務端一體的熱更新方案。作到了圖形界面一鍵打包、加密傳輸、簽名校驗和服務端控制發佈與灰度功能,讓你用最少的時間實現最強大可靠的全方位熱更新。而且在代碼修復、資源修復、SO庫修復方面,都作到了業界最佳。
不少人會把熱修復技術跟其餘國內廠商的「黑科技」混爲一談。有人說,大家國內開發者就是瞎搞,就不能給咱們Android用戶一個更加純淨的環境嗎?
這裏我須要澄清一下。熱修復技術不一樣於其餘國內的Android「黑科技」。就好比,國內Android進程保活,是讓app持續駐留在後臺避免被系統殺死,這既耗費手機電量又佔內存,浪費了不少手機資源。再好比,app自行定製的推送服務,無節操地對用戶進行信息轟炸。還有更過度的全家桶,一個app同時拉起一票app,而且長期佔着內存,使得手機卡頓不堪。總歸,這些技術都是爲了app廠商的利益而損害手機使用者的實際體驗。
而熱修復技術是徹底不一樣的,它達到的是一個手機用戶和開發者共贏的目的。不只廠商能夠快速迭代更新app,使得功能能最快上線。而且因爲熱更新過程是毫無感知的,手機用戶也減小了繁瑣的更新步驟,節省了大量等待更新的時間。這其實是改善了Android的生態環境。只是這其中最重要的,是要保證熱修復功能的穩定性。而Sophix的穩定性,是通過了無數開發者檢驗的,而且還有手淘多年深厚的技術沉澱做爲保障。
前段時間,蘋果封殺了iOS的熱修復功能,不少人就所以不看好熱修復技術了。這裏我想說的是,蘋果的政策並不能證實他有多先進,相反,做爲獨裁者,蘋果作過不少不得人心的事,就好比前段時間封殺微信的文章打賞功能。熱修復功能被禁止,會使得不少app不得不靠直接發版進行更新,這樣一旦新版本出了問題,整個更新迭代過程變得十分漫長。而且一些試驗性功能沒法進行灰度,這就使得一個重要功能的更新將直接全量發版,若是功能不夠穩定,波及範圍就變得很是廣。並且,用戶須要從新下載整個app,不只流程漫長,本來不到1MB的補丁就能解決的事,如今不得不下載幾十MB的完整包才能更新。
再看回Android的狀況,Android熱修復和iOS是有極大不一樣的。主要有兩個方面:
谷歌在中國沒有像蘋果那樣的控制力,即便它想要封殺也不可能,國內是有各個安卓應用市場的,沒有統一的app安裝渠道。另外,Android是開源的,各個廠商均可以作定製,想統一各家的安裝渠道幾乎是不可能的。
咱們對於將來是很樂觀的,Android的熱修復領域不只不會受到封殺,反而還有很大的發展空間。咱們正在嘗試支持各大加固廠商,目前阿里聚安全修復已經支持了Sophix,熱修復結合安全加固,將會使得app的穩定性和安全性更加堅如盤石。甚至後續還能夠與系統廠商合做,對系統app乃至系統組件進行修復,這樣就能夠避免頻繁OTA升級。
所以,熱修復所能發揮是價值將是十分巨大的。熱修復還能夠與其餘領域進行碰撞,引起無限的可能性。在這裏,咱們歡迎全部應用廠商以及ROM廠商與咱們合做,共同使得Android的生態更加完善。