1. 程式人生 > >【轉】Android外掛化:從入門到放棄

【轉】Android外掛化:從入門到放棄

本文根據包建強在2016GMTC全球移動開發大會上的演講整理而成。

首先自我介紹一下,我叫包建強,是這個分場的主持人。我去年寫了一本書,叫《App研發錄》,相信有很多從事技術的朋友看過或買過。

引言

先簡單介紹一下Android外掛化。很早之前已經有公司在研究這項技術,淘寶做得比較早,但淘寶的這項技術一直是保密的。直到2015年才陸續出現很多框架,Android外掛化分成很多技術流派,實現的方式都不太一樣。我今天的主題就是,Android外掛化的不同流派、不同思想,以及做外掛化需要掌握哪些知識。

今天分享的題目是“從入門到放棄”。外掛化技術不是45分鐘演講能說清楚的,我大致算了一下,要真能講清楚這門技術起碼需要六個小時,每個小時講一個主題,包括Android底層系統與外掛化相關的類、四大元件的原理與相應外掛化實現方式、增量更新、AAPT等技術 。今天的分享濃縮成45分鐘,希望大家可以獲益。

分享內容分成七大部分。首先是前世今生,即外掛化的歷史。第二部分是入門知識,即與外掛化有關的Android系統底層的實現原理。第三部分是技術流派,即目前Android外掛化多種技術流派及其具體不同的實現方式。第四部分是周邊相關的技術實現。第五部分是目前國內流行的開源框架,包括各個公司正在使用的框架,還有流傳不是很廣但意義也很重大的框架。第六,是針對Android外掛化的一些問題的經驗分享。最後,是Android外掛化的未來——我們是否該放棄這門技術。

前世今生

Android外掛化的歷史,可能是本次演講最有價值的內容。我梳理了三年前到現在Android外掛化發展的一些規律。

首先,要記住2012年這個時間點。2012年的時候,就有人做外掛化技術,是大眾點評的屠毅敏,他推出了AndroidDynamicLoader框架,用Fragment來實現。大眾點評是國內做App比較早的公司,他們積累了很多的經驗,尤其是外掛化技術 。通過動態載入不同的Fragement,把想換的頁面都換掉。我們也是在這個專案中第一次看到了如何通過addAssetPath來讀取外掛中的資源。

2013年,出現了23Code。23Code提供了一個殼,在這個殼裡可以動態下載外掛,然後動態執行。可以在殼外編寫各種各樣的控制元件,放在這個框架下去執行。這就是Android外掛化技術。這個專案的作者和開源地址,目前不是很清楚。

2014年初,大家也許看過一個視訊,阿里一位員工做了一次技術分享,專門講淘寶的Altas技術,以及這項技術的大方向。但是很多技術細節沒有分享。

然後是任玉剛的里程碑式的專案。2014年底,玉剛釋出了一個Android外掛化專案,起名為dynamic-load-apk,這跟後續介紹的很多外掛化專案都不太一樣。它沒有Hook太多的系統底層方法,而是從上層,即App應用層解決問題,建立一個繼承自Activity的ProxyActivity類,然後讓外掛中的所有Activity都繼承自ProxyActivity,並重寫Activity所有的方法。之所以說這個專案是里程碑式的,是因為在2015年之前業界沒有太多資料可以參考。之後曾和玉剛聊天,他十分感慨當年如何舉步維艱地開發這個框架。我當時在途牛工作,使用了這個Android外掛化框架。

2015年4月,一個新框架推出來,叫OpenAltas,後來改名為ACDD。這個框架參考了淘寶App的很多經驗,主要就是Hook的思想,同時,還首次提出來通過擴充套件AAPT來解決外掛與宿主的資源id衝突的問題。

2015年8月,張勇釋出DroidPlugin。這是Android外掛化中第二個里程碑式的專案,這個專案太牛了,能把任意的App都載入到宿主裡。可以基於這個框架寫一個宿主App,然後就可以把別人寫的App都當作外掛來載入。這個框架的功能的確很強大,但強大的代價就是要改寫很多Android系統的底層程式碼,更別提這哥們還比較懶,沒有制訂任何說明文件,導致技術人員掌握這個框架不太容易。360的田維術曾編寫了一系列文章,專門介紹這個框架,後面我會介紹。

再之後就是百花齊放的時代了,GitHub上有很多外掛化框架,但這些框架影響都不大,我們這裡就略過了。

接下來登場的是熱修復技術。2015年5月,iOS推出了JSPatch,JSPatch通過Runtime的機制,能迅速修復線上App任何一個類的任何一個方法。而當時的Android系統沒有能迅速替換的方式。於是,在2015年9月,有人找到了實現迅速替換的途徑,就是Andfix,後面會講它的原理。

2015年10月,大眾點評的賈吉鑫做了一個專案,起名為Nuwa(女媧),主要思路跟Andfix差不多,都是解決Android的修復問題,能修復線上的任何一個方法。可惜後來沒有繼續維護。

