Markdown版本筆記 | 我的GitHub首頁 | 我的博客 | 我的微信 | 我的郵箱 |
---|---|---|---|---|
MyAndroidBlogs | baiqiantao | baiqiantao | bqt20094 | baiqiantao@sina.com |
組件化 得到 DDComponent JIMU 模塊 插件 MD
目錄
DDComponent 組件化介紹
淺談Android組件化
什么是組件化
為什么要“徹底組件化”?
組件化過程中要注意的問題
iOS 和 Android 的組件化有何區別?
組件化后的具體成果
Android徹底組件化方案實踐
模塊化、組件化與插件化
如何實現組件化
代碼解耦:module + library
組件的單獨調試 isRunAlone
組件的數據傳輸:service
組件之間的UI跳轉:apt
組件的生命周期:Javassist
集成調試:aar
代碼隔離:apply plugin
組件化的拆分步驟和動態需求
總結
DDComponent 組件化介紹
原始的得到官網的項目(2.7K):DDComponentForAndroid
項目作者離職后維護的項目(1.4K):JIMU
個人實踐Demo
官方Demo簡化
已實現的功能
- 組件可以單獨調試
- 杜絕組件之間相互耦合,代碼完全隔離,徹底解耦
- 組件之間通過接口+實現的方式進行數據傳輸
- 使用scheme和host路由的方式進行activity之間的跳轉
- 自動生成路由跳轉路由表
- 任意組件可以充當host,集成其他組件進行集成調試
- 可以動態對已集成的組件進行加載和卸載
- 支持kotlin組件
淺談Android組件化
什么是組件化
模塊化、插件化和組件化的關系
在技術開發領域,模塊化
是指分拆代碼,即當我們的代碼特別臃腫的時候,用模塊化將代碼分而治之、解耦分層。具體到 android 領域,模塊化的具體實施方法分為插件化和組件化
。
插件化和組件化的區別
一套完整的插件化或組件化都必須能夠實現單獨調試、集成編譯、數據傳輸、UI 跳轉、生命周期和代碼邊界
這六大功能。插件化和組件化最重要而且是唯一的區別的就是:插件化可以動態
增加和修改線上的模塊,組件化的動態能力相對較弱,只能對線上已有模塊進行動態的加載和卸載
,不能新增和修改
.。
如何取舍插件化和組件化?
在插件化和組件化取舍的一個重要原則是:APP 是否有動態增加或修改
線上模塊的需求,如果這種動態性的需求很弱,就不需要考慮插件化,一般說來,電商類或廣告類產品對這個需求比較強烈,而類似“得到 APP”這類的知識服務產品,每個功能的推出都是經過精細打磨的,對這種即時的動態性要求不高,所以不需要采用插件化。
如果你的產品對動態性的要求比較高,那么在選擇插件化之前也需要從兩個方面權衡一下:
- 一是插件化不可避免的去 hook 一些系統的 api,也就不可避免地有兼容性的問題,因此每個插件化方案需要有專門的團隊去負責維護;
- 二是從一個業務邏輯復雜的項目中去拆分插件化需要的時間可能是非常巨大的,需要考慮對開發節奏的影響。
因此,對大多數產品來說,組件化都是一個不錯甚至最佳的選擇,它沒有兼容性,可以更方便地拆分,並且幾乎沒有技術障礙,可以更順利地去執行。特別是對急需拆分的產品來說,組件化是一個可退可守的方案,可以更快地執行下去,並且將來要是遷移到插件化,組件化拆分也是必經的一步。
何為“徹底”組件化
之所以稱這個方案是徹底的組件化,主要是為了更好地強調組件之間代碼邊界
的問題,組件之間的直接引用(compile)是要堅決避免的,一旦這么做了,就難免會導致直接使用其他組件的具體實現類,這樣針對接口編程的要求就成了一句空話。更嚴重的是,一旦決定對組件進行動態地加載或卸載,就會導致嚴重地崩潰。所以只有做到了代碼隔離,這個組件化方案才可以稱之為“徹底”的。
在現在的方案中可以做到代碼編寫期間組件之間是完全不可見的,因此杜絕了直接使用具體的實現類的情況,但是在編譯打包的時候,又會自動把依賴的組件打包進去。該方案是一個集合了六大功能的完整方案,覆蓋了組件化中需要考慮的全部情況。
既然是“徹底”組件化,那么代碼解耦之后,怎樣才能讓主項目間接引用
各個獨立的組件呢?
方案采用的是一個配置文件,每個組件聲明自己所需要的其他組件,配置分為 debug 和 release 兩種,可以在日常開發和正式打包之間更靈活的切換。
方案自定義了一個 gradle 插件,它去讀取每個組件的配置文件,構建出組件之間的依賴關系
。這個插件更“智能”的地方在於,它分析運行的 task 命令,判斷是否是打包命令
,是的話(例如 assembleRelease)自動根據配置引入,不是(例如正常的 sync /build)等則不引入,也就是在代碼編寫期間
組件之間是完全不可見的,因此杜絕了直接使用具體的實現類的情況。但是在編譯打包
的時候,又會自動把依賴的組件打包進去。當然這里面還會涉及到組件之間如何通過“接口 + 實現”的方式進行數據傳輸,每個組件如果進行加載等問題,這些在 方案 中都有成熟的解決方式。
方案中自定義的 gradle 插件還有一個比較好的功能就是可以自動的識別和修改組件的屬性,它可以識別出當前調試的是哪個組件,然后把這個組件修改為 application 項目,而其他組件則默默的修改成 library 項目。因此不論是要單獨編譯一個組件還是要把這個組件集成到其他組件中調試,都是不需要做任何的手動修改,使用起來相當的方便。
為什么要“徹底組件化”?
在剛開始對“得到 APP”Android 端的代碼進行組件化拆分的時候,“得到 APP”已經是一個千萬用戶級的產品。經過那么長時間的積累,幾十萬行代碼堆積在一起,編譯一次大約需要 10 分鍾的時間,這嚴重影響了開發效率。
由於業務復雜,代碼交織在一起,可謂牽一發而動全身,因此每個人在寫新需求的時候都有嚴重的代碼包袱,瞻前顧后,花費在熟悉之前的代碼的時間甚至大於新需求的開發時間。並且每個改動都需要測試人員進行大范圍的回歸,所以整個開發團隊的效率都受到了影響。在這種情況下,實施組件化是迫在眉睫了。
由於國內對插件化
的研究是比較火爆的,而對組件化的研究熱情就相對淡了很多。在設計“得到 APP”組件化方案的時候,幾乎查閱了全部的組件化文章,都沒有找到一個完整的支持上面說的六大功能的方案,所以不得不從頭開始設計,“徹底組件化”的方案可跳轉閱讀 Android 徹底組件化方案實踐 和 Android 徹底組件化 demo
組件化過程中要注意的問題
讓方案從紙上運用到實際,是一個比較困難的過程,這期間要注意兩個方面的問題:一是技術細節上的不斷完善,二是團隊的共識建設問題。
技術上的問題主要是如何讓方案更靈活,需求總是比預期要復雜,遇到特殊的需求,之前的設計可能就沒法實現,或者必須得突破之前確定的拆分原則。這時候就需要回過頭再審視整個方案,看看能否在某些方面做一些調整。方案中數據傳輸和 UI 跳轉是分開的兩個功能,這是在實際拆分中才做出的選擇,因為數據傳輸更為頻繁,且交互形式更多樣,使用一個標准化的路由協議難以滿足,因此把數據傳輸改成了接口 + 實現的形式,針對接口編程就可以更加靈活地處理這些情況。
除了技術上的,更重要的是團隊的共識問題。要執行一個組件化拆分這樣的大工程,需要團隊的每個人達成共識,無論是在方案還是在技術的實現細節上,大家都能有一個統一的方向。
為此,在拆分之前多做幾次組內的分享討論,從方案的制定到每一次的實施,都讓團隊的大部分成員參與進來。正所謂磨刀不誤砍柴工,在這種前提下,團隊的共識建設會對后期工作效率的提高產生很大的價值。確立了共識,還需要確立統一的規則,雖說條條大路通羅馬,但是在一個產品里,還是需要選擇統一的道路,否則即便做了拆分,效果也會大打折扣。
iOS 和 Android 的組件化有何區別?
無論是 Android 還是 iOS,要解決的問題都是一樣的,因此在組件化方案上要實現的功能(即上面所說的上面六種功能)也都是一樣的,所以兩者的組件化大體上來說是基本相同的。
有一個微小的區別在於技術實現方式的不同,由於兩個平台用到的開發技術是不同的,Android 的組件化可能需要考慮向插件化的遷移,后期一旦有動態變動功能的強需求,可以快速地切換。而目前蘋果官方是不允許這種動態性的,所以這方面的考慮就會少一點。但是 iOS 同樣可以做到動態地加載和卸載組件的,因此在諸如生命周期、代碼邊界等問題上也需要格外注意,只是目前一些 iOS 組件化方案在這方面可能考慮的相對少一點。
組件化后的具體成果
組件化后的代碼結構非常清晰,分層結構以及之間的交互很明了,團隊中的任何一個人都可以很輕松的繪制出代碼結構圖,這個在之前是沒法做到的,並且每個組件的編譯時間從 10 分鍾降到了幾十秒,工作效率有了很大地提升,最關鍵的還是解耦之后,每次開發需求的時候,面對的代碼越來越少,不用背負那么重的代碼包袱,可以說達到了“代碼越寫越少”的理想情況。
其實組件化對外輸出也是很可觀的,現在一個版本開發完成后,我們可以跟測試說這期就回歸“每天聽本書”組件,其他的不需要回歸。這種自信在之前是絕對沒有的,測試的壓力也可以小很多。更重要的是我們的組件可以復用,“得到 APP”會上線新的產品,他們可以直接使用已有的組件,省去了很多重復造輪子的工作,這點對整個公司效率的提升也是很有幫助的。
Android徹底組件化方案實踐
模塊化、組件化與插件化
項目發展到一定程度,隨着人員的增多,代碼越來越臃腫,這時候就必須進行模塊化的拆分。在我看來,模塊化是一種指導理念,其核心思想就是分而治之、降低耦合。而在Android工程中如何實施,目前有兩種途徑,也是兩大流派,一個是組件化,一個是插件化。
提起組件化和插件化的區別,有一個很形象的圖:
上面的圖看上去似乎比較清晰,其實容易導致一些誤解,有下面幾個小問題,圖中說的就不太清楚:
- 組件化是一個整體嗎?去了頭和胳膊還能存在嗎?左圖中,似乎組件化是一個有機的整體,需要所有器官都健在才可以存在。而實際上組件化的目標之一就是降低整體(app)與器官(組件)的依賴關系,缺少任何一個器官app都是可以存在並正常運行的。
- 頭和胳膊可以單獨存在嗎?左圖也沒有說明白,其實答案應該是肯定的。每個器官(組件)可以在補足一些基本功能之后都是可以獨立存活的。這個是組件化的第二個目標:組件可以單獨運行。
- 組件化和插件化可以都用右圖來表示嗎?如果上面兩個問題的答案都是YES的話,這個問題的答案自然也是YES。每個組件都可以看成一個單獨的整體,可以按需的和其他組件(包括主項目)整合在一起,從而完成的形成一個app
- 右圖中的小機器人可以動態的添加和修改嗎?如果組件化和插件化都用右圖來表示,那么這個問題的答案就不一樣了。對於組件化來講,這個問題的答案是部分可以,也就是在編譯期可以動態的添加和修改,但是在運行時就沒法這么做了。而對於插件化,這個問題的答案很干脆,那就是完全可以,不論是在編譯期還是運行時!
本文主要集中講的是組件化的實現思路,對於插件化的技術細節不做討論,我們只是從上面的問答中總結出一個結論:組件化和插件化的最大區別(應該也是唯一區別)就是組件化在運行時不具備動態添加和修改組件的功能
,但是插件化是可以的。
暫且拋棄對插件化“道德”上的批判,我認為對於一個Android開發者來講,插件化的確是一個福音,這將使我們具備極大的靈活性。但是苦於目前還沒有一個完全合適、完美兼容的插件化方案,特別是對於已經有幾十萬代碼量的一個成熟產品來講,套用任何一個插件化方案都是很危險的工作。所以我們決定先從組件化做起,本着做一個最徹底的組件化方案的思路去進行代碼的重構,下面是最近的思考結果,歡迎大家提出建議和意見。
如何實現組件化
要實現組件化,不論采用什么樣的技術路徑,需要考慮的問題主要包括下面幾個:
- 代碼解耦。如何將一個龐大的工程拆分成有機的整體?
- 組件單獨運行。上面也講到了,每個組件都是一個完整的整體,如何讓其單獨運行和調試呢?
- 數據傳遞。因為每個組件都會給其他組件提供的服務,那么主項目(Host)與組件、組件與組件之間如何傳遞數據?
- UI跳轉。UI跳轉可以認為是一種特殊的數據傳遞,在實現思路上有啥不同?
- 組件的生命周期。我們的目標是可以做到對組件可以按需、動態的使用,因此就會涉及到組件加載、卸載和降維的生命周期。
- 集成調試。在開發階段如何做到按需的編譯組件?一次調試中可能只有一兩個組件參與集成,這樣編譯的時間就會大大降低,提高開發效率。
- 代碼隔離。組件之間的交互如果還是直接引用的話,那么組件之間根本沒有做到解耦,如何從根本上避免組件之間的直接引用呢?也就是如何從根本上杜絕耦合的產生呢?只有做到這一點才是徹底的組件化。
代碼解耦:module + library
把龐大的代碼進行拆分,AndroidStudio能夠提供很好的支持,使用IDE中的multiple module
這個功能,我們很容易把代碼進行初步的拆分。在這里我們對兩種module進行區分:
- 一種是
基礎庫
library,這些代碼被其他組件直接引用
(是次方案中非常核心的設計理念),比如網絡庫module可以認為是一個library。 - 另一種我們稱之為Component,這種module是一個
完整的功能模塊
,比如讀書或者分享module就是一個Component。
為了方便,我們統一把library稱之為依賴庫,而把Component稱之為組件,我們所講的組件化也主要是針對Component這種類型。而負責拼裝這些組件以形成一個完成app的module,一般我們稱之為主項目、主module或者Host
,方便起見我們也統一稱為主項目。
經過簡單的思考,我們可能就可以把代碼拆分成下面的結構:
這種拆分都是比較容易做到的,從圖上看,讀書、分享等都已經拆分組件,並共同依賴於公共的依賴庫(簡單起見只畫了一個),然后這些組件都被主項目所引用。讀書、分享等組件之間沒有直接的聯系,我們可以認為已經做到了組件之間的解耦。
但是這個圖有幾個問題需要指出:
- 從上面的圖中,我們似乎可以認為組件只有集成到主項目才可以使用,而實際上我們的希望是每個組件是個整體,可以獨立運行和調試,那么如何做到單獨的調試呢?
- 主項目可以直接引用組件嗎?也就是說我們可以直接使用compile project(:reader)這種方式來引用組件嗎?如果是這樣的話,那么主項目和組件之間的耦合就沒有消除啊。我們上面講,組件是可以動態管理的,如果我們刪掉reader(讀書)這個組件,那么主項目就不能編譯了啊,談何動態管理呢?所以
主項目對組件的直接引用是不可以的
,但是我們的讀書組件最終是要打到apk里面,不僅代碼要和並到claases.dex里面,資源也要經過meage操作合並到apk的資源里面,怎么避免這個矛盾呢? - 組件與組件之間真的沒有相互引用或者交互嗎?讀書組件也會調用分享模塊啊,而這在圖中根本沒有體現出來啊,那么組件與組件之間怎么交互呢?
這些問題我們后面一個個來解決,首先我們先看代碼解耦要做到什么效果,像上面的直接引用並使用其中的類肯定是不行的了。所以我們認為代碼解耦的首要目標就是組件之間的完全隔離
,我們不僅不能直接使用其他組件中的類,最好能根本不了解其中的實現細節。只有這種程度的解耦才是我們需要的。
組件的單獨調試 isRunAlone
其實單獨調試比較簡單,只需要把 apply plugin: 'com.android.library'
切換成 apply plugin: 'com.android.application'
就可以,但是我們還需要修改一下AndroidManifest
文件,因為一個單獨調試需要有一個入口的actiivity。
我們可以設置一個變量isRunAlone
,標記當前是否需要單獨調試,根據isRunAlone的取值,使用不同的gradle插件和AndroidManifest文件,甚至可以添加Application等Java文件,以便可以做一下初始化的操作。
為了避免不同組件之間資源名重復,在每個組件的build.gradle中增加resourcePrefix "xxx_"
,從而固定每個組件的資源前綴。下面是讀書組件的build.gradle
的示例:
if (isRunAlone.toBoolean()) {
apply plugin: 'com.android.application'
} else {
apply plugin: 'com.android.library'
}
//... ..
resourcePrefix "readerbook_"
sourceSets {
main {
if (isRunAlone.toBoolean()) {
manifest.srcFile 'src/main/runalone/AndroidManifest.xml'
java.srcDirs = ['src/main/java', 'src/main/runalone/java']
res.srcDirs = ['src/main/res', 'src/main/runalone/res']
} else {
manifest.srcFile 'src/main/AndroidManifest.xml'
}
}
}
通過這些額外的代碼,我們給組件搭建了一個測試Host
,從而讓組件的代碼運行在其中,所以我們可以再優化一下我們上面的框架圖。
組件的數據傳輸:service
上面我們講到,主項目和組件、組件與組件之間不能直接使用類的相互引用來進行數據交互。那么如何做到這個隔離呢?
在這里我們采用接口+實現
的結構。每個組件聲明自己提供的服務Service(抽象類或者接口),組件負責將這些Service實現並注冊到一個統一的路由Router中去。如果要使用某個組件的功能,只需要向Router請求這個Service的實現,具體的實現細節我們全然不關心(也沒有暴露),只要能返回我們需要的結果就可以了。這與Binder的C/S架構很相像。
因為我們組件之間的數據傳遞都是基於接口編程的,接口和實現是完全分離的,所以組件之間就可以做到解耦,我們可以對組件進行替換、刪除等動態管理。
這里面有幾個小問題需要明確:
- 組件怎么暴露自己提供的服務呢?在項目中我們簡單起見,專門建立了一個
componentservice
(另一個核心設計)的依賴庫,里面定義了每個組件向外提供的service和一些公共model。將所有組件的service整合在一起,是為了在拆分初期操作更為簡單,后面需要改為自動化的方式來生成。這個依賴庫需要嚴格遵循開閉原則,以避免出現版本兼容等問題。 - service的具體實現是由所屬組件注冊到Router中的,那么是在什么時間注冊的呢?這個就涉及到組件的加載等生命周期,我們在后面專門介紹。
- 一個很容易犯的小錯誤就是通過持久化的方式來傳遞數據,例如file、sharedpreference等方式,這個是需要避免的。
下面就是加上數據傳輸功能之后的架構圖:
組件之間的UI跳轉:apt
可以說UI的跳轉也是組件提供的一種特殊的服務,可以歸屬到上面的數據傳遞中去。不過一般UI的跳轉我們會單獨處理,一般通過短鏈
的方式來跳轉到具體的Activity。每個組件可以注冊自己所能處理的短鏈的scheme和host
,並定義傳輸數據的格式。然后注冊到統一的UIRouter
中,UIRouter通過scheme和host的匹配關系負責分發路由。
UI跳轉部分的具體實現是通過在每個Activity上添加注解,然后通過apt(Annotation Processing Tool, 注解處理器)
形成具體的邏輯代碼。這個也是目前Android中UI路由的主流實現方式。
具體的功能介紹和使用規范,請大家參見文章:android徹底組件化—UI跳轉升級改造
組件的生命周期:Javassist
由於我們要動態的管理組件,所以給每個組件添加幾個生命周期狀態:加載、卸載和降維
。為此我們給每個組件增加一個ApplicationLike
類,里面定義了onCreate和onStop
兩個生命周期函數。
- 加載 onCreate:上面講了,每個組件負責將自己的服務實現注冊到Router中,其具體的實現代碼就寫在onCreate方法中。那么主項目調用這個onCreate方法就稱之為組件的加載,因為一旦onCreate方法執行完,組件就把自己的服務注冊到Router里面去了,其他組件就可以直接使用這個服務了。
- 卸載 onStop:卸載與加載基本一致,所不同的就是調用ApplicationLike的onStop方法,在這個方法中每個組件將自己的服務實現從Router中取消注冊。不過這種使用場景可能比較少,一般適用於一些只用一次的組件。
- 降維:降維使用的場景更為少見,比如一個組件出現了問題,我們想把這個組件從本地實現改為一個wap頁。降維一般需要后台配置才生效,可以在onCreate對線上配置進行檢查,如果需要降維,則把所有的UI跳轉到配置的wap頁上面去。
一個小的細節是,主項目負責加載組件,由於主項目和組件之間是隔離的,那么主項目如何調用組件ApplicationLike的生命周期方法呢?
目前我們采用的是基於編譯期字節碼插入
的方式,掃描所有的ApplicationLike
類(其有一個共同的父類),然后通過Javassist
(Java assist ,一個開源的分析、編輯和創建Java字節碼的類庫)在主項目的onCreate中插入調用 ApplicationLike.onCreate 的代碼
。
我們再優化一下組件化的架構圖:
集成調試:aar
每個組件單獨調試通過並不意味着集成在一起沒有問題,因此在開發后期我們需要把幾個組件機集成到一個app里面去驗證。由於我們上面的機制保證了組件之間的隔離,所以我們可以任意選擇幾個組件參與集成。這種按需索取的加載機制可以保證在集成調試中有很大的靈活性,並且可以極大的加快編譯速度。
我們的做法是這樣的,每個組件開發完成之后,發布一個relaese的aar
到一個公共倉庫,一般是本地的maven庫。然后主項目通過參數配置要集成的組件就可以了。
所以我們再稍微改動一下組件與主項目之間的連接線,形成的最終組件化架構圖如下:
代碼隔離:apply plugin
此時再回顧我們在剛開始拆分組件化時提出的三個問題,應該說都找到了解決方式,但是還有一個隱患沒有解決,那就是我們可以使用compile project(xxx:reader.aar)
來引入組件嗎?雖然我們在數據傳輸章節使用了接口+實現
的架構,組件之間必須針對接口編程,但是一旦我們引入了reader.aar,那我們就完全可以直接使用到其中的實現類啊,這樣我們針對接口編程的規范就成了一紙空文。千里之堤毀於蟻穴,只要有代碼(不論是有意還是無意)是這么做了,我們前面的工作就白費了。
我們希望只在assembleDebug或者assembleRelease
的時候把aar引入進來(也就是在執行打包
命令的時候,首先通過compile引入組件),而在開發階段,所有組件都是看不到的(因為開發階段我們並沒有通過compile引入組件),這樣就從根本上杜絕了引用實現類的問題。
為了實現這個目的,我們是這么做的:我們把這個問題交給gradle
來解決,我們創建一個gradle插件,然后每個組件都apply這個插件,插件的配置代碼也比較簡單,就是在執行打包命令的時候,根據配置添加各種組件依賴,並且自動化生成組件加載代碼:
apply plugin: 'com.dd.comgradle'
//根據配置添加各種組件依賴,並且自動化生成組件加載代碼
if (project.android instanceof AppExtension) {
AssembleTask assembleTask = getTaskInfo(project.gradle.startParameter.taskNames)
if (assembleTask.isAssemble && (assembleTask.modules.contains("all") || assembleTask.modules.contains(module))) {
project.dependencies.add("compile","xxx:reader-release@aar") //添加組件依賴
//字節碼插入的部分也在這里實現
}
}
//獲取正在執行的Task的信息
private AssembleTask getTaskInfo(List<String> taskNames) {
AssembleTask assembleTask = new AssembleTask();
for (String task : taskNames) {
if (task.toUpperCase().contains("ASSEMBLE")) {
assembleTask.isAssemble = true;
String[] strs = task.split(":")
assembleTask.modules.add(strs.length > 1 ? strs[strs.length - 2] : "all");
}
}
return assembleTask
}
組件化的拆分步驟和動態需求
拆分原則(只是建議)
組件化的拆分是個龐大的工程,特別是從幾十萬行代碼的大工程拆分出去,所要考慮的事情千頭萬緒。為此我覺得可以分成三步:
- 從產品需求到開發階段再到運營階段都有清晰邊界的功能開始拆分,比如讀書模塊、直播模塊等,這些開始分批先拆分出去
- 在拆分中,造成組件依賴主項目的依賴的模塊繼續拆出去,比如賬戶體系等
- 最終主項目就是一個Host,包含很小的功能模塊(比如啟動圖)以及組件之間的拼接邏輯
組件化的動態需求(然並不支持)
最開始我們講到,理想的代碼組織形式是插件化的方式,屆時就具備了完備的運行時動態化。在向插件化遷徙的過程中,我們可以通過下面的集中方式來實現編譯速度的提升和動態更新。
- 在快速編譯上:采用組件級別的增量編譯。在抽離組件之前可以使用代碼級別的增量編譯工具,如 freeline(但databinding支持較差)、fastdex等。
- 動態更新方面:暫時不支持新增組件等大的功能改進。可以臨時采用方法級別的熱修復,或者功能級別的 Tinker 等工具(Tinker的接入成本較高)。
得到組件化改造大流程
總結
本文是筆者在設計"得到app"的組件化中總結一些想法(目前已經離職加入頭條),在設計之初參考了目前已有的組件化和插件化方案,站在巨人的肩膀上又加了一點自己的想法,主要是組件化生命周期以及完全的代碼隔離方面。特別是最后的代碼隔離,不僅要有規范上的約束(針對接口編程),更要有機制保證開發者不犯錯,我覺得只有做到這一點才能認為是一個徹底的組件化方案。
2018-4-22