updata time 2020年10月09日14:03:57
前言
?? 在平時的開發(fā)過程中,中小項目MVC 、MVP 已經(jīng)夠用。但在大公司中,由于過多的業(yè)務(wù)邏輯,數(shù)個開發(fā)人員合作開發(fā)。復(fù)用問題、不方便、編譯時長,方便測試等問題的出現(xiàn),也就決定了Android 架構(gòu)的演變方向,最近時間充裕,該片文章為本人從零搭建組件化的一些思考記錄及總結(jié),有不對的地方還望多包涵。
一、 什么是組件化
?? 項目按功能拆分成功若干個組件,每個組件負(fù)責(zé)相應(yīng)的功能,每個組件都可以以一個單獨的 module 開發(fā),并且可以單獨抽出來作為 SDK 對外發(fā)布使用,比如登錄組件,視頻組件。組件化與模塊化其實很相似,但不同的是模塊化是以業(yè)務(wù)為導(dǎo)向,組件化是以功能為導(dǎo)向。一般一個模塊可以包含多個組件。
?? 自己對項目架構(gòu)進(jìn)行一個圖形化

BaseLib :各種基礎(chǔ)工具類,比如:Log工具類,Activity 入棧出棧, 等基礎(chǔ)工具類為基礎(chǔ)Lib,可以供所有Lib依賴
Lib : 基礎(chǔ)功能Sdk底層封裝。比如:網(wǎng)絡(luò)Sdk ,圖片加載,數(shù)據(jù)庫等功能都封裝為單獨的Lib。
基礎(chǔ)業(yè)務(wù)層 :module可封裝工具類 如: BaseActivity BaseFragment 等
業(yè)務(wù)層 : 每個模塊代表了一個業(yè)務(wù),模塊之間相互隔離解耦,方便維護(hù)和復(fù)用。(類似模塊化)
宿主層 : App外殼,不參與業(yè)務(wù)功能實現(xiàn),主要承擔(dān)App生成和 初始化功能。
上述架構(gòu)為本人項目,讀者可以根據(jù)業(yè)務(wù)情況自行拆分,baseLib和Lib其實也可以合并為一個整體
二、 組件化完善與優(yōu)化
?? 相比于單個App Module ,多個Module 多個Lib 要實現(xiàn)業(yè)務(wù)邏輯,還存在一些問題:
1 . 組件間界面跳轉(zhuǎn),不同組件之間不僅會有數(shù)據(jù)的傳遞,也會有相互的頁面跳轉(zhuǎn)。(ARouter)
2 . 數(shù)據(jù)傳遞與組件間方法 如何相互調(diào)用。
3 . 如何獲取組件中 Fragment 的實例并將組件中的 Fragment 實例添加到主項目的界面中?
4 . 單個模塊如何獨立運行和組件式運行 切換問題。
5 . 多個content provider 初始話插件方式的優(yōu)化 (startup)
- module單獨運行 和合并運行管理。
三、組件化問題解決
1 . 界面跳轉(zhuǎn)
?? Activity跳轉(zhuǎn)分為 顯示跳轉(zhuǎn)和隱式跳轉(zhuǎn),但是顯示跳轉(zhuǎn)存在Activity之間的雙向依賴,不符合組件化的架構(gòu)思想定位,我們這里使用 隱式跳轉(zhuǎn),但是手搓Schame,清單文件會非?;靵y,這里我們使用 alibaba 的開源庫 ARouter。
原理:代碼里加入的@Route注解,在編譯時期通過apt生成存儲path和activityClass映射關(guān)系的類文件,app進(jìn)程啟動的時候拿到這些類文件,把保存這些映射關(guān)系的數(shù)據(jù)讀到map中,進(jìn)行路由跳轉(zhuǎn)的時候,通過build()方法傳入要到達(dá)頁面的路由地址,ARouter會通過它自己存儲的路由表找到路由地址對應(yīng)的Activity.class(activity.class = map.get(path)),然后new Intent(),當(dāng)調(diào)用ARouter的withString()方法它的內(nèi)部會調(diào)用intent.putExtra(String name, String value),調(diào)用navigation()方法,它的內(nèi)部會調(diào)用startActivity(intent)進(jìn)行跳轉(zhuǎn),這樣便可以實現(xiàn)相互沒有依賴的module順利的啟動對方的Activity了。
代碼示例:
// 路由 gradle引入(kotlin 和 java引入方式區(qū)分)
api libs.arouter_api
kapt libs.arouter_compiler
// 定義路由地址
object ARouterConstant {
// == 服務(wù)
const val LOGIN_SERVICE = "/login/login_service"
//====== login
//跳轉(zhuǎn)到登陸頁面
const val LOGIN_ACTIVITY = "/login/LoginActivity"
const val LOGIN_FRAGMENT = "/login/LoginFragment"
...
}
// 路由地址目標(biāo)類
@Route(path = LOGIN_ACTIVITY)
class LoginActivity : ContainerActivity() {
override fun initBaseFragment(): Fragment? {
return LoginFragment()
}
}
//調(diào)用界面跳轉(zhuǎn)
ARouter.build(ARouterConstant . LOGIN_ACTIVITY).navigation()
2 . 通信問題
?? 常用的通信方式有:BroadcastReceiver 、EventBus、接口等,考慮到 BroadcastReceiver 偏重,Eventbus 雖然3.0以后采用注解方式通信比2.X版本采用反射快得多,但根據(jù)組件化架構(gòu)多個Module都依賴于Base Module,這里使用ARouter 的IProvider方式進(jìn)行數(shù)據(jù)傳遞。
//接口定義
interface LoginService : IProvider {
val userInfo: UserBean?
...
}
//接口實現(xiàn)
@Route(path = LOGIN_SERVICE)
class LoginServiceImpl : LoginService {
override val userInfo: UserBean
get() = getUserBean()
...
}
// 調(diào)用
class LoginImpl public constructor() {
init {
//初始化
ARouter.getInstance().inject(this)
}
@Autowired(name = ARouterConstant.LOGIN_SERVICE)
lateinit var mLoginService: LoginService
/**
* 獲取用戶信息
*/
val userInfo: UserBean?
get() = mLoginService.userInfo
}
3. 單工程方案
??由于多個Module,存在獨立運行和合并運行的情況,資源文件命名沖突及 Lib、Module定義等問題。
// module_home.gradle文件
apply from: rootProject.file('module.gradle')
android {
defaultConfig {
//僅在以application方式編譯時才添加applicationId屬性
if (runAsApp) {
applicationId build_version.applicationId + '.module_home'
}
}
//資源命名規(guī)范
resourcePrefix "home_"
kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8.toString()
}
}
dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"])
...
}
通過上述資源規(guī)范,可以讓各個module出現(xiàn)命名不規(guī)范的變量名,會給予提示。
//
//是否library
def isLibrary = ext.has('isLibrary')
ext.isLibrary = isLibrary
//
def isMainLibrary = ext.has('isMainLibrary')
ext.isMainLibrary = isMainLibrary
//設(shè)置到ext中,供lib的build.gradle使用
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
compileSdkVersion build_version.compileSdkVersion
buildToolsVersion build_version.buildToolsVersion
defaultConfig {
...
}
buildTypes {
debug {
...
}
release {
release {
...
}
}
}
sourceSets {
main {
jniLibs.srcDirs = ['libs']
}
}
// 省略部分配置
...
}
dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"])
...
}
上述代碼,通過ext.isLibrary來區(qū)分該module是以lib方式運行還是獨立運行。
local.properties 文件
sdk.dir=/Users/yangmingchuan/Library/Android/sdk
// 配置本地debug 開關(guān)
#module_home=true
#module_login=true
module.gradle 文件
//配置AndroidManifest.xml在library模式和application模式下的文件路徑
android {
//隱藏部分配置
...
sourceSets {
main {
jniLibs.srcDirs = ['libs']
//默認(rèn)的作為application運行時Manifest文件路徑
def debugManifest = 'src/main/debug/AndroidManifest.xml'
if (runAsApp && project.file(debugManifest).exists()) {
manifest.srcFile debugManifest
} else {
manifest.srcFile 'src/main/AndroidManifest.xml'
//集成開發(fā)模式下自動排除debug文件夾中的所有Java文件
// 可以將debug代碼放在這個包內(nèi),例如:Application子類
java {
exclude 'debug/**'
}
}
// 注:2018-03-12推薦:將組件單獨以app運行時的測試代碼及資源放到src/main/debug/目錄下
if (runAsApp) {
//debug模式下,如果存在src/main/debug/assets,則自動將其添加到assets源碼目錄
if (project.file('src/main/debug/assets').exists()) {
assets.srcDirs = ['src/main/assets', 'src/main/debug/assets']
}
//debug模式下,如果存在src/main/debug/java,則自動將其添加到j(luò)ava源碼目錄
if (project.file('src/main/debug/java').exists()) {
java.srcDirs = ['src/main/java', 'src/main/debug/java']
}
//debug模式下,如果存在src/main/debug/res,則自動將其添加到資源目錄
if (project.file('src/main/debug/res').exists()) {
res.srcDirs = ['src/main/res', 'src/main/debug/res']
}
}
}
}
//隱藏部分配置
...
}
上述代碼,使用讀取 local.properties 本地配置,sync project 后,即可單Module運行 加載src/main/debug文件夾下的清單文件, 開始debug測試。

該文章為本人學(xué)習(xí)組件化總結(jié),有不當(dāng)?shù)牡胤竭€望指出,一起學(xué)習(xí)。
后續(xù)不斷更新....
不要臉貼下GitHub ComponentMaster