外掛化踩坑之路——Small和Atlas方案對比

WeaponZhi發表於2017-09-17

外掛化算是去年到今年一直比較火的一個技術了,各個開源庫的方案實際上原理都大差不離,但在整合和使用上有一定的區別,不同的方案針對的場景也不同,下面就主要分析一下 Small 和 Atlas ,他們的使用場景,優缺點,以及踩過的坑都會一一介紹。

作為一個公司 Android 團隊架構組成員,肯定就要接觸最前沿的技術嘛,這之前我一直在研究長連線相關的技術,封裝了一套以 Netty 為核心的 TCP 客戶端庫,當時架構組另一位成員一直在研究外掛化,但他手上還有一些別的事要做,外掛化就擱置了,技術組需求評審的時候,我自告奮勇,決心要完成專案的外掛化改造。

面對未知的挑戰,人在擁有一定程度自信的時候,會毫不畏懼,但真正上戰場的時候,可能就不是那麼一回事了,該踩的坑還是要踩一踩的。

元件化和外掛化

在具體介紹一個具體的外掛化框架之前,我們得先區分好 元件化,和外掛化,這兩個術語的含義,這兩個術語都是最近移動端,特別是 Android 端非常火的概念,但能把這兩個概念理清楚的人並不多。

元件化實際上是一種程式設計思想,而外掛化是一種實實在在的技術,元件化是為了程式碼的高度複用而出現的,我們可以通過把不同模組的業務做成一個個獨立的 Library ,可以單獨對這些 Library 進行版本管理,從而可以供給給想使用它的一切 Apk,這樣做的好處不僅可以提高程式碼的複用性,而且也可以幫助專案進行業務解耦,提升開發效率。

外掛化是為了解決應用越來越龐大、佔用記憶體越來越高、Apk 體量過大、解決65535方法數等複雜問題而出現的,外掛化會把各個解耦業務單獨的封裝到 APK 外掛中,通過外掛附屬到宿主 APK 中,從而完成功能的實現,這些獨立的 APK 外掛甚至可以單獨打包成應用。比如手機淘寶,它不僅有淘寶這個宿主應用本身的一些功能,還有一些像聚划算、書城這類的其他應用也需要接入進來,那麼如果沒有外掛化,像聚划算的開發人員就要同時維護兩套程式碼,一套是自己的聚划算專案,一套是接入到手淘的聚划算專案,出現了Bug也得同時去兩套程式碼去更改,這在管理和開發效率上就會非常受限。但如果我們把聚划算做成一個外掛,那麼哪個應用要接入聚划算,只要把這個 APK 包接入進來即可,在程式碼中的就是一些 compile 和 gradle 的配置,外掛內部的程式碼完全不用你操心了。

當然,即使你是一個獨立的應用,你也可以把外掛化和元件化思想結合起來,把你的業務模組元件化後,再分別把這些業務做成可以獨立打包的外掛,這樣,你的應用不僅具備了外掛化的能力和優勢,還可以通過 so 包替換或者打補丁,完成線上熱修復的功能,甚至可以替代常規的功能發版。

Atlas

之前做外掛化的那個同事建議我嘗試 Atlas,因為 Atlas 是目前阿里系一個比較成熟的方案,而且有一個完整的團隊在維護,值得信賴。那麼就先看看 Atlas 是怎麼回事吧。

Atlas 是伴隨著手機淘寶不斷髮展而衍生出來的一個執行於 Android 系統上的外掛化框架,也可以叫動態元件化框架,主要提供瞭解耦化、元件化、動態性的支援。是目前比較成熟的方案,功能強大,但相對的,使用和整合的難度也比較大。

這裡就要吐槽一下了,實際上我對 Atlas 文件不是很滿意,說實話寫的非常不詳細,對觀看者的技術要求特別高,甚至連常規的接入說明都不是太清晰,gradle 的一些配置說明都不全,我在學習和使用的過程中可謂是非常痛苦。大家接入這些框架的時候,最好加入一下他們官網提供的討論群,裡面有很多前輩或者是已經踩坑過的老手,互相交流會事半功倍。這篇文章是一個大概的概述,以後會再寫專門針對各個外掛的接入和原理分析的文章。

Atlas 的 GitHub 地址:github.com/alibaba/atl…

Atlas demo 的專案結構如下圖:

atlas 專案結構
atlas 專案結構

這裡面,app 是宿主,這裡配置一些啟動頁和一些初始化的操作,同時,Atlas 在 Gradle 中主要配置程式碼都在 app 下面的 gradle.build 完成,Atlas 一個非常重要的配置就是基線版本的配置:

patchConfigs {
     debug {
            createTPatch true
     }
 }

buildTypes {
   debug {
      if (apVersion) {
          baseApDependency "com.taobao.android.atlasdemo:AP-debug:${apVersion}@ap"
          patchConfig patchConfigs.debug
      }
   }
}複製程式碼

這裡的 apVersion 代表的是基線版本號,在平時的開發工程中,這個版本號往往是我們通過渠道上線的大版本,比如 1.0.0patchConfig.debug代表了 Debug 版本下的一些補丁配置,這裡我們設定 createTPatch true 代表我們使用 Atlas 的動態部署補丁修復方案,這裡也可以設定為阿里另一款熱修復方案 Andfix

官網的動態部署方案是根據版本號的,比如我現在需要釋出一個 1.0.2 的補丁包,那麼我將會在專案工程下輸入指令

./gradlew clean assembleDebug -DapVersion=1.0.0 -DversionName=1.0.2

這個指令任務結束後,將會生成一些對應的版本差異檔案,將這些差異檔案匯入到使用者手機特定的目錄中後,APP 就會通過解析這些檔案完成動態部署。這裡需要注意的是,以上指令將會產生兩個補丁檔案,1.0.0-1.0.21.0.1-1.0.2,修復補丁的時候,服務端需要根據使用者當前的版本號來下發相應的補丁。當然如果你覺得這樣有點太麻煩,想像 Tinker 一樣,只需要一個補丁檔案就可以完成功能替換,可以根據 Atlas 的原始碼進行修改,然後將外掛的 so 包上傳到伺服器,進行外掛 bundle 的完整替換,這裡就需要各位小夥伴花時間自己研究了。因為我在研究過程中請教的一個老哥是這麼做並上線的,所以這種方案肯定是有可行性的。

上面的工程目錄圖中,middlewarelibrary是一個公共模組,外掛和宿主都可以進行依賴,但這裡 Atlas 提供了一種特殊的依賴方式---providedCompile,外掛 bundle 中使用這種依賴公共模組將不會把依賴庫打入宿主 apk 和外掛 so 中,只是用來幫助你在編譯的時候使用依賴程式碼。

firstbundlesecondbundle都是外掛 bundle,他們在打包時候,會以 SO 的形式在 apk 的 libs 目錄下,這裡的 secondbundlelibrary是 secondbundle 單獨依賴的公共包,這裡它會使用 compile project 而不是 providedCompile。

remotebundle是遠端 bundle,它不會打入到 apk 中,不佔用 apk 體積,比如某些計算器小工具,當使用者點選的時候,才開始進行下載外掛,下載完畢後才能使用功能,這是 atlas 提供的遠端外掛的功能,需要在宿主 gradle 中進行特殊配置,指定外掛為 remotebundle。這裡的思路也可以作為上面的動態部署 so 替換思路。

Atlas 的優點:

  1. 穩定,成熟,功能強悍
  2. 維護團隊比較負責,技術實力值得信賴
  3. 能承擔大體量應用的外掛化改造,例如手機淘寶這樣的巨型應用
  4. 能夠實現單 bundle 的快速除錯(速度類似於 freeline 增量編譯)
  5. 具有遠端外掛和動態部署功能,可以實現熱修復和線上版本釋出功能

Atlas 的缺點:

  1. 整合較為複雜。
  2. 文件很是簡略。
  3. 版本管理較為複雜。
  4. 官方的動態部署方法,需要根據版本來下方補丁包。
  5. 外掛必須要以 library 的形式,如果需要單獨打包,需要自己配置 gradle 檔案,並且每個 bundle 都得進行 atlas 配置,沒有和 atlas 完全分離。
  6. 外掛跳轉必須通過 activity ,如果是舊專案遷移,可能有一定的改造成本。

挖過的坑:

  • demo 中的基線包會通過 gradlew publish釋出到 mavenLocal 中,如果在專案 gradle 中的maven配置有問題,將會在打補丁的時候找不到 mavenLocal 中的基線包。
    repositories {
        mavenLocal()//mavenLocal()要放在 jcenter() 上面
        jcenter()
    }複製程式碼
    當然你也可以自己配置 maven ,自定義路徑。
  • 外掛中如果依賴了外掛 libs 資料夾下的 aar 檔案,可能會出問題,需要注意幾個點:1. 關閉 Instant Run 功能,防止打包不完整。2. 查詢庫模組中是否有 gradle 遠端依賴 3. 檢視是否做了混淆,配置是否有問題,如果實在解決不了只能提 issue,扒帖子了。
  • 外掛內部無法用 ButterKnife ,可以通過 databinding 等方式改造。
  • 宿主無法反射獲取外掛中的類,外掛 A 也無法反射外掛 B 的類,當然如果你有這種需求,說明你的解耦不夠徹底,還需要進行分包改造。

Small

Small 是一款輕量級的跨平臺外掛化框架,更側重於業務的解耦,元件化開發。所有的外掛支援內建於宿主包中,並且高度透明,外掛編碼、佈局編寫方式、除錯與獨立整包開發無異,通過 URL 來進行宿主與外掛之間的通訊和傳遞引數。

Small GitHub 地址:github.com/wequick/Sma…

Small 的整合異常簡單,只需要在專案下的 build.gradle 檔案dependencies 中新增 Small 編譯外掛 gradle-small:

classpath 'net.wequick.tools.build:gradle-small:1.3.0-beta2'複製程式碼

並且在檔案末尾引用外掛:

apply plygin: 'net.wequick.small'複製程式碼

這樣,整個整合就完成了,是不是很簡單?但 Small 對於專案結構的要求比較嚴格,比如宿主最好就是剛開始新建專案時候的 app 應用,這個名字最好不要改,Small 會通過名字來尋找宿主和外掛。外掛業務模組命名以 app. 作開頭,報名最好也要以 app.或者 app* 結尾,外掛業務模組都是可以單獨打包的模組,這些模組和 app 一樣,都有個小手機在開頭,是 application 而不是 library。

Small目錄結構
Small目錄結構

公共模組以 lib.* 為開頭,這些模組中放置了一些供宿主和外掛使用的 java 程式碼和資原始檔。還有一種 stub 模組,這個模組是宿主的分身模組,內部引用的 compile 包,外掛模組將會預設引用。

完成這一系列如上圖的工程結構的建立後,通過指令完成外掛編譯。

./gradlew cleanLib
./gradlew buildLib     //編譯lib外掛
./gradlew buildBundle  //編譯業務外掛複製程式碼

外掛之間的跳轉通過bundle.json檔案進行路由設定,然後通過方法 Small.openUri() 進行外掛跳轉,這裡的 API 就不細說了,大家可以自己去官網看。

Small 是一個輕量級的外掛化框架,他沒辦法承載過大體量的應用,像手機淘寶那種,每個外掛實際上是一個 APP 的這種場景就不太適合 Small,它難以承載那樣龐大的方法數,你可以把 Small 理解成一個可以熱修復的元件化框架,它可以幫助你進行高效的分佈開發,每一個外掛都可以輕鬆的進行單獨編譯和打包,也不用特別的針對外掛和宿主配置,版本管理也非常簡單清晰。同時能支援 AppCompat 資源共享,整合外掛資源的過程中,會自動分配外掛資源 ID 端,避免資源 ID 衝突。業務外掛也可以擁有自己的 application。

同時,Small 的動態部署就是簡單的 SO 替換了,這樣雖然簡單,但可能每次上傳的補丁大小不會小,因為相當於是外掛的整包上傳。如果是大版本的更新,可能不太適合用這種動態部署的方式了。總而言之,無法完全替代發版功能。

Small 對於專案的結構要求非常嚴格,甚至命名包名都有一定的要求規範,所以如果是把舊專案遷移到 Small 中,特別是以前 eclipse 轉 Android studio 的專案,遷移的成本將會異常巨大。而且在遷移過程中要時刻關注每個分包的大小,如果過大,Small 可能會無法載入。

不過 Small 有一點特別好,就是作者---林光亮和他的團隊,非常勤快,人也很好,文件寫的也非常清晰詳細,而且都是中文的,也可以通過加入他們的討論群進行提問,GitHub 上的 issue 只要你提的符合格式,而且不是之前大家提過的問題,往往也是非常快就會回覆,是一個非常負責的團隊。

Small 中需要注意的地方和挖過的坑

  • 宿主模組中,不能依賴 Lib 外掛,宿主的主要功能就是 Small 初始化,還有一些呼叫第一個外掛的入口程式碼。不要放任何和業務有關的程式碼。
  • lib 中不要放業務程式碼。
  • 包名要非常規範,字首最好有宿主包名,字尾要有 lib. ,lib,app,app.這種格式,星號和包的名字要一致。
  • 如果修改了程式碼,cleanLib,buildLib,buildBundle 的過程中,有 ClassNotFoundException 、ClassNotDefException 這種錯誤,試著把 app/smallLibs 和 project/build-small 刪除,再重新編譯一次。
  • 我把舊專案遷移到 Small 後,點選跳轉外掛,也沒報錯,就是跳不過去,最終放棄,後來才知道,因為我的外掛和 Lib 模組有點大,超過了好幾兆了,Small 有點扛不住,所以說如果你的應用體量較大,不適合用 Small。
  • 在 lib 中使用註解或者 ButterKnife 這類註解框架會有問題。

總結

實際上不是每一個應用都需要進行外掛化改造的,你得結合自己專案的需要以及衡量一下投入產出比再做決定,一個老專案要進行外掛化,首先得進行元件化,而恰恰元件化本身是整個外掛化改造過程中最為繁瑣和人容易出問題的地方。各位朋友在決定進行外掛化改造之前,一定要慎之又慎,做好充足的預研準備。

外掛化改造的過程還有一些可預料到的問題:外掛化改造過程因為較為繁瑣複雜,所以整個過程可能要持續很久,可能要幾個月時間,但這幾個月,原來的專案又不能放著不管,總有新的需求需要你開發,所以這就涉及到外掛化改造和需求開發的並行問題。因為兩套程式碼結構不一樣,如果你把一個業務模組外掛化了,但需求又有變動,那麼你就需要在這個並行階段同時維護兩套程式碼,這極大的降低了容錯率並指數級別的降低你的開發效率。

所以我的建議就是,先撿一些比較穩定的功能,比如聚類搜尋、一些小工具如計算器,天氣啊這種。然後在需求評審階段,如果確定這個版本某些關鍵模組改動較小,可以在這個開發週期將這部分的元件化列入到技術需求之中。

一言以概之,元件化外掛化的過程不能一蹴而就,是一項需要整個團隊之間悉心協作,認真統籌才能做好的巨大工程,如果能克服困難,完成這項工程,為以後的開發和專案管理將會帶來極大的幫助。所以說,外掛化對團隊要求很高,還是那句話,大家權衡好成本和產出。


我的簡書首頁
我最近在維護的一個開源專案,歡迎 star:WeaponApp

相關文章