Android組件化框架搭建

前言

組件化是什么钓丰,是把一個(gè)功能完整的 App 或模塊拆分成多個(gè)子模塊, 表現(xiàn)在androidStudio項(xiàng)目工程里就是分多個(gè)module内狗。每個(gè)子模塊可以獨(dú)立編譯和運(yùn)行, 模塊之間可以任意組合成另一個(gè)新的 App 或模塊, 每個(gè)模塊不必須相互依賴但可以相互調(diào)起和通信苹支。
組件化的意義,對(duì)于一個(gè)小型項(xiàng)目來說可能覺得多此一舉,但是對(duì)于一個(gè)中型以上的項(xiàng)目莺匠,組件化還是非常有意義的浇衬。APP版本不斷的迭代懒构,新功能的不斷增加,業(yè)務(wù)也會(huì)變的越來越復(fù)雜耘擂,的代碼也變的越來越多胆剧,代碼耦合嚴(yán)重,影響開發(fā)效率醉冤,增加項(xiàng)目的維護(hù)成本秩霍,編譯代碼的時(shí)間長,每修改一處代碼后都要重新編譯整個(gè)項(xiàng)目蚁阳。組件化的出現(xiàn)就是解決以上問題铃绒,并且還具備自由組裝成新的模塊的優(yōu)點(diǎn)。
我研究組件化主要還是為了技術(shù)儲(chǔ)備和支持新業(yè)務(wù)的開發(fā)螺捐,對(duì)于主項(xiàng)目要使用的話颠悬,就相當(dāng)于項(xiàng)目重構(gòu),比較費(fèi)時(shí)費(fèi)力定血,在各組都有KPI的情況下是不會(huì)給我們時(shí)間重構(gòu)的赔癌,閑話不多說,下面介紹組件化框架和搭建過程中注意的問題糠悼。

組件化架構(gòu)

直接上圖:


21.png

架構(gòu)介紹:
如圖一共分了4層:應(yīng)用層届榄、業(yè)務(wù)組件、功能組件倔喂、基礎(chǔ)組件铝条。

  • 應(yīng)用層:我們的app殼工程,負(fù)責(zé)管理各個(gè)業(yè)務(wù)組件席噩,和打包apk班缰,沒有具體的業(yè)務(wù)功能。
  • 業(yè)務(wù)組件:具體業(yè)務(wù)而獨(dú)立形成一個(gè)的工程悼枢,可以單獨(dú)運(yùn)行提供功能埠忘。
  • 功能組件:APP的某些基礎(chǔ)功能,可單獨(dú)編譯,但不會(huì)單獨(dú)發(fā)布提供功能apk莹妒。
  • 基礎(chǔ)組件:開源的第三方的庫名船。

Eventbus 是用來各層之間的通信,組件路由是用來實(shí)現(xiàn)組件之間的通信和調(diào)起旨怠。
網(wǎng)上有的文章有不同的分層發(fā)渠驼,有人分三層把業(yè)務(wù)組件和功能組件統(tǒng)稱組件層。有人對(duì)業(yè)務(wù)組件進(jìn)行細(xì)分鉴腻,如Main組件迷扇。整體是一樣的,關(guān)鍵大家能理解整個(gè)架構(gòu)爽哎。

組件化項(xiàng)目結(jié)構(gòu)介紹

直接上圖:

223.png

大家看到有多個(gè)module蜓席,以app_開頭的是可以單獨(dú)打包發(fā)布的module,也就是app殼module课锌,和業(yè)務(wù)組件module厨内,module開頭的是不單獨(dú)打包發(fā)布的組件,就是功能組件渺贤。我們一一說明:

  • app:app殼隘庄,就是我們主項(xiàng)目。
  • app_radio:電臺(tái)業(yè)務(wù)模塊癣亚,可以單獨(dú)作為電臺(tái)app發(fā)布,也是app主項(xiàng)目的一個(gè)功能模塊获印。
  • module_core:項(xiàng)目的基礎(chǔ)核心組件述雾,說是基礎(chǔ),他是對(duì)基礎(chǔ)組件的封裝兼丰,包括架構(gòu)圖中的Glide玻孟、Retrofit、Rxjava等的封裝鳍征,提供基礎(chǔ)功能黍翎。說是核心,他是對(duì)項(xiàng)目基礎(chǔ)架構(gòu)的封裝艳丛,這里使用的是MVP架構(gòu)匣掸。
  • module_commons:封裝組件共用的類和資源,各組件模塊解耦之后氮双,避免不了一些共用的類和資源碰酝,比如實(shí)體類、錯(cuò)誤頁戴差、dialog等送爸。
  • module_router:封裝組件路由,實(shí)現(xiàn)組件調(diào)起和通信。
  • module_share / module_playserservice:我們項(xiàng)目用到的功能組件袭厂,不是必要的墨吓。