最後,2015年底,仍然是Android外掛化框架,福建的林廣亮提出了一個新機制——Small框架,這個機制不太一樣的地方就是,通過指令碼的方式來解決資源衝突的問題。

入門知識

介紹完Android外掛化的歷史,接下來講一講Android外掛化需要的Android系統底層知識。在座的基本都是做Android開發出身,或許有一半到三分之一是資深的,還有的只做了一兩年,希望對外掛化有更深的認識。要想完全明白外掛化技術,首先需要了解Android系統的底層實現。

首先,做Android系統原始碼的人應該非常熟悉Binder,如果沒有它真的寸步難行。Binder涉及兩層技術。你可以認為它是一箇中介者模式,在客戶端和伺服器端之間,Binder就起到中介的作用,這是我這段時間對Binder的思考。要實現四大元件的外掛化,就需要在Binder上做修改。Binder服務端的內容沒辦法修改,只能改客戶端的程式碼。四大元件每個元件的客戶端都不太一樣,這個需要大家自己去發現,時間關係,這裡就不多說了。

學習Binder的最好方式就是AIDL。你可以讀到很多關於AIDL的資料,通過制訂一個aidl檔案自動生成一個Java類,研究一下這個Java類的每個方法和變數,然後再反過來看四大元件,其實都是跟AIDL差不多的方式。

其次,是App打包的流程。程式碼寫完了,執行一次打包操作,中途經歷了資源打包、dex生成、簽名等過程。其中最重要的就是資源的打包,即AAPT這一步,如果宿主和外掛的資源id衝突,一種解決辦法就是在這裡做修改。

第三,App在手機上的安裝流程也很重要。熟悉安裝流程不僅對外掛化有幫助,在遇到安裝bug的時候也非常重要。手機安裝App的時候,經常會有下載異常,提示資源包不能解析,這時需要知道安裝App的這段程式碼在什麼地方,這只是第一步。第二步需要知道,App下載到本地後,具體要做哪些事情。手機有些目錄不能訪問,App下載到本地之後,放到哪個目錄下,然後會生成哪些檔案。外掛化有個增量更新的概念,如何下載一個增量包,從本地具體哪個位置取出一個包,這個包的具體命名規則是什麼,等等。這些細節都必須要清楚明白。

第四,是App的啟動流程。Activity啟動有幾種方式?一種是寫一個startActivity,第二種是點選手機App,通過手機系統裡的Launcher機制,啟動App裡預設的Activity。通常,App開發人員喜聞樂見的方式是第二種。那麼第一種方式的啟動原理是什麼呢?另外,啟動的時候,main函式在哪裡?這個main函式的位置很重要,我們可以對它所在的類做修改,從而實現外掛化。

第五點更重要,做Android外掛化需要控制兩個地方。首先是外掛Dex的載入,如何把外掛Dex中的類載入到記憶體?另外是資源載入的問題。外掛可能是apk也可能是so格式,不管哪一種,都不會生成R.id,從而沒辦法使用。這個問題有好幾種解決方案。一種是是重寫Context的getAsset、getResource之類的方法,偷換概念,讓外掛讀取外掛裡的資源,但缺點就是宿主和外掛的資源id會衝突,需要重寫AAPT。另一種是重寫AMS中儲存的外掛列表,從而讓宿主和外掛分別去載入各自的資源而不會衝突。第三種方法,就是打包後,執行一個指令碼,修改生成包中資源id。

第六點,在實施外掛化後,如何解決不同外掛的開發人員的工作區問題。比如,外掛1和外掛2,需要分別下載哪些程式碼,如何獨立執行?就像機票和火車票,如何只執行自己的外掛,而不執行別人的外掛?這是協同工作的問題。火車票和機票,這兩個Android團隊的各自工作區是不一樣的,這時候就要用到Gradle指令碼了,每個專案分別有各自的倉庫,有各自不同的打包指令碼,只需要把自己的外掛跟宿主專案一起打包執行起來,而不用引入其他外掛,還有更厲害的是,也可以把自己的外掛當作一個App來打包並執行。

上面介紹了外掛化的入門知識,一共六點,每一點都需要花大量時間去理解。否則,在面對外掛化專案的時候,很多地方你會一頭霧水。而只要理解了這六點核心,一切可迎刃而解。

技術流派

接下來是技術流派。技術流派目前分三種。

第一種是動態替換,也就是Hook。可以在不同層次進行Hook,從而動態替換也細分為若干小流派。可以直接在Activity裡做Hook,重寫getAsset的幾個方法,從而使用自己的ResourceManager和AssetPath;也可以在更抽象的層面,也就是在startActivity方法的位置做Hook,涉及的類包括ActivityThread、Instrumentation等;最高層次則是在AMS上做修改,也就是張勇的解決方案,這裡需要修改的類非常多,AMS、PMS等都需要改動。總之,在越抽象的層次上做Hook,需要做的改動就越大,但好處就是更加靈活了。沒有哪一個方法更好,一切看你自己的選擇。

