插件化踩坑之路——Small和Atlas方案對比

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

做爲一個公司 Android 團隊架構組成員,確定就要接觸最前沿的技術嘛,這以前我一直在研究長鏈接相關的技術,封裝了一套以 Netty 爲核心的 TCP 客戶端庫,當時架構組另外一位成員一直在研究插件化,但他手上還有一些別的事要作,插件化就擱置了,技術組需求評審的時候,我挺身而出,決心要完成項目的插件化改造。android

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

組件化和插件化

在具體介紹一個具體的插件化框架以前,咱們得先區分好 組件化,和插件化,這兩個術語的含義,這兩個術語都是最近移動端,特別是 Android 端很是火的概念,但能把這兩個概念理清楚的人並很少。github

組件化其實是一種編程思想,而插件化是一種實實在在的技術,組件化是爲了代碼的高度複用而出現的,咱們能夠經過把不一樣模塊的業務作成一個個獨立的 Library ,能夠單獨對這些 Library 進行版本管理,從而能夠供給給想使用它的一切 Apk,這樣作的好處不只能夠提升代碼的複用性,並且也能夠幫助項目進行業務解耦,提高開發效率。編程

插件化是爲了解決應用愈來愈龐大、佔用內存愈來愈高、Apk 體量過大、解決65535方法數等複雜問題而出現的,插件化會把各個解耦業務單獨的封裝到 APK 插件中,經過插件附屬到宿主 APK 中,從而完成功能的實現,這些獨立的 APK 插件甚至能夠單獨打包成應用。好比手機淘寶,它不只有淘寶這個宿主應用自己的一些功能,還有一些像聚划算、書城這類的其餘應用也須要接入進來,那麼若是沒有插件化,像聚划算的開發人員就要同時維護兩套代碼,一套是本身的聚划算項目,一套是接入到手淘的聚划算項目,出現了Bug也得同時去兩套代碼去更改,這在管理和開發效率上就會很是受限。但若是咱們把聚划算作成一個插件,那麼哪一個應用要接入聚划算,只要把這個 APK 包接入進來便可,在代碼中的就是一些 compile 和 gradle 的配置,插件內部的代碼徹底不用你操心了。json

固然,即便你是一個獨立的應用,你也能夠把插件化和組件化思想結合起來,把你的業務模塊組件化後,再分別把這些業務作成能夠獨立打包的插件,這樣,你的應用不只具有了插件化的能力和優點,還能夠經過 so 包替換或者打補丁,完成線上熱修復的功能,甚至能夠替代常規的功能發版。bash

Atlas

以前作插件化的那個同事建議我嘗試 Atlas,由於 Atlas 是目前阿里系一個比較成熟的方案,並且有一個完整的團隊在維護,值得信賴。那麼就先看看 Atlas 是怎麼回事吧。服務器

Atlas 是伴隨着手機淘寶不斷髮展而衍生出來的一個運行於 Android 系統上的插件化框架,也能夠叫動態組件化框架,主要提供瞭解耦化、組件化、動態性的支持。是目前比較成熟的方案,功能強大,但相對的,使用和集成的難度也比較大。架構

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

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

相關文章
相關標籤/搜索