網(wǎng)上有同學(xué)有不同的module分發(fā),比如module_commons進(jìn)一步細(xì)分公共的類module和資源module纹磺。大家可以根據(jù)實(shí)際情況細(xì)分帖烘。到這里大家會(huì)發(fā)現(xiàn)組件化的一個(gè)弊端,就是項(xiàng)目會(huì)有很多的module爽航。

組件化搭建注意問題

組建如何單獨(dú)編譯

思路就是在build.gradle的區(qū)分是apply plugin: 'com.android.application'還是apply plugin: 'com.android.library'蚓让。具體實(shí)現(xiàn):
在gradle.properties文件添加一個(gè)判斷屬性

isBuildAsModule=false

在build.gradle里根據(jù)屬性加載不同插件:

if(isBuildAsModule.toBoolean()){
    apply plugin: 'com.android.application'
}else{
    apply plugin: 'com.android.library'
}

在兩種情況下AndroidManifest.xml文件是有差別的。作為獨(dú)立運(yùn)行的app讥珍,有自己的Application历极,要加Launcher的入口intent,而作為library不需要衷佃。所以需要寫兩個(gè)不同的AndroidManifest.xml即可趟卸,通過isBuildAsModule加以區(qū)分。

   if (isBuildAsModule.toBoolean()) {
          manifest.srcFile 'src/main/module/AndroidManifest.xml'
   } else {
         manifest.srcFile 'src/main/library/AndroidManifest.xml'
   }

這種方式同時(shí)避免了AndroidManifest.xml清單文件沖突問題氏义。
其他需要區(qū)分兩種編譯模式锄列,原理相同。

基礎(chǔ)庫和SDK版本號(hào)統(tǒng)一

這是多module開發(fā)需要解決的問題惯悠,不同module的依賴sdk版本不一致或者引入的庫版本不一致會(huì)導(dǎo)致編譯問題和兼容性問題邻邮。解決辦法是在主項(xiàng)目最外層用一個(gè)config.xml文件來統(tǒng)一管理基礎(chǔ)庫和sdk的版本。config.xml部分代碼如下:

def retrofit_version = '2.3.0'
def okhttp_version = '3.9.0'
def dagger_version = '2.11'
def autodispose_version = "0.5.1"
def support_version = '27.1.0'
def espresso_version = '2.2.2'
def glide_version = '4.6.1'
def butterknife_version = '8.8.1'
def routerVersion = "1.2.4"
def routerCompilerVersion = "1.1.4"

project.ext {
    android = [
            compileSdkVersion: 27,
            buildToolsVersion: "27.0.3",
            applicationId    : "com.taihe.music.mvpsample",
            minSdkVersion    : 16,
            targetSdkVersion : 27,
            versionCode      : 1,
            versionName      : "1.0"
    ]

    dependencies = [
            //android-support
            "support-v4"                 : "com.android.support:support-v4:${support_version}",
            "appcompat-v7"               : "com.android.support:appcompat-v7:${support_version}",
         ..........

使用config.xml克婶,時(shí)在最外層build.gradle配置config文件:

apply from: "config.gradle"

具體引入基礎(chǔ)庫的地方在各module的build.gradle文件里:

        minSdkVersion rootProject.ext.android["minSdkVersion"]
        targetSdkVersion rootProject.ext.android["targetSdkVersion"]
        versionCode rootProject.ext.android["versionCode"]
        versionName rootProject.ext.android["versionName"]
.....
   //glide
    api rootProject.ext.dependencies["glide"]
    annotationProcessor rootProject.ext.dependencies["glide-compiler"]
組件之間資源名沖突

組件之間如果資源命名相同筒严,就會(huì)產(chǎn)生沖突,解決這個(gè)問題最簡(jiǎn)單的辦法就是在項(xiàng)目中制定資源文件命名規(guī)范情萤,比如app_radio組件所有資源以radio_開頭鸭蛙,所有開發(fā)人員必須遵守規(guī)范。
當(dāng)然筋岛,gladle也給我提供了解決方案娶视,就是在build.gradle中添加如下的代碼:

 resourcePrefix "radio_"

設(shè)置了這個(gè)屬性后,所有的資源名必須以指定的字符串做前綴睁宰,否則會(huì)報(bào)錯(cuò)肪获。而且這種形式只能限定xml里面的資源,并不能限定圖片資源勋陪,我們?nèi)匀恍枰謩?dòng)去修改資源名贪磺;所以不推薦使用這種方法來解決資源名沖突。

