業界首個非侵入式熱修復方案Sophix重磅推出,顛覆移動端傳統更新流程!
阿里巴巴對Android熱修復技術已經進行了長達多年的探索。
最開始,是手淘基於Xposed進行了改進,產生了針對Android Dalvik虛擬機運行時的Java Method Hook技術,Dexposed。但這個方案由於對底層Dalvik結構過於依賴,最終無法繼續兼容Android5.0以後ART虛擬機,因此作罷。
後來支付寶提出了新的熱修復方案Andfix。Andfix同樣是一種底層結構替換的方案,也達到了運行時生效即時修復的效果,並且重要的是,做到了Dalvik和ART環境的全版本兼容。阿里百川結合手淘在實際工程中使用Andfix的經驗,對相關業務邏輯解耦後,推出了阿里百川Hotfix方案,並得到了良好的反響。
此時的百川Hotfix已經是一個很不錯的產品了,對於基本的代碼修復需求都可以解決,安全性和易用性都做的比較好。然而,它所依賴的基石,Andfix本身,是有局限性的。且不說其底層固定結構的替換方案穩定性不好,其使用範圍也存在著諸多限制,雖然可以通過改造代碼繞過限制來達到相同的修複目的,但這種方式既不優雅也不方便。而更大的問題是,Andfix只提供了代碼層面的修復,對於資源和so的修復都還未能實現。
再看一下同期的其他熱修復方案,此時的熱修復技術可謂是百花齊放,微信的Tinker、QQ空間的Nuwa、餓了么的Amigo、美團的Robust等等,各個熱修復方案爭相發布,都聲稱自己可以做到全方位全功能的熱修復。不過他們各自有自身的局限性,或者不夠穩定,或者補丁過大,或者效率低下,或者使用起來過於繁瑣,大部分技術上看起來似乎可行,但實際體驗並不好。而在我們看來,有很多技術細節能夠做得更加完美。
終於在2017年6月11日,手淘技術團隊聯合阿里雲正式發布了新一代Android移動熱修復方案——Sophix。
Sophix的橫空出世,將會打破各家熱修復技術紛爭的局面。我們可以滿懷信心地說,在Android熱修復的三大領域:代碼修復、資源修復、so修復方面,以及方案的安全性和易用性方面,Sophix都做到了業界領先。
設計理念
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的技術優勢以及它與同類技術的差別。可以看到,Sophix在各個指標上全面佔優。而其中唯一不支持的地方就是四大組件的修復,這是因為如果要修復四大組件,必須在AndroidManifest裡面預先插入代理組件,並且儘可能聲明所有許可權,而這麼做就會給原先的app添加很多臃腫的代碼,對app運行流程的侵入性很強,所以,本著對開發者透明與代碼極簡的原則,我們沒有做這種多餘的處理。
技術分析
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,並通過反射調用addAssetPath,把這個完整的新資源包加入到AssetManager中。這樣就得到了一個含有所有新資源的AssetManager。
- 找到所有之前引用到原有AssetManager的地方,通過反射,把引用處替換為AssetManager。
我們發現,其實大量代碼都是在處理兼容性問題和找到所有AssetManager的引用處,真正的替換的邏輯其實很簡單。
我們的方案沒有直接使用Instant Run的技術,而是另闢蹊徑,構造了一個package id為0x66的資源包,這個包里只包含改變了的資源項,然後直接在原有AssetManager中addAssetPath這個包就可以了。由於補丁包的package id為0x66,不與目前已經載入的0x7f衝突,因此直接加入到已有的AssetManager中就可以直接使用了。補丁包裡面的資源,只包含原有包裡面沒有而新的包裡面有的新增資源,以及原有內容發生了改變的資源。並且,我們採用了更加優雅的替換方式,直接在原有的AssetManager對象上進行析構和重構,這樣所有原先對AssetManager對象的引用是沒有發生改變的,所以就不需要像Instant Run那樣進行繁瑣的修改了。
可以說,我們的資源修復方案,優越性超過了Google官方的Instant Run方案。整個資源替換的方案優勢在於:
- 不修改AssetManager的引用處,替換更快更完全。(對比Instanat Run以及所有copycat的實現)
- 不必下發完整包,補丁包中只包含有變動的資源。(對比Instanat Run、Amigo等方式的實現)
- 不需要在運行時合成完整包。不佔用運行時計算和內存資源。(對比Tinker的實現)
資源修復的更多技術細節,可通過這篇文章一探究竟:Android熱修復升級探索——資源更新之新思路
so庫修復
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「黑科技」。就比如,國內Android進程保活,是讓app持續駐留在後台避免被系統殺死,這既耗費手機電量又占內存,浪費了很多手機資源。再比如,app自行定製的推送服務,無節操地對用戶進行信息轟炸。還有更過分的全家桶,一個app同時拉起一票app,並且長期占著內存,使得手機卡頓不堪。總歸,這些技術都是為了app廠商的利益而損害手機使用者的實際體驗。
而熱修復技術是完全不同的,它達到的是一個手機用戶和開發者雙贏的目的。不僅廠商可以快速迭代更新app,使得功能能最快上線。並且由於熱更新過程是毫無感知的,手機用戶也減少了繁瑣的更新步驟,節省了大量等待更新的時間。這實際上是改善了Android的生態環境。只是這其中最重要的,是要保證熱修復功能的穩定性。而Sophix的穩定性,是經過了無數開發者檢驗的,並且還有手淘多年深厚的技術沉澱作為保障。
Android與iOS熱修復的不同
前段時間,蘋果封殺了iOS的熱修復功能,很多人就因此不看好熱修復技術了。這裡我想說的是,蘋果的政策並不能證明他有多先進,相反,作為獨裁者,蘋果做過很多不得人心的事,就比如前段時間封殺微信的文章打賞功能。熱修復功能被禁止,會使得很多app不得不靠直接發版進行更新,這樣一旦新版本出了問題,整個更新迭代過程變得十分漫長。並且一些試驗性功能無法進行灰度,這就使得一個重要功能的更新將直接全量發版,如果功能不夠穩定,波及範圍就變得非常廣。而且,用戶需要重新下載整個app,不僅流程漫長,原本不到1MB的補丁就能解決的事,現在不得不下載幾十MB的完整包才能更新。
再看回Android的情況,Android熱修復和iOS是有極大不同的。主要有兩個方面:
谷歌和蘋果在中國的地位不同
Android和IOS的開放性不同
谷歌在中國沒有像蘋果那樣的控制力,即使它想要封殺也不可能,國內是有各個安卓應用市場的,沒有統一的app安裝渠道。另外,Android是開源的,各個廠商都可以做定製,想統一各家的安裝渠道幾乎是不可能的。
未來,無限可能!
我們對於未來是很樂觀的,Android的熱修復領域不僅不會受到封殺,反而還有很大的發展空間。我們正在嘗試支持各大加固廠商,目前阿里聚安全修復已經支持了Sophix,熱修復結合安全加固,將會使得app的穩定性和安全性更加堅不可摧。甚至後續還可以與系統廠商合作,對系統app乃至系統組件進行修復,這樣就可以避免頻繁OTA升級。
因此,熱修復所能發揮是價值將是十分巨大的。熱修復還可以與其他領域進行碰撞,引發無限的可能性。在這裡,我們歡迎所有應用廠商以及ROM廠商與我們合作,共同使得Android的生態更加完善。
推薦閱讀:
※開發一個App要100萬?新技術只要1萬
※UWA 新功能 | 優化方法進階—定位子函數的開銷
※為什麼他們都用UWA GOT?
※【輿情熱點】空殼APP黑色產業鏈 比想像中更可怕