上次,我們講了MVC、MVP、MVVM,其實從狹義上來講,Android的架構(gòu)概念就在這兒,無論怎么變,都是加加減減一些邊邊角角的東西,不足在意。
從本篇開始,打算從廣義上探究一下移動架構(gòu)的思想;包括現(xiàn)在仍然比較火熱的模塊化,組件化, 插件化等架構(gòu)思想。話說在前面,本篇旨在說明當(dāng)前主流架構(gòu)的設(shè)計概念,讓大家了解當(dāng)前的架構(gòu)形式,以及主要用了什么設(shè)計思想,什么設(shè)計思路,算是引導(dǎo)篇...
一、模塊化,組件化,插件化
上述概念已經(jīng)好久了,或許還是有一些同胞對這些概念不是很清楚,大體知道是什么,但是詳細也不知道是什么。現(xiàn)在來解析一下。
二、單工程模式
移動開發(fā)誕生,我們開發(fā)移動項目,我相信大多用的是單工程單任務(wù)的開發(fā)模式,二話不說,直接就開始寫起,是不是這樣呢?
new Project -> 分包 -> 寫起。我相信都經(jīng)歷過,也寫的比較爽,為什么呢?
這種模式不涉及亂七八糟的處理方式, 上手快,開發(fā)快,足夠敏捷。那么原因是什么呢?Mobile Project 剛起步,項目都偏小,一些附加業(yè)務(wù)還沒綁到App上。
1、模塊化
Android Studio出來了,多出來了一個新的概念, Project, Module... 模塊;當(dāng)時以包的形式分離的公共包common,現(xiàn)在成了AS中的Module。
大家都知道,Module包含兩種格式: application, library。也就是說,一個Module就是一個小的項目,也是AS概念中的模塊。
因此我們開始設(shè)計common模塊, common_business模塊,甚至db模塊。模塊的好處是什么?
相比于包來講,模塊更靈活,耦合更低,隨意插拔,想引入哪個就引入哪個。根據(jù)不同的關(guān)注點,將一個項目的可以共享的部分抽取出來,形成獨立的Module,就是模塊化。
當(dāng)然,模塊化不只包含公共部分,當(dāng)然也可以是業(yè)務(wù)模塊。
2、組件化
平時看看論壇,好多人都在問: 模塊化和組件化有什么區(qū)別? 到底有什么區(qū)別呢,其實很?。坏⒉皇峭耆嗤母拍?。 通過以上模塊化的概念講述,應(yīng)該對模塊化有了一個了解,那么區(qū)別是什么呢?
組件化是建立在模塊化思想上的一次演進,一個變種。組件化本來就是模塊化的概念。但是組件化的核心是 什么? 是模塊角色的可轉(zhuǎn)換性。是的,就是可轉(zhuǎn)換性。
組件化的核心是角色的轉(zhuǎn)換。 在打包時, 是library; 在調(diào)試時, 是application。
怎么理解組件化的概念 ?
Module的模式分兩種, application和library
library就是引用庫,如你抽取的common。 而application就是一個apk, 是一個完整的項目。
在調(diào)試時,我只關(guān)心我負責(zé)的模塊,我希望我的模塊是一個單獨的app,因為這樣更小,業(yè)務(wù)更專一,相對來講修改與調(diào)試就會越省時省心,編譯就會越快。試想當(dāng)你需要改一段代碼,既要關(guān)注自己的,也要關(guān)注別人的,是一種什么體驗 ? 或者, 編譯一個項目10M的代碼和一個工程全部1G的代碼,哪個比較舒服一些?
3、 插件化
又有人問了:插件化和組件化又有什么區(qū)別呢?
插件化嚴格意義來講,其實也算是模塊化的觀念。將一個完整的工程,按業(yè)務(wù)劃分為不同的插件,都是分治法的一種體現(xiàn)?;麨榱悖嗷ヅ浜稀T叫〉哪K越容易維護。
插件化按理也算是模塊化的一種體現(xiàn),和組件化就不一個概念了。那么,到底有什么區(qū)別呢?
組件化的單位是組件(module)。
插件化的單位是apk(一個完整的應(yīng)用)。
組件化實現(xiàn)的是解耦與加快編譯, 隔離不需要關(guān)注的部分。
插件化實現(xiàn)的也是解耦與加快編譯,同時實現(xiàn)熱插拔也就是熱更新。
組件化的靈活性在于按加載時機切換,分離出獨立的業(yè)務(wù)組件,比如微信的朋友圈
插件化的靈活性在于是加載apk, 完全可以動態(tài)下載,動態(tài)更新,比組件化更靈活。
組件化能做的只是, 朋友圈已經(jīng)有了,我想單獨調(diào)試,維護,和別人不耦合。但是和整個項目還是有關(guān)聯(lián)的。
插件化可以說朋友圈就是一個app, 我需要整合了,把它整合進微信這個大的app里面
其實從框架名稱就可以看出: 組 和 插。
組本來就是一個系統(tǒng),你把微信分為朋友圈,聊天, 通訊錄按意義上劃為獨立模塊,但并不是真正意義上的獨立模塊。
插本來就是不同的apk, 你把微信的朋友圈,聊天,通訊錄單獨做一個完全獨立的app, 需要微信的時候插在一起,就是一個大型的app了。
插件化的加載是動態(tài)的,這點很重要,也是靈活的根源。
以上是對三個思想的解析,相信應(yīng)該能明白不同的概念的具體意義和區(qū)別在哪了。所謂架構(gòu),無非兩個方面: 分層和通信方式。 其實廣義的架構(gòu)也可以說是這兩個方面:子模塊(子系統(tǒng))劃分和通信。
4、子模塊劃分
除了大家公認的common部分, 業(yè)務(wù)模塊的劃分尤為重要,相比于狹義上的架構(gòu),廣義上的子系統(tǒng)的劃分的關(guān)注點,很考驗技術(shù)經(jīng)驗以及對業(yè)務(wù)的理解。
5、通信方式
模塊化的通信方式,無非是相互引入;我抽取了common, 其他模塊使用自然要引入這個module 組件化的通信方式,按理說可以劃分為多種,主流的是隱式和路由。隱式的存在使解耦與靈活大大降低,因此路由是主流 插件化的通信方式,不同插件本身就是不同的進程了。因此通信方式偏向于Binder機制類似的進程間通信
廢話說了這么多,其實本篇作為組件化的引導(dǎo)篇,本意是要探究一下組件化的思路的,嗯,本篇只講思路;其實思路清晰了,結(jié)合一定的技術(shù)儲備,完全可以自己來實現(xiàn)。好了,開始切入主題...
三、組件化的技術(shù)準備
- 反射與apt
- gradle與groovy
- 路由機制
情報篇
做一件事,首先要明白我們要做什么, 然后劃分步驟,哪一步怎么做,最后逐個解決。這也是分治法的一種思維方式,它當(dāng)然不只是一種算法的解決思想。只有這樣,我們才會建立信息,不會一下子被嚇傻從而放棄。
組件化的思想是Module模式的切換。 上面已經(jīng)說過,在打包時,業(yè)務(wù)module為library; 調(diào)試時,業(yè)務(wù)module成了application。
1.如何切換module的模式呢 ?
我相信都能想到,定義一個Boolean變量作為開關(guān)。根據(jù)開關(guān)分別設(shè)置module的模式,如下
if (isModule) {
apply plugin: 'com.android.application'
} else {
apply plugin: 'com.android.library'
}
2.兩種模式的區(qū)別是什么?
1\. applicationId (包的配置)
只存在于apply plugin: 'com.android.application'模式下
2\. Manifest.xml(主頁面的配置)
集成模式下,使用app模塊下的Manifest.xml配置; 組件模式下,使用組件自己的Manifest.xml配置
3\. Application
不同的組件肯定有自己的初始化的資源或框架,因此自定義的Application也是必要的。但是集成模式下,會造成重復(fù)的Application
3. 面臨的問題
問題1:多業(yè)務(wù)模塊下的統(tǒng)一配置
問題2:Application分發(fā)
問題3:資源的沖突
注意:不同的業(yè)務(wù)模塊禁止彼此依賴
解決方案1:
不同的模塊(20個業(yè)務(wù)模塊)的配置,必須做到統(tǒng)一配置。在Java代碼實現(xiàn)統(tǒng)一配置,SO Easy ~ 但是在gradle中呢 ? 那就是定義一個配置文件,統(tǒng)一存放需要配置的項。如下
ext {
isModule = false // 組件開關(guān): true 組件 false 集成
defaultConfig = [
minSdkVersion : 14,
targetSdkVersion : 26,
versionCode : 1,
versionName : "1.0",
testInstrumentationRunner: "android.support.test.runner.AndroidJUnitRunner"
]
android = [
compileSdkVersion: 26
]
applicationId = [
app : "com.archer.componentsarchitecture",
card1: "com.archer.card1",
card2: "com.archer.card2"
]
supportLibrary = "26.1.0"
appcompatv7a = "com.android.support:appcompat-v7:${supportLibrary}"
resourcePrefixs =[
card1: "card1",
card2: "card2"
]
router = [
arouter: "com.alibaba:arouter-api:1.2.1.1",
processor: "com.alibaba:arouter-compiler:1.1.2.1"
]
}
在Project下創(chuàng)建一個config.gradle(什么?創(chuàng)建不了?那就把Project自帶的build.gradle復(fù)制一份rename & clear)。 xt是groovy提供的擴展參數(shù),不可修改的。
以下可以隨意定義自己的配置,如代碼。這里說下兩個概念:
- 占位符 ${supportLibrary} 占據(jù)一個位置,然后用{}里面的變量補充,達到一致配置的目的
- android = [ compileSdkVersion: 26 ] 以上相當(dāng)于定義了一個Map, 存放鍵值對,以Key: Value的形式,以,分隔。這是groovy的寫法。android 為Map的名稱,你可以用你自己的命名,但是注意不要和系統(tǒng)變量沖突
以上是統(tǒng)一變量的定義,配置文件config.gradle。 配置文件定義好了,那么如何引入呢?
-
在[Project]下的build.gradle引入配置文件
image2.在Module中引用是通過rootProject.ext.你定義的名稱。但是每次這么用比較繁瑣,推薦定義變量實現(xiàn),如下
if (isModule) {
apply plugin: 'com.android.application'
} else {
apply plugin: 'com.android.library'
}
def cfg = rootProject.ext.defaultConfig
def drd = rootProject.ext.android
def app = rootProject.ext.applicationId
android {
compileSdkVersion drd.compileSdkVersion
defaultConfig {
if (isModule) {
applicationId app['card2']
}
minSdkVersion cfg.minSdkVersion
targetSdkVersion cfg.targetSdkVersion
versionCode cfg.versionCode
versionName cfg.versionName
testInstrumentationRunner cfg.testInstrumentationRunner
resourcePrefix rootProject.ext.resourcePrefixs['card2']
javaCompileOptions {
annotationProcessorOptions {
arguments = [ moduleName : project.getName() ]
}
}
}
調(diào)用Groovy map中的字段的兩種方式: rootProject.ext.android.key和 rootProject.ext.android['key']
解決方案2:
application的分發(fā),錯誤的做法是不同的組件下初始化自己的框架,工具等。正確的做法是在BaseApplication或統(tǒng)一實現(xiàn)公共模塊如網(wǎng)絡(luò), 緩存, 數(shù)據(jù)庫等的初始化,在各Module實現(xiàn)自己需要的初始化,來避免重復(fù)的初始化與沖突。
解決方案3:
資源的沖突解決辦法有兩個: 1) 公共資源建議由公共模塊管理 2) 模塊私有資源,添加前綴限制 (只能解決xml沖突) 3)資源謹慎命名
資源命名只能在開發(fā)中加以注意, 通過以上共有資源和前綴極大可能的保證資源不會沖突,且不會重復(fù)浪費。至于萬一的沖突,只能交給開發(fā)規(guī)范了。
解決方案4:
這個是新加的,也就是前面說的,怎么控制application和library的轉(zhuǎn)換,全部配置如下:
// 自由控制模式轉(zhuǎn)換
if (isModule) {
apply plugin: 'com.android.application'
} else {
apply plugin: 'com.android.library'
}
// 包名配置
defaultConfig {
if (isModule) {
applicationId app['card2']
}
}
// Manifest.xml application配置
sourceSets {
main {
if (isModule) {
// src/main下新建文件夾,存放組件模式下的Manifest.xml與Application
manifest.srcFile 'src/main/component/AndroidManifest.xml'
java.srcDirs = ['src/main/java', 'src/main/component/java']
} else {
// library模式下不需要Application
manifest.srcFile 'src/main/AndroidManifest.xml'
java.srcDirs 'src/main/java'
}
}
}
以上配置在相應(yīng)Module中的build.gradle的android下
本來打算出這篇為Router原理思想的,結(jié)果為了引導(dǎo)從模塊化的概念直到組件化的核心概念以及初步實現(xiàn)。因為真的不少人可能對這些概念了解不是很清楚,包括以前的我,比如模塊化和組件化。
從整篇來看,組件化無非就是實現(xiàn)了一次轉(zhuǎn)換,解決的一些轉(zhuǎn)換過程中涉及的問題。沒那么難, 也存在一些坑,只有在開發(fā)過程中隨著遇到進一步填平。
組件化的配置核心就是library和application的toggle。 真正實現(xiàn)的功能核心卻是通信部分的路由實現(xiàn)部分,下一篇講一下,如何手寫Rooter通信框架。
最后
由于水平有限,有錯誤的地方在所難免,未免誤導(dǎo)他人,歡迎大佬指正!碼字不易,感謝大家的點贊關(guān)注!
視頻:
價值100w+Android項目實戰(zhàn)大全:插件化
價值100w+Android項目實戰(zhàn)大全:組件化