基礎(chǔ)組件依賴問題

所有的基礎(chǔ)組件我們封裝在module_core中诅愚,這樣就存在一個(gè)問題寒锚,我們項(xiàng)目依賴了module_core就默認(rèn)依賴了所有的基礎(chǔ)組件劫映,但實(shí)際情況中我們是不需要依賴所有的,或者module_core中的基礎(chǔ)組件和其他模塊有沖突的情況刹前。所以我們需要排除掉用不到和重復(fù)的庫泳赋,Gradle支持兩種排除方式,根據(jù)組件名排除或者根據(jù)包名排除喇喉,代碼如下:

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])    compile("com.jude:easyrecyclerview:$rootProject.easyRecyclerVersion") {
        exclude module: 'support-v4'//根據(jù)組件名排除
        exclude group: 'android.support.v4'//根據(jù)包名排除
    }
}
組件之間跳轉(zhuǎn)和通信

組件之間的頁面調(diào)起祖今,雖然我們可以使用intentFilter來實(shí)現(xiàn)Activity的隱式啟動(dòng),但是邏輯上存在耦合拣技,并且不支持Fragment千诬,所以這里我們使用的是阿里的組件路由Arouter。它可以實(shí)現(xiàn)Activity和Fragment的調(diào)起膏斤,支持依賴注入和數(shù)據(jù)通信徐绑。這里我們只介紹Activty調(diào)起,詳細(xì)使用可參考https://blog.csdn.net/zhaoyanjun6/article/details/76165252
我們以調(diào)起app_radio組件中的HomeActivity為例,HomeActivity類配置如下:

@Route(path = RouterConstants.RADIO_HOME_ACTIVITY)
public class HomeActivity extends BaseActivity<HomePresenter> implements HomeContract.View {

    @BindView(R2.id.btnUserInfo)
    Button button;
    @BindView(R2.id.ivTest)
    ImageView imageView;
    ........

調(diào)起HomeActivity的代碼:

ARouter.getInstance()
       .build(RouterConstants.RADIO_HOME_ACTIVITY)
       .withObject("user", userModel)
       .navigation()莫辨;

可以看出組件路由需要定義常量RouterConstants.RADIO_HOME_ACTIVITY來關(guān)聯(lián)調(diào)起頁面傲茄,withObject("user", userModel)是頁面間傳遞的數(shù)據(jù)。

手動(dòng)單獨(dú)編譯

組件化的一個(gè)優(yōu)點(diǎn)就是可以單獨(dú)編譯沮榜,但是在開發(fā)過程發(fā)現(xiàn)有的時(shí)候修改了模塊module里一些配置文體之后盘榨,編譯主app,模塊module不會(huì)重新編譯蟆融,導(dǎo)致修改無效草巡。所以在修改了配置文件之后最好手動(dòng)編譯module確保修改有效。單獨(dú)編譯的入口在下圖:


1538204810(1).png
動(dòng)態(tài)變化網(wǎng)絡(luò)baseUrl

module_core中我使用Retrofit+OKhttp封裝的網(wǎng)絡(luò)層型酥,并對(duì)外提供配置接口捷犹,包括配置baseUrl,主module中Applicatuion中會(huì)完成這些配置冕末。問題來了,如果其他模塊使用baseUrl不同怎么處理侣颂。思路就是档桃,對(duì)OkHttp進(jìn)行修改,無非就是使用攔截器憔晒,幸好好心人提供了開源庫RetrofitUrlManager可以解決該問題藻肄。詳細(xì)參考,https://github.com/JessYanCoding/RetrofitUrlManager
動(dòng)態(tài)修改baseurl的代碼:
將OkHttpClient.Builder傳給RetrofitUrlManager拒担。

 RetrofitUrlManager.getInstance().with(builder);

以app_radio配置baseUrl為例:

  RetrofitUrlManager.getInstance().putDomain(RADIO_DOMAIN_NAME, RADIO_BASE_API);

RADIO_BASE_API為具體的baseUrl嘹屯,RADIO_DOMAIN_NAME為對(duì)應(yīng)的key。
具體使用Retrofit來定義請(qǐng)求接口時(shí):

  @Headers({DOMAIN_NAME_HEADER + RADIO_DOMAIN_NAME})
    @GET("users/{user}")
    Maybe<UserInfo> getUserInfo(@Path("user") String user);

這樣就可以實(shí)現(xiàn)baseUrl的動(dòng)態(tài)變化从撼。同理如果其他的網(wǎng)絡(luò)配置不同也是使用攔截器是形式來修改州弟,大家可以參考RetrofitUrlManager的實(shí)現(xiàn)。
本文從整體上介紹了組件化的搭建問題,沒有具體到基礎(chǔ)庫和框架的封裝婆翔,也就是module_core的搭建和使用拯杠。想要熟悉整體框架或者使用,首先要熟悉第三方庫的使用和原理啃奴,封裝的過程就是改造的過程所以要了解基本原理潭陪;其次再熟悉module_core的封裝代和使用,最后才是本文提到的組件化過程中的問題解決最蕾。
跟本文一樣依溯,大部分開源組件化項(xiàng)目,基礎(chǔ)庫用到了Dagger瘟则、Rxjava黎炉。這兩個(gè)開源項(xiàng)目需要一定的學(xué)習(xí)成本,建議大家擇情而定是否使用壹粟。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末拜隧,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子趁仙,更是在濱河造成了極大的恐慌洪添,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,204評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件雀费,死亡現(xiàn)場(chǎng)離奇詭異干奢,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)盏袄,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門忿峻,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人辕羽,你說我怎么就攤上這事逛尚。” “怎么了刁愿?”我有些...
    開封第一講書人閱讀 164,548評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵绰寞,是天一觀的道長。 經(jīng)常有香客問我铣口,道長滤钱,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,657評(píng)論 1 293
  • 正文 為了忘掉前任脑题,我火速辦了婚禮件缸,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘叔遂。我一直安慰自己他炊,他們只是感情好争剿,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,689評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著佑稠,像睡著了一般秒梅。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上舌胶,一...
    開封第一講書人閱讀 51,554評(píng)論 1 305
  • 那天捆蜀,我揣著相機(jī)與錄音,去河邊找鬼幔嫂。 笑死辆它,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的履恩。 我是一名探鬼主播锰茉,決...
    沈念sama閱讀 40,302評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼切心!你這毒婦竟也來了飒筑?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,216評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤绽昏,失蹤者是張志新(化名)和其女友劉穎协屡,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體全谤,經(jīng)...
    沈念sama閱讀 45,661評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡肤晓,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,851評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了认然。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片补憾。...
    茶點(diǎn)故事閱讀 39,977評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖卷员,靈堂內(nèi)的尸體忽然破棺而出盈匾,到底是詐尸還是另有隱情,我是刑警寧澤毕骡,帶...
    沈念sama閱讀 35,697評(píng)論 5 347
  • 正文 年R本政府宣布威酒,位于F島的核電站,受9級(jí)特大地震影響挺峡,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜担钮,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,306評(píng)論 3 330
  • 文/蒙蒙 一橱赠、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧箫津,春花似錦狭姨、人聲如沸宰啦。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,898評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽赡模。三九已至,卻和暖如春师抄,著一層夾襖步出監(jiān)牢的瞬間漓柑,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,019評(píng)論 1 270
  • 我被黑心中介騙來泰國打工叨吮, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留辆布,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,138評(píng)論 3 370
  • 正文 我出身青樓茶鉴,卻偏偏與公主長得像锋玲,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子涵叮,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,927評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,133評(píng)論 25 707
  • 用兩張圖告訴你惭蹂,為什么你的 App 會(huì)卡頓? - Android - 掘金 Cover 有什么料? 從這篇文章中你...
    hw1212閱讀 12,724評(píng)論 2 59
  • 1割粮、通過CocoaPods安裝項(xiàng)目名稱項(xiàng)目信息 AFNetworking網(wǎng)絡(luò)請(qǐng)求組件 FMDB本地?cái)?shù)據(jù)庫組件 SD...
    陽明先生_X自主閱讀 15,981評(píng)論 3 119
  • 第十八回 洞天福地 王洛楨和林阿嬌所騎之馬盾碗,見到狼群后嚇得驚慌失措,待二人反應(yīng)過來時(shí)穆刻,早已跑得沒影了置尔。二人相視苦笑...
    歐陸作文章閱讀 535評(píng)論 0 0
  • 時(shí)光匆匆,年輪寫在臉上,卻沒有寫在心里.淡然與挫敗充斥著內(nèi)心. 明天的明天,太陽依舊會(huì)在老地方升起,可是今天卻與昨...
    首啟文閱讀 577評(píng)論 0 0