摘要:主工程具有和組件進行綁定和解綁的功能。如下圖組件化需要考慮問題考慮的問題分而治之,并行開發,一切皆組件。引用阿里的框架,通過注解方式進行頁面跳轉。
目錄介紹
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"http://根據組件名排除 exclude group: "android.support.v4"http://根據包名排除 }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(Mapatlas) { 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 關于博客匯總鏈接
1.技術博客匯總
2.開源項目匯總
3.生活博客匯總
4.喜馬拉雅音頻匯總
5.其他匯總
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...
郵箱:yangchong211@163.com
阿里云博客:https://yq.aliyun.com/users/a... 239.headeruserinfo.3.dT4bcV
segmentfault頭條:https://segmentfault.com/u/xi...
6.3 開源項目地址文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/73157.html
摘要:不努力不奮斗,可能就會在基層一輩子止步不前。不過,只一句,如果你還在做這一行,還是一名程序猿媛,想走上坡路的你,也許我這到手的十幾家一線互聯網公司性能優化項目實戰可能會對你有所幫助。 ...
平日學習接觸過的網站積累,以每月的形式發布。2017年以前看這個網址:http://www.kancloud.cn/jsfron... 1. Javascript 前端生成好看的二維碼 十大經典排序算法(帶動圖演示) 為什么知乎前端圈普遍認為H5游戲和H5展示的JSer 個人整理和封裝的YU.js庫|中文詳細注釋|供新手學習使用 擴展JavaScript語法記錄 - 掉坑初期工具 漢字拼音轉換...
閱讀 1781·2021-11-11 11:02
閱讀 1687·2021-09-22 15:55
閱讀 2492·2021-09-22 15:18
閱讀 3492·2019-08-29 11:26
閱讀 3749·2019-08-26 13:43
閱讀 2651·2019-08-26 13:32
閱讀 905·2019-08-26 10:55
閱讀 969·2019-08-26 10:27