前言
相信各位小伙伴們對(duì)組件化開發(fā)都不陌生了,本文只對(duì)我所理解和使用的組件化開發(fā)方案做一個(gè)總結(jié),有不正確或者需要改進(jìn)和優(yōu)化的地方,望大家及時(shí)指出
如何將一個(gè)項(xiàng)目組件化
下面以我的ModuleDemo舉例說明
架構(gòu)圖
大致分為4部分:
1. App殼
啟動(dòng)頁,組件初始化,項(xiàng)目基本框架
2. 業(yè)務(wù)組件
具體的組件,例如上圖home,moduleA組件
3. 組件基礎(chǔ)庫
例如上圖business
定義了各個(gè)組件對(duì)外提供的服務(wù),組件間的共享資源
4. 通用庫,路由庫
全局通用的資源(例如Theme和color等),第三方庫
路由庫主要用戶業(yè)務(wù)組件間交互
調(diào)試和發(fā)布
為了避免每個(gè)組件都進(jìn)行重復(fù)的一些配置,我這里對(duì)gradle做了些封裝,下面會(huì)給完整的配置,這里我們先來看看大致步驟
工程 gradle配置
apply from: "${rootProject.rootDir}/version.gradle"
統(tǒng)一依賴,組件模式開關(guān)
App gradle配置
dependencies {
implementation rootProject.ext.dependencies["appcompat-v7"]
if (rootProject.ext.isModuleADebug){
implementation project(':modulea')
}
if (rootProject.ext.isHomeDebug){
implementation project(':home')
}
}
組件單獨(dú)運(yùn)行時(shí),取消app的依賴
組件 gradle配置
模式切換
apply from: "${rootProject.rootDir}/config.gradle"
def isHomeDebug = rootProject.ext.isHomeDebug
if (rootProject.ext.isHomeDebug) {
project.ext.setLibDefaultConfig project
} else {
project.ext.setAppDefaultConfig project
}
通過控制isHomeDebug的值,來導(dǎo)入不同的配置,調(diào)試模式引入Application插件,和設(shè)置Application相關(guān)的配置,組件模式引入library插件,設(shè)置library相關(guān)配置
指定資源目錄
sourceSets {
main {
if (!isModuleADebug) {
manifest.srcFile 'src/main/module/AndroidManifest.xml'
} else {
manifest.srcFile 'src/main/AndroidManifest.xml'
//集成開發(fā)模式下排除debug文件夾中的所有Java文件
java {
exclude 'debug/**'
}
}
}
}
如圖
當(dāng)組件作為單獨(dú)的project運(yùn)行時(shí),我們需要提供調(diào)試相關(guān)一些資源,所以我創(chuàng)建了兩個(gè)文件夾,module文件夾里面提供AndroidManifest為了調(diào)試模式時(shí)的啟動(dòng)頁配置,debug文件夾可以放置調(diào)試時(shí)需要使用的資源,例如Activity等
UI跳轉(zhuǎn)
組件內(nèi)部,采用原生的跳轉(zhuǎn)方式,不同組件間采用Arouter中的Provider來提供組件的相關(guān)跳轉(zhuǎn),我覺得這樣可以很清楚的知道每個(gè)模塊對(duì)外提供的功能,比起在模塊上的Activity上加注解跳轉(zhuǎn)方式好一些
在business組件基礎(chǔ)庫中,定義home組件的接口
/**
* home模塊對(duì)外暴露功能
*/
public interface IHomeProvider extends IProvider {
void goHome(Context context);
}
在home組件中實(shí)現(xiàn)該接口
@Route(path = RouterHelper.ROUTER_HOME_PROVIDER)
public class HomeProviderImpl implements IHomeProvider {
@Override
public void init(Context context) {
Log.d("HomeProviderImpl", "home初始化");
}
@Override
public void goHome(Context context) {
Intent intent = new Intent(context, HomeActivity.class);
context.startActivity(intent);
}
}
跳轉(zhuǎn)
((IHomeProvider) RouterHelper.getModule(RouterHelper.ROUTER_HOME_PROVIDER)).goHome(MainActivity.this);
組件通信
組件間都是相互獨(dú)立的,他們之間不存在任何依賴.沒有依賴,就無法產(chǎn)生關(guān)系,沒有關(guān)系就無法傳遞任何的信息.這個(gè)時(shí)候我們需要依賴第三方協(xié)助我們,也就是業(yè)務(wù)基礎(chǔ)庫,所有的業(yè)務(wù)組件都依賴該庫,通過它來進(jìn)行通信.
我們可以采用事件總線的方式,例如EventBus,將在業(yè)務(wù)基礎(chǔ)庫中定義需要傳遞的消息對(duì)象,在業(yè)務(wù)組件中訂閱該消息,通過這種方式我們就可以實(shí)現(xiàn)不同組件中的消息通信,當(dāng)然我們同樣也可以在上面提到的Provider方式,不過我覺得這兩種方式?jīng)]啥區(qū)別
注意點(diǎn)
組件化過程中還是有許多需要注意的地方,這里主要說三個(gè)地方
1. 依賴沖突
每個(gè)組件都可能引入自己的庫,這樣合并的時(shí)候就會(huì)產(chǎn)生沖突,解決這個(gè)沖突的方式就是統(tǒng)一依賴管理,后面會(huì)給出詳細(xì)配置
2. 不要使用butterknife
相信很多人都喜歡使用這個(gè)庫,但是組件化中最好還是不要使用了,雖然它提供了R2這種方式解決了lib中的id問題,但是每次單獨(dú)運(yùn)行的時(shí)候得手動(dòng)改成R,個(gè)人覺得有點(diǎn)麻煩,再說findviewbyid也花不了多少時(shí)間,一個(gè)插件的就搞定的事,也不容易出問題
3. 資源沖突
如果A和B組件中都有同一個(gè)名字的資源文件,在集成模式的時(shí)候打包就會(huì)報(bào)錯(cuò),可以使用在gradle中配置resourcePrefix 來解決
//設(shè)置了resourcePrefix值后,所有的資源名必須以指定的字符串做前綴,否則會(huì)報(bào)錯(cuò)。
//但是resourcePrefix這個(gè)值只能限定xml里面的資源,并不能限定圖片資源,所有圖片資源仍然需要手動(dòng)去修改資源名。
resourcePrefix "模塊名"
當(dāng)然也可以團(tuán)隊(duì)約定好代碼規(guī)范,那樣更好
詳細(xì)配置
version.gradle
ext {
isHomeDebug = true
isModuleADebug = true
android = [
applicationId : "com.hc.moduledemo",
compileSdkVersion: 27,
minSdkVersion : 19,
targetSdkVersion : 22,
versionCode : 1,
versionName : "1.0",
supportVersion : "27.1.1"
]
dependencies = [
"arouter" : ["arouter-api" : "com.alibaba:arouter-api:1.4.1",
"arouter-compiler": "com.alibaba:arouter-compiler:1.2.2"],
"appcompat-v7": "com.android.support:appcompat-v7:${android.supportVersion}"
]
}
config.gradle
//所有業(yè)務(wù)模塊通用的一些配置
ext {
//設(shè)置App配置
setAppDefaultConfig = {
extension ->
extension.apply plugin: 'com.android.application'
extension.description "app"
setAndroidConfig extension.android
setDependencies extension.dependencies
extension.android.defaultConfig {//設(shè)置applicationId
applicationId rootProject.ext.android.applicationId + "." + extension.getName()
}
}
//設(shè)置Lib配置
setLibDefaultConfig = {
extension ->
extension.apply plugin: 'com.android.library'
extension.description "lib"
setAndroidConfig extension.android
setDependencies extension.dependencies
}
//設(shè)置Android配置
setAndroidConfig = {
extension ->
extension.compileSdkVersion rootProject.ext.android.compileSdkVersion
extension.defaultConfig {
minSdkVersion rootProject.ext.android.minSdkVersion
targetSdkVersion rootProject.ext.android.targetSdkVersion
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
javaCompileOptions {
annotationProcessorOptions {
arguments = [AROUTER_MODULE_NAME: extension.project.getName()]
}
}
}
}
//設(shè)置依賴
setDependencies = {
extension ->
extension.implementation fileTree(dir: 'libs', include: ['*.jar'])
extension.testImplementation 'junit:junit:4.12'
extension.androidTestImplementation 'com.android.support.test:runner:1.0.2'
extension.androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
extension.annotationProcessor rootProject.ext.dependencies["arouter"]["arouter-compiler"]
}
}