目錄介紹
-
1.爲什麼要組件化
- 1.1 爲什麼要組件化
- 1.2 現階段遇到的問題
-
2.組件化的概念
- 2.1 什麼是組件化
- 2.2 區分模塊化與組件化
- 2.3 組件化優勢好處
- 2.4 區分組件化和插件化
- 2.5 application和library
-
3.創建組件化框架
- 3.1 傳統APP架構圖
- 3.2 組件化需要考慮問題
- 3.3 架構設計圖
- 3.4 組件通信是通過路由轉發
- 3.5 解決考慮問題
- 3.6 業務組件的生命週期
- 3.7 Fragment通信難點
-
4.實際開發案例
- 4.1 組件化實踐的開源項目
- 4.1 如何創建模塊
- 4.2 如何建立依賴
- 4.3 如何統一配置文件
- 4.4 組件化的基礎庫
- 4.5 組件模式和集成模式如何切換
- 4.6 組件化解決重複依賴
- 4.7 組件化注意要點
- 4.8 組件化時資源名衝突
- 4.9 組件化開發遇到問題
-
5.組件間通信
- 5.1 選擇那個開源路由庫
- 5.2 阿里Arouter基礎原理
- 5.3 使用Arouter注意事項
-
6.關於其他
- 6.1 參考博客鏈接
- 6.2 我的博客介紹
- 6.3 開源項目地址
1.爲什麼要組件化
1.1 爲什麼要組件化
-
APP迭代維護成本增高
- 投資界,新芽,項目工廠等APP自身在飛速發展,版本不斷迭代,新功能不斷增加,業務模塊數量不斷增加,業務上的處理邏輯越變越複雜,同時每個模塊代碼也變得越來越多,這就引發一個問題,所維護的代碼成本越來越高,稍微一改動可能就牽一髮而動全身,改個小的功能點就需要回歸整個APP測試,這就對開發和維護帶來很大的挑戰。
-
多人組合需要組件化
- APP 架構方式是單一工程模式,業務規模擴大,隨之帶來的是團隊規模擴大,那就涉及到多人協作問題,每個移動端軟件開發人員勢必要熟悉如此之多代碼,如果不按照一定的模塊組件機制去劃分,將很難進行多人協作開發,隨着單一項目變大,而且Andorid項目在編譯代碼方面就會變得非常卡頓,在單一工程代碼耦合嚴重,每修改一處代碼後都需要重新編譯打包測試,導致非常耗時。
1.2 現階段遇到的問題
-
結合投資界,新芽客戶端分析
- 代碼量膨脹,不利於維護,不利於新功能的開發。項目工程構建速度慢,在一些電腦上寫兩句代碼,重新編譯整個項目,測試的話編譯速度起碼 10-20 分鐘,有的甚至更長。
- 不同模塊之間代碼耦合嚴重,有時候修改一處代碼而牽動許多模塊。每個模塊之間都有引用第三方庫,但有些第三方庫版本不一致,導致打包APP時候代碼冗餘,容易引起版本衝突。
- 現有項目基於以前其他人項目基礎上開發,經手的人次過多,存在着不同的代碼風格,項目中代碼規範亂,類似的功能寫法卻不一樣,導致不統一。
2.組件化的概念
2.1 什麼是組件化
-
什麼是組件化呢?
- 組件(Component)是對數據和方法的簡單封裝,功能單一,高內聚,並且是業務能劃分的最小粒度。
- 組件化是基於組件可重用的目的上,將一個大的軟件系統按照分離關注點的形式,拆分成多個獨立的組件,使得整個軟件系統也做到電路板一樣,是單個或多個組件元件組裝起來,哪個組件壞了,整個系統可繼續運行,而不出現崩潰或不正常現象,做到更少的耦合和更高的內聚。
2.2 區分模塊化與組件化
-
模塊化
- 模塊化就是將一個程序按照其功能做拆分,分成相互獨立的模塊,以便於每個模塊只包含與其功能相關的內容,模塊我們相對熟悉,比如登錄功能可以是一個模塊,搜索功能可以是一個模塊等等。
-
組件化
- 組件化就是更關注可複用性,更注重關注點分離,如果從集合角度來看的話,可以說往往一個模塊包含了一個或多個組件,或者說模塊是一個容器,由組件組裝而成。簡單來說,組件化相比模塊化粒度更小,兩者的本質思想都是一致的,都是把大往小的方向拆分,都是爲了複用和解耦,只不過模塊化更加側重於業務功能的劃分,偏向於複用,組件化更加側重於單一功能的內聚,偏向於解耦。
2.3 組件化優勢好處
-
簡單來說就是提高工作效率,解放生產力,好處如下:
-
1.提高編譯速度,從而提高並行開發效率。
- 問題:那麼如何提高編譯速度的呢?組件化框架可以使模塊單獨編譯調試,可以有效地減少編譯的時間。
-
2.穩定的公共模塊採用依賴庫方式
- 提供給各個業務線使用,減少重複開發和維護工作量。代碼簡潔,冗餘量少,維護方便,易擴展新功能。
-
3.每個組件有自己獨立的版本,可以獨立編譯、測試、打包和部署。
- 針對開發程序員多的公司,組件化很有必要,每個人負責自己的模塊,可以較少提交代碼衝突。
- 爲新業務隨時集成提供了基礎,所有業務可上可下,靈活多變。
- 各業務線研發可以互不干擾、提升協作效率,並控制產品質量。
- 4.避免模塊之間的交叉依賴,做到低耦合、高內聚。
-
5.引用的第三方庫代碼統一管理,避免版本統一,減少引入冗餘庫。
- 這個可以創建一個公共的gradle管理的文件,比如一個項目有十幾個組件,想要改下某個庫或者版本號,總不至於一個個修改吧。這個時候提取公共十分有必要
- 6.定製項目可按需加載,組件之間可以靈活組建,快速生成不同類型的定製產品。
-
2.4 區分組件化和插件化
-
組件化和插件化的區別
- 組件化不是插件化,插件化是在【運行時】,而組件化是在【編譯時】。換句話說,插件化是基於多APK的,而組件化本質上還是隻有一個 APK。
- 組件化和插件化的最大區別(應該也是唯一區別)就是組件化在運行時不具備動態添加和修改組件的功能,但是插件化是可以的。
-
組件化的目標
- 組件化的目標之一就是降低整體工程(app)與組件的依賴關係,缺少任何一個組件都是可以存在並正常運行的。app主工程具有和組件進行綁定和解綁的功能。
2.5 application和library
-
在studio中,對兩種module進行區分,如下所示
- 一種是基礎庫library,比如常見第三方庫都是lib,這些代碼被其他組件直接引用。
- 另一種是application,也稱之爲Component,這種module是一個完整的功能模塊。比如分享module就是一個Component。
- 爲了方便,統一把library稱之爲依賴庫,而把Component稱之爲組件,下面所講的組件化也主要是針對Component這種類型。
-
在項目的build.gradle文件中
//控制組件模式和集成模式 if (rootProject.ext.isDouBanApplication) { //是Component,可以獨立運行 apply plugin: 'com.android.application' } else { //是lib,被依賴 apply plugin: 'com.android.library' }
3.創建組件化框架
3.1 傳統APP架構圖
-
傳統APP架構圖
- 如圖所示,從網上摘來的……
-
存在的問題
- 普遍使用的 Android APP 技術架構,往往是在一個界面中存在大量的業務邏輯,而業務邏輯中充斥着各種網絡請求、數據操作等行爲,整個項目中也沒有模塊的概念,只有簡單的以業務邏輯劃分的文件夾,並且業務之間也是直接相互調用、高度耦合在一起的。單一工程模型下的業務關係,總的來說就是:你中有我,我中有你,相互依賴,無法分離。如下圖:
3.2 組件化需要考慮問題
- 考慮的問題
-
分而治之,並行開發,一切皆組件。要實現組件化,無論採用什麼樣的技術方式,需要考慮以下七個方面問題:
-
代碼解耦。
- 如何將一個龐大的工程分成有機的整體?這個需要一步步來了!
- 對已存在的項目進行模塊拆分,模塊分爲兩種類型,一種是功能組件模塊,封裝一些公共的方法服務等,作爲依賴庫對外提供;另一種是業務組件模塊,專門處理業務邏輯等功能,這些業務組件模塊最終負責組裝APP。
-
組件單獨運行。
- 因爲每個組件都是高度內聚的,是一個完整的整體,如何讓其單獨運行和調試?
- 通過 Gradle腳本配置方式,進行不同環境切換,我自己操作是添加一個boolean值的開關。比如只需要把 Apply plugin: 'com.android.library' 切換成Apply plugin: 'com.android.application' 就可以獨立運行呢!
- 需要注意:當切換到application獨立運行時,需要在AndroidManifest清單文件上進行設置,因爲一個單獨調試需要有一個入口的Activity。
-
組件間通信。
- 由於每個組件具體實現細節都互相不瞭解,但每個組件都需要給其他調用方提供服務,那麼主項目與組件、組件與組件之間如何通信就變成關鍵?
- 這個我是直接用阿里開源的路由框架,當然你可以根據需要選擇其他大廠的開源路由庫。引用阿里的ARouter框架,通過註解方式進行頁面跳轉。
-
組件生命週期。
- 這裏的生命週期指的是組件在應用中存在的時間,組件是否可以做到按需、動態使用、因此就會涉及到組件加載、卸載等管理問題。
-
集成調試。
- 在開發階段如何做到按需編譯組件?一次調試中可能有一兩個組件參與集成,這樣編譯時間就會大大降低,提高開發效率。
-
代碼隔離。
- 組件之間的交互如果還是直接引用的話,那麼組件之間根本沒有做到解耦,如何從根本上避免組件之間的直接引用?目前做法是主項目和業務組件都會依賴公共基礎組件庫,業務組件通過路由服務依賴庫按需進行查找,用於不同組件之間的通信。
- 告別結構臃腫,讓各個業務變得相對獨立,業務組件在組件模式下可以獨立開發,而在集成模式下又可以變爲AAR包集成到“APP殼工程”中,組成一個完整功能的 APP。
-
3.3 架構設計圖
-
組件化架構圖
- 業務組件之間是獨立的,互相沒有關聯,這些業務組件在集成模式下是一個個 Library,被 APP 殼工程所依賴,組成一個具有完整業務功能的 APP 應用,但是在組件開發模式下,業務組件又變成了一個個Application,它們可以獨立開發和調試,由於在組件開發模式下,業務組件們的代碼量相比於完整的項目差了很遠,因此在運行時可以顯著減少編譯時間。
3.4 組件通信是通過路由轉發
-
傳統以前工程下模塊
- 記得剛開始進入Android開發工作時,只有一個app主工程,後期幾乎所有的需求都寫在這個app主工程裏面。只有簡單的以業務邏輯劃分的文件夾,並且業務之間也是直接相互調用、高度耦合在一起的。
- 導致後期改項目爲組件化的時候十分痛苦,不同模塊之間的業務邏輯實在關聯太多,但還是沒辦法,於是目錄4步驟一步步實踐。終極目標是,告別結構臃腫,讓各個業務變得相對獨立,業務組件在組件模式下可以獨立開發。
-
組件化模式下如何通信
- 這是組件化工程模型下的業務關係,業務之間將不再直接引用和依賴,而是通過“路由”這樣一箇中轉站間接產生聯繫。在這個開源項目中,我使用的阿里開源的路由框架。關於Arouter基礎使用和代碼分析,可以看我這篇博客:Arouter使用與代碼解析
3.6 業務組件的生命週期
-
按照理想狀態的來看待的話
- 各個業務組件之間沒有任何依賴關係,這時我們可以把每個獨立的業務組件看成一個可運行的app,所以業務組件的生命週期和應與獨立的app保持一致。
3.7 Fragment通信難點
- 在網上看到很多博客說,如何拆分組件,按模塊拆分,或者按照功能拆分。但很少有提到fragment在拆分組件時的疑問,這個讓我很奇怪。
- 先來說一個業務需求,比如一個購物商城app,有4個模塊,做法一般是一個activity+4個fragment,這個大家都很熟悉,這四個模塊分別是:首頁,發現,購物車,我的。然後這幾個頁面是用fragment寫的,共用一個宿主activity,那麼在做組件化的時候,我想把它按照業務拆分成首頁,發現,購物車和我的四個獨立的業務模塊。
-
遇到疑問:
- 如果是拆分成四個獨立的業務模塊,那麼對應的fragment肯定要放到對應的組件中,那麼這樣操作,當主工程與該業務組件解綁的情況下,如何拿到fragment和傳遞參數進行通信。
- Fragment 中 開啓Activity帶requestCode,開啓的Activity關閉後,不會回調Fragment中的onActivityResult。只會調用Fragment 所在Activity的onActivityResult。
- 多fragment單activity攔截器不管用,難道只能用於攔截activity的跳轉?那如果是要實現登錄攔截的話,那不是只能在PathReplaceService中進行了?
-
網絡解決辦法
- 第一個疑問:由於我使用阿里路由,所以我看到zhi1ong大佬說:用Router跳轉到這個Activity,然後帶一個參數進去,比方說tab=2,然後自己在onCreate裏面自行切換。但後來嘗試,還是想問問廣大程序員有沒有更好的辦法。
- 第二個疑問:還是zhi1ong大佬說,通過廣播,或者在Activity中轉發這個事件,比方說讓Fragment統一依賴一個接口,然後在Activity中轉發。
4.實際開發案例
4.1 組件化實踐的開源項目
-
關於組件化開發一點感想
- 關於網上有許多關於組件化的博客,講解了什麼是組件化,爲何要組件化,以及組件化的好處。大多數文章提供了組件化的思路,給我着手組件化開發提供了大量的便利。感謝前輩大神的分享!雖然有一些收穫,但是很少有文章能夠給出一個整體且有效的方案,或者一個具體的Demo。
- 但是畢竟看博客也是爲了實踐做準備,當着手將之前的開源案例改版成組件化案例時,出現了大量的問題,也解決了一些問題。主要是學些了組件化開發流程。
- 大多數公司慢慢着手組件化開發,在小公司,有的人由於之前沒有做過組件化開發,嘗試組件化也是挺好的;在大公司,有的人一去只是負責某個模塊,可能剛開始組件化已經有人弄好了,那學習實踐組件化那更快一些。業餘實踐,改版之前開源項目,寫了這篇博客,耗費我不少時間,要是對你有些幫助,那我就很開心呢。由於我也是個小人物,所以寫的不好,勿噴,歡迎提出建議!
-
關於組件化開源項目
- 項目整體架構模式採用:組件化+MVP+Rx+Retrofit+design+Dagger2+VLayout+X5
- 包含的模塊:wanAndroid【kotlin】+乾貨集中營+知乎日報+番茄Todo+精選新聞+豆瓣音樂電影小說+小說讀書+簡易記事本+搞笑視頻+經典遊戲+其他更多等等
- 此項目屬於業餘時間練手的項目,接口數據來源均來自網絡,如果存在侵權情況,請第一時間告知。本項目僅做學習交流使用,API數據內容所有權歸原作公司所有,請勿用於其他用途。
- 關於開源組件化的項目地址:https://github.com/yangchong2...
4.1 如何創建模塊
- 根據3.3 架構設計圖可以知道
-
主工程:
- 除了一些全局配置和主 Activity 之外,不包含任何業務代碼。有的也叫做空殼app,主要是依賴業務組件進行運行。
-
業務組件:
- 最上層的業務,每個組件表示一條完整的業務線,彼此之間互相獨立。原則上來說:各個業務組件之間不能有直接依賴!所有的業務組件均需要可以做到獨立運行的效果。對於測試的時候,需要依賴多個業務組件的功能進行集成測試的時候。可以使用app殼進行多組件依賴管理運行。
- 該案例中分爲:幹活集中營,玩Android,知乎日報,微信新聞,頭條新聞,搞笑視頻,百度音樂,我的記事本,豆瓣音樂讀書電影,遊戲組件等等。
-
功能組件:
- 該案例中分爲,分享組件,評論反饋組件,支付組件,畫廊組件等等。同時注意,可能會涉及多個業務組件對某個功能組件進行依賴!
-
基礎組件:
- 支撐上層業務組件運行的基礎業務服務。此部分組件爲上層業務組件提供基本的功能支持。
- 該案例中:在基礎組件庫中主要有,網絡請求,圖片加載,通信機制,工具類,分享功能,支付功能等等。當然,我把一些公共第三方庫放到了這個基礎組件中!
4.2 如何建立依賴
-
關於工程中組件依賴結構圖如下所示
-
業務模塊下完整配置代碼
//控制組件模式和集成模式 if (rootProject.ext.isGankApplication) { apply plugin: 'com.android.application' } else { apply plugin: 'com.android.library' } android { compileSdkVersion rootProject.ext.android["compileSdkVersion"] buildToolsVersion rootProject.ext.android["buildToolsVersion"] defaultConfig { minSdkVersion rootProject.ext.android["minSdkVersion"] targetSdkVersion rootProject.ext.android["targetSdkVersion"] versionCode rootProject.ext.android["versionCode"] versionName rootProject.ext.android["versionName"] if (rootProject.ext.isGankApplication){ //組件模式下設置applicationId applicationId "com.ycbjie.gank" } javaCompileOptions { annotationProcessorOptions { arguments = [AROUTER_MODULE_NAME: project.getName()] } } } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } //jdk1.8 compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } sourceSets { main { if (rootProject.ext.isGankApplication) { manifest.srcFile 'src/main/module/AndroidManifest.xml' } else { manifest.srcFile 'src/main/AndroidManifest.xml' } jniLibs.srcDirs = ['libs'] } } } dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation project(':library') annotationProcessor rootProject.ext.dependencies["router-compiler"] }
4.3 如何統一配置文件
- 由於組件化實踐中模塊比較多,因此配置gradle,添加依賴庫時,需要考慮簡化工作。那麼究竟如何做呢?
-
第一步,首先在項目根目錄下創建一個yc.gradle文件。實際開發中只需要更改該文件中版本信息即可。
- 我在網上看到的絕大多數案例,都是通過一個開關控件組件模式和集成模式的切換,但是這裏我配置了多個組件的開關,分別控制對應的組件切換狀態。
ext { isApplication = false //false:作爲Lib組件存在, true:作爲application存在 isAndroidApplication = false //玩Android模塊開關,false:作爲Lib組件存在, true:作爲application存在 isLoveApplication = false //愛意表達模塊開關,false:作爲Lib組件存在, true:作爲application存在 isVideoApplication = false //視頻模塊開關,false:作爲Lib組件存在, true:作爲application存在 isNoteApplication = false //記事本模塊開關,false:作爲Lib組件存在, true:作爲application存在 isBookApplication = false //book模塊開關,false:作爲Lib組件存在, true:作爲application存在 isDouBanApplication = false //豆瓣模塊開關,false:作爲Lib組件存在, true:作爲application存在 isGankApplication = false //乾貨模塊開關,false:作爲Lib組件存在, true:作爲application存在 isMusicApplication = false //音樂模塊開關,false:作爲Lib組件存在, true:作爲application存在 isNewsApplication = false //新聞模塊開關,false:作爲Lib組件存在, true:作爲application存在 isToDoApplication = false //todo模塊開關,false:作爲Lib組件存在, true:作爲application存在 isZhiHuApplication = false //知乎模塊開關,false:作爲Lib組件存在, true:作爲application存在 isOtherApplication = false //其他模塊開關,false:作爲Lib組件存在, true:作爲application存在 android = [ compileSdkVersion : 28, buildToolsVersion : "28.0.3", minSdkVersion : 17, targetSdkVersion : 28, versionCode : 22, versionName : "1.8.2" //必須是int或者float,否則影響線上升級 ] version = [ androidSupportSdkVersion: "28.0.0", retrofitSdkVersion : "2.4.0", glideSdkVersion : "4.8.0", canarySdkVersion : "1.5.4", constraintVersion : "1.0.2" ] dependencies = [ //support "appcompat-v7" : "com.android.support:appcompat-v7:${version["androidSupportSdkVersion"]}", "multidex" : "com.android.support:multidex:1.0.1", //network "retrofit" : "com.squareup.retrofit2:retrofit:${version["retrofitSdkVersion"]}", "retrofit-converter-gson" : "com.squareup.retrofit2:converter-gson:${version["retrofitSdkVersion"]}", "retrofit-adapter-rxjava" : "com.squareup.retrofit2:adapter-rxjava2:${version["retrofitSdkVersion"]}", //這裏省略一部分代碼 ] }
-
第二步,然後在項目中的lib【注意這裏是放到基礎組件庫的build.gradle】中添加代碼,如下所示
apply plugin: 'com.android.library' android { compileSdkVersion rootProject.ext.android["compileSdkVersion"] buildToolsVersion rootProject.ext.android["buildToolsVersion"] defaultConfig { minSdkVersion rootProject.ext.android["minSdkVersion"] targetSdkVersion rootProject.ext.android["targetSdkVersion"] versionCode rootProject.ext.android["versionCode"] versionName rootProject.ext.android["versionName"] } } dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) api rootProject.ext.dependencies["appcompat-v7"] api rootProject.ext.dependencies["design"] api rootProject.ext.dependencies["palette"] api rootProject.ext.dependencies["glide"] api (rootProject.ext.dependencies["glide-transformations"]){ exclude module: 'glide' } annotationProcessor rootProject.ext.dependencies["glide-compiler"] api files('libs/tbs_sdk_thirdapp_v3.2.0.jar') api "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" //省略部分代碼 }
-
第三步,在其他model中添加依賴
- implementation project(':library')即可。
4.4 組件化的基礎庫
-
基礎庫組件封裝
- 基礎庫組件封裝庫中主要包括開發常用的一些框架。可以直接看我的項目更加直觀!
- 1、網絡請求(採用Retrofit+RxJava框架),攔截器
- 2、圖片加載(策略模式,Glide與Picasso之間可以切換)
- 3、通信機制(RxBus),路由ARouter簡單封裝工具類(不同model之間通信)
- 4、mvp框架,常用的base類,比如BaseActivity,BaseFragment等等
- 5、通用的工具類,比如切割圓角,動畫工具類等等
- 6、自定義view(包括對話框,ToolBar佈局,圓形圖片等view的自定義)
- 7、共有的shape,drawable,layout,color等資源文件
- 8、全局初始化異步線程池封裝庫,各個組件均可以用到
-
組件初始化
- 比如,你將該案例中的新聞組件切換成獨立運行的app,那麼由於新聞跳轉詳情頁需要使用到x5的WebView,因此需要對它進行初始化。最剛開始做法是,爲每一個可以切換成app的組件配置一個獨立的application,然後初始化一些該組件需要初始化的任務。但是這麼做,有一點不好,不是很方便管理。後來看了知乎組件化實踐方案後,該方案提出,開發了一套多線程初始化框架,每個組件只要新建若干個啓動 Task 類,並在 Task 中聲明依賴關係。但是具體怎麼用到代碼中後期有待實現!
-
如何簡化不熟悉組件化的人快速適應組件獨立運行
- 設置多個組件開關,需要切換那個組件就改那個。如果設置一個開關,要麼把所有組件切成集成模式,要麼把所有組件切成組件模式,有點容易出問題。更多可以往下看!
-
嚴格限制公共基礎組件的增長
- 隨着開發不斷進行,要注意不要往基礎公共組件加入太多內容。而是應該減小體積!倘若是基礎組件過於龐大,那麼運行組件也是比較緩慢的!
4.5 組件模式和集成模式如何切換
-
在玩Android組件下的build.gradle文件,其他組件類似。
- 通過一個開關來控制這個狀態的切換,module如果是一個庫,會使用com.android.library插件;如果是一個應用,則使用com.android.application插件
//控制組件模式和集成模式 if (rootProject.ext.isAndroidApplication) { apply plugin: 'com.android.application' } else { apply plugin: 'com.android.library' }
-
集成模式如下所示
- 首先需要在yc.gradle文件中設置 isApplication=false。Sync下後,發現該組件是library
ext { isAndroidApplication = false //false:作爲Lib組件存在, true:作爲application存在
-
組件模式如下所示
- 首先需要在yc.gradle文件中設置 isApplication=true。Sync下後,發現該組件是application,即可針對模塊進行運行
ext { isAndroidApplication = true //false:作爲Lib組件存在, true:作爲application存在
-
需要注意的地方,這個很重要
-
首先看看網上絕大多數的作法,非常感謝這些大神的無私奉獻!但是我覺得多個組件用一個開關控制也可以,但是sourceSets裏面切換成組件app時,可以直接不用下面這麼麻煩,可以複用java和res文件。
- 接下來看看我的做法:
- 下面這個配置十分重要。也就是說當該玩Android組件從library切換到application時,由於可以作爲獨立app運行,所以序意設置applicationId,並且配置清單文件,如下所示!
- 在 library 和 application 之間切換,manifest文件也需要提供兩套
android { defaultConfig { if (rootProject.ext.isAndroidApplication){ //組件模式下設置applicationId applicationId "com.ycbjie.android" } } sourceSets { main { if (rootProject.ext.isAndroidApplication) { manifest.srcFile 'src/main/module/AndroidManifest.xml' } else { manifest.srcFile 'src/main/AndroidManifest.xml' } jniLibs.srcDirs = ['libs'] } } }
- 具體在項目中如下所示
-
4.6 組件化解決重複依賴
-
重複依賴問題說明
- 重複依賴問題其實在開發中經常會遇到,比如項目 implementation 了一個A,然後在這個庫裏面又 implementation 了一個B,然後你的工程中又 implementation 了一個同樣的B,就依賴了兩次。
- 默認情況下,如果是 aar 依賴,gradle 會自動幫我們找出新版本的庫而拋棄舊版本的重複依賴。但是如果使用的是project依賴,gradle並不會去去重,最後打包就會出現代碼中有重複的類了。
-
解決辦法,舉個例子
api(rootProject.ext.dependencies["logger"]) { exclude module: 'support-v4'//根據組件名排除 exclude group: 'android.support.v4'//根據包名排除 }
4.7 組件化注意要點
-
業務組件之間聯動導致耦合嚴重
- 比如,實際開發中,購物車和首頁商品分別是兩個組件。但是遇到產品需求,比如過節做個活動,發個購物券之類的需求,由於購物車和商品詳情頁都有活動,因此會造成組件經常會發生聯動。倘若前期準備不足,隨着時間的推移,各個業務線的代碼邊界會像組件化之前的主工程一樣逐漸劣化,耦合會越來越嚴重。
- 第一種解決方式:使用 sourceSets 的方式將不同的業務代碼放到不同的文件夾,但是 sourceSets 的問題在於,它並不能限制各個 sourceSet 之間互相引用,所以這種方式並不太友好!
- 第二種解決方式:抽取需求爲工具類,通過不同組件傳值而達到調用關係,這樣只需要改工具類即可改需求。但是這種只是符合需求一樣,但是用在不同模塊的場景。
-
組件化開發之數據庫分離
- 比如,我現在開發的視頻模塊想要給別人用,由於緩存之類需要用到數據庫,難道還要把這個lib還得依賴一個體積較大的第三方數據庫?但是使用系統原生sql數據庫又不太方便,怎麼辦?暫時我也沒找到辦法……
4.8 組件化時資源名衝突
-
資源名衝突有哪些?
- 比如,color,shape,drawable,圖片資源,佈局資源,或者anim資源等等,都有可能造成資源名稱衝突。這是爲何了,有時候大家負責不同的模塊,如果不是按照統一規範命名,則會偶發出現該問題。
- 尤其是如果string, color,dimens這些資源分佈在了代碼的各個角落,一個個去拆,非常繁瑣。其實大可不必這麼做。因爲android在build時,會進行資源的merge和shrink。res/values下的各個文件(styles.xml需注意)最後都只會把用到的放到intermediate/res/merged/../valus.xml,無用的都會自動刪除。並且最後我們可以使用lint來自動刪除。所以這個地方不要耗費太多的時間。
-
解決辦法
- 這個問題也不是新問題了,第三方SDK基本都會遇到,可以通過設置 resourcePrefix 來避免。設置了這個值後,你所有的資源名必須以指定的字符串做前綴,否則會報錯。但是 resourcePrefix 這個值只能限定 xml 裏面的資源,並不能限定圖片資源,所有圖片資源仍然需要你手動去修改資源名。
-
個人建議
- 將color,shape等放到基礎庫組件中,因爲所有的業務組件都會依賴基礎組件庫。在styles.xml需注意,寫屬性名字的時候,一定要加上前綴限定詞。假如說不加的話,有可能會在打包成aar後給其他模塊使用的時候,會出現屬性名名字重複的衝突,爲什麼呢?因爲BezelImageView這個名字根本不會出現在intermediate/res/merged/../valus.xml裏, 所以不要以爲這是屬性的限定詞!
4.9 組件化開發遇到問題
-
如何做到各個組件化模塊能獲取到全局上下文
-
情景再現
- 比如,剛開始線上項目是在app主工程裏創建的單利,那麼在lib中或者後期劃分的組件化,是無法拿到主工程的application類中的上下文。這個時候可以
-
解決辦法
- 很容易,在lib裏寫一個Utils工具類,然後在主工程application中初始化Utils.init(this),這樣就可以在lib和所有業務組件[已經依賴公共基礎組件庫]中拿到全局上下文呢!
-
-
butterKnife使用問題
- 儘管網上有不少博客說可以解決butterKnife在不同組件之間的引用。但是我在實際開發時,遇到組件模式和集成模式切換狀態時,導致出現編譯錯誤問題。要是那位在組件化中解決butterKnife引用問題,可以告訴我,非常感謝!
-
當組件化是lib時
- 不能使用switch(R.id.xx),需要使用if..else來代替。
-
不要亂髮bus消息
- 如果項目中大量的使用eventbus,那麼會看到一個類中有大量的onEventMainThread()方法,寫起來很爽,閱讀起來很痛苦。
- 雖然說,前期使用EventBus或者RxBus發送消息來實現組件間通信十分方便和簡單,但是隨着業務增大,和後期不斷更新,有的還經過多個程序員前前後後修改,會使代碼閱讀量降低。項目中發送這個Event的地方非常多,接收這個Event的地方也很多。在後期想要改進爲組件化開發,而進行代碼拆分時,都不敢輕舉妄動,生怕哪些事件沒有被接收。
-
頁面跳轉存在問題
- 如果一個頁面需要登陸狀態纔可以查看,那麼會寫if(isLogin()){//跳轉頁面}else{//跳轉到登錄頁面},每次操作都要寫這些個相同的邏輯。
- 原生startActivity跳轉,無法監聽到跳轉的狀態,比如跳轉錯誤,成功,異常等問題。
- 後時候,後臺會控制從點擊按鈕【不同場景下】跳轉到不同的頁面,假如後臺配置信息錯誤,或者少了參數,那麼跳轉可能不成功或者導致崩潰,這個也沒有一個好的處理機制。
- 阿里推出的開源框架Arouter,便可以解決頁面跳轉問題,可以添加攔截,或者即使後臺配置參數錯誤,當監聽到跳轉異常或者跳轉錯誤時的狀態,可以直接默認跳轉到首頁。我在該開源案例就是這麼做的!
-
關於跳轉參數問題
-
先來看一下這種代碼寫法,這種寫法本沒有問題,只是在多人開發時,如果別人想要跳轉到你開發模塊的某個頁面,那麼就容易傳錯值。建議將key這個值,寫成靜態常量,放到一個專門的類中。方便自己,也方便他人。
//跳轉 intent.setClass(this,CommentActivity.class); intent.putExtra("id",id); intent.putExtra("allNum",allNum); intent.putExtra("shortNum",shortNum); intent.putExtra("longNum",longNum); startActivity(intent); //接收 Intent intent = getIntent(); int allNum = intent.getExtras().getInt("allNum"); int shortNum = intent.getExtras().getInt("shortNum"); int longNum = intent.getExtras().getInt("longNum"); int id = intent.getExtras().getInt("id");
-
5.組件間通信
5.1 選擇那個開源路由庫
-
比較有代表性的組件化開源框架有得到得到DDComponentForAndroid、阿里Arouter、聚美Router 等等。
- 得到DDComponentForAndroid:一套完整有效的android組件化方案,支持組件的組件完全隔離、單獨調試、集成調試、組件交互、UI跳轉、動態加載卸載等功能。
- 阿里Arouter:對頁面、服務提供路由功能的中間件,簡單且夠用好用,網上的使用介紹博客也很多,在該組件化案例中,我就是使用這個。
- Router:一款單品、組件化、插件化全支持的路由框架
5.2 阿里Arouter基礎原理
-
這裏只是說一下基礎的思路
-
在代碼里加入的@Route註解,會在編譯時期通過apt生成一些存儲path和activityClass映射關係的類文件,然後app進程啓動的時候會拿到這些類文件,把保存這些映射關係的數據讀到內存裏(保存在map裏),然後在進行路由跳轉的時候,通過build()方法傳入要到達頁面的路由地址。
- 添加@Route註解然後編譯一下,就可以生成這個類,然後看一下這個類。如下所示:
/** * DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */ public class ARouter$$Group$$video implements IRouteGroup { @Override public void loadInto(Map<String, RouteMeta> atlas) { atlas.put("/video/VideoActivity", RouteMeta.build(RouteType.ACTIVITY, VideoActivity.class, "/video/videoactivity", "video", null, -1, -2147483648)); } }
-
ARouter會通過它自己存儲的路由表找到路由地址對應的Activity.class(activity.class = map.get(path)),然後new Intent(),當調用ARouter的withString()方法它的內部會調用intent.putExtra(String name, String value),調用navigation()方法,它的內部會調用startActivity(intent)進行跳轉,這樣便可以實現兩個相互沒有依賴的module順利的啓動對方的Activity了。
- 看_ARouter類中的 _navigation方法代碼,在345行。
private Object _navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) { final Context currentContext = null == context ? mContext : context; switch (postcard.getType()) { case ACTIVITY: // Build intent final Intent intent = new Intent(currentContext, postcard.getDestination()); intent.putExtras(postcard.getExtras()); // Set flags. int flags = postcard.getFlags(); if (-1 != flags) { intent.setFlags(flags); } else if (!(currentContext instanceof Activity)) { // Non activity, need less one flag. intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); } // Set Actions String action = postcard.getAction(); if (!TextUtils.isEmpty(action)) { intent.setAction(action); } // Navigation in main looper. runInMainThread(new Runnable() { @Override public void run() { startActivity(requestCode, currentContext, intent, postcard, callback); } }); break; case PROVIDER: //這裏省略代碼 case BOARDCAST: case CONTENT_PROVIDER: case FRAGMENT: //這裏省略代碼 case METHOD: case SERVICE: default: return null; } return null; }
-
5.3 使用Arouter注意事項
-
使用阿里路由抽取工具類,方便後期維護!
-
首先看一下網絡上有一種寫法。
//首先通過註解添加下面代碼 @Route(path = "/test/TestActivity") public class TestActivity extends BaseActivity { } //跳轉 ARouter.getInstance().inject("/test/TestActivity");
-
優化後的寫法
- 下面這種做法,是方便後期維護。
//存放所有的路由路徑常量 public class ARouterConstant { //跳轉到視頻頁面 public static final String ACTIVITY_VIDEO_VIDEO = "/video/VideoActivity"; //省略部分diamagnetic } //存放所有的路由跳轉,工具類 public class ARouterUtils { /** * 簡單的跳轉頁面 * @param string string目標界面對應的路徑 */ public static void navigation(String string){ if (string==null){ return; } ARouter.getInstance().build(string).navigation(); } } //調用 @Route(path = ARouterConstant.ACTIVITY_VIDEO_VIDEO) public class VideoActivity extends BaseActivity { } ARouterUtils.navigation(ARouterConstant.ACTIVITY_VIDEO_VIDEO);
-
06.關於其他內容介紹
6.1 關於博客彙總鏈接
6.1 參考博客鏈接
- Android徹底組件化方案實踐:https://www.jianshu.com/p/1b1...
- 教你打造一個Android組件化開發框架:https://blog.csdn.net/cdecde1...
- Android組件化框架設計與實踐:https://www.jianshu.com/p/1c5...
- 知乎 Android 客戶端組件化實踐:https://www.jianshu.com/p/f1a...
- 聚美組件化實踐之路:https://juejin.im/post/5a4b44...
- Android 組件化 —— 路由設計最佳實踐:https://www.jianshu.com/p/8a3...
6.2 關於我的博客
- 我的個人站點:www.yczbj.org,www.ycbjie.cn
- github:https://github.com/yangchong211
- 知乎:https://www.zhihu.com/people/...
- 簡書:http://www.jianshu.com/u/b7b2...
- csdn:http://my.csdn.net/m0_37700275
- 喜馬拉雅聽書:http://www.ximalaya.com/zhubo...
- 開源中國:https://my.oschina.net/zbj161...
- 泡在網上的日子:http://www.jcodecraeer.com/me...
- 郵箱:[email protected]
- 阿里雲博客:https://yq.aliyun.com/users/a... 239.headeruserinfo.3.dT4bcV
- segmentfault頭條:https://segmentfault.com/u/xi...