第二種是靜態代理,這是任玉剛的框架採取的思路。寫一個PluginActivity繼承自Activity基類,把Activity基類裡面涉及生命週期的方法全都重寫一遍,外掛中的Activity是沒有生命週期的,所以要讓外掛中的Activity都繼承自PluginActivity,這樣就有生命週期了。

第三種是Dex合併,Dex合併就是Android熱修復的思想。剛才說到了兩個專案——AndFix和Nuwa,它們的思想是相同的。原生Apk自帶的Dex是通過PathClassLoader來載入的,而外掛Dex則是通過DexClassLoader來載入的。但有一個順序問題,是由Davlik的機制決定的,如果宿主Dex和外掛Dex都有一個相同名稱空間的類的方法,那麼先載入哪個Dex,哪個Dex中的這個類的方法將會佔山為王,後面其他同名方法都替換了。所以,AndFix熱修復就是優先載入外掛包中的Dex,從而實現熱修復。由於熱修復的外掛包通常只包括一個類的方法,體量很小,和正常的外掛不是一個數量級的,所以只稱為熱修復補丁包,而不是外掛。

技術周邊

關於技術周邊的內容有三個部分。首先是AAPT,資源衝突,就是說預設App應用,外掛裡的資源和資料資源衝突,如果不引入這個資源,相安無事。很多時候就算有衝突也無所謂,問題就出在外掛引用資源的時候有衝突了,無法解決,怎麼辦?那就要立刻改寫App。有一個關於打包的App,可以加當前的字首,改成你想要的。比如,火車票和酒店分別取名,這樣就可以指定字首、打包,插進一個模組,資源的字首都不一樣。小米也承認,會佔用0x11這個字首。這是需要關注的一個點。

第二是增量更新。360目前最牛逼的地方是,把所有資料跟之前一個版本差,產生增量的資料。他們當然也更新了外掛化。360的劉存棟做了一個增量更新的框架。可以在後臺伺服器把兩個版本的Android App做拆分,然後把增量包下載到本地,再跟本地進行合併,提供一個STK,再合在一起,這就是增量更新。

第三是外掛管理平臺,要管理每個版本的差異、每個外掛最低資料的版本號。

尷尬困境

接下來是最近兩年出現的一些尷尬困境。

首先,外掛化已經淪落為修bug的工具。這跟外掛化的初衷不一樣,外掛化是實現新功能,而不是修復bug。

其次,外掛化現在有一個更好的替代品——RN。接下來,RN會是真正實現動態化的最佳方式,至少我是這麼認為的。

第三,外掛化技術只在中國有市場。國外的公司根本不看好這項技術,這可能是因為他們用GooglePlay,而谷歌官方不建議用外掛化這種方式。國外開發者不敢越雷池半步。

第四,四大元件都需要做外掛化嗎?根據我自己的經驗,做一款電商或旅遊類的App,有一兩百個Activity,Service用得很少,ContentProvider和BroadcastReceiver基本不用。所以,這種App實現Activity和Service的外掛化就夠了。像手機助手這樣的App,非常頻繁使用四大元件,所以四大元件都必須實現外掛化,這也是張勇當年在360開發出DroidPlugin支援四大元件的原因。

未來方向

最後講一下Android外掛化未來的方向。阿里一位技術專家馮森林曾說,外掛化最厲害的發展方向應該是每個Activity都是一個外掛。這個觀點在外掛化技術交流群裡一提出來之後,群裡所有人都沉默良久。仔細想想,外掛化的未來好像的確是這個發展方向,這樣就可以將任何一個出問題的Activity迅速替換。但當RN一經提出,這個觀點就慢慢消失了,RN比外掛化更輕量級,越來越多人選擇了RN。

其次,就是雙開技術。雙開技術是現在非常火的一項技術。如果想實現這種機制,一定要實現上面講的外掛化所涉及的內容。寧波一位高中生Lody,他從初三就開始研究這門技術,將很多不錯的雙開專案放在GitHub上。

第三,剛才講的所有資料都增量下載的機制,大家都可以實施一下,雖然做起來很麻煩,但是一旦實現,會讓你的App非常快。比如,你每次進入都需要重新整理城市列表嗎?大約幾百KB,即使你開gzip,重新整理速度仍然很慢,這時候增量更新就是一個很好的方式。

最後是內功的修煉。通讀一遍上面列出來的基礎知識,然後再去做App應用,你會清楚知道靜態廣播、動態廣播具體什麼時候用,什麼情況下可能出bug。AIDL可能會很少用,但真正做設計和實現的時候,這個基本功就非常重要了。所以,外掛化只是一門技術,你最應該關注的是其背後的本質,也就是內功修煉。