本篇文章已授權(quán)微信公眾號 hongyangAndroid (鴻洋)獨家發(fā)布
背景
當(dāng)一個項目經(jīng)過N手人開發(fā),N個產(chǎn)品經(jīng)理的蹂躪,N長時間的維護,此時一定存在大量代碼冗余、業(yè)務(wù)耦合抖坪、項目臃腫,資源文件大把重復(fù)等等闷叉,不堪重負擦俐。當(dāng)需要增加新功能或者修改之前某個功能的時候,我相信很多同仁都說只敢增加握侧,不敢隨意的去刪除蚯瞧、修改原有的代碼,因為不知道哪些有用品擎,哪些沒有用埋合。不但增加了維護成本,也在無形中增加了APK的體積萄传,浪費了資源甚颂。
在此背景下,就衍生除了模塊化秀菱、組件化的概念振诬。目前也已經(jīng)有很多優(yōu)秀的案例,我就踩在巨人的肩膀上搭建了符合組件業(yè)務(wù)的組件化框架衍菱。
一.淺談模塊
其基本理念就是,把常用的功能脊串、控件辫呻、基礎(chǔ)類、第三方庫洪规、權(quán)限等公共部分抽離封裝印屁,把業(yè)務(wù)拆分成N個模塊進行獨立(module)的管理,而所有的業(yè)務(wù)組件都依賴于封裝的基礎(chǔ)庫斩例,業(yè)務(wù)組件之間不做依賴,這樣的目的是為了讓每個業(yè)務(wù)模塊能單獨運行从橘。而在APP層對整個項目的模塊進行組裝念赶,拼湊成一個完整的APP础钠。借助路由(Arouter)來對各個業(yè)務(wù)組件之間的跳轉(zhuǎn),通過消息(eventbus)來做各個業(yè)務(wù)模塊之間的通信叉谜。
模塊化的好處:
- 1.解耦 只要封裝做得好旗吁,實際開發(fā)中會省去大量的重復(fù)代碼的coding。
- 2.結(jié)構(gòu)清晰停局、層次明顯很钓,對后面的維護也是極其容易。
- 3.每個業(yè)務(wù)模塊可獨立運行董栽,單獨提測码倦,節(jié)省開發(fā)時間。
二.基礎(chǔ)搭建
先來一張整個項目構(gòu)思圖
根據(jù)項目構(gòu)思圖搭建的項目結(jié)構(gòu)圖
下面逐一介紹每個模塊的功:
-
app模塊
app殼沒有任何功能主要就是集成每個業(yè)務(wù)組件锭碳,最終打包成一個完整的APK
app殼的gradle
做如下配置袁稽,根據(jù)配置文件中的isModule
字段來依賴不同的業(yè)務(wù)組件
...
dependencise{
//公用依賴包
implementation project(':common_base')
if (!Boolean.valueOf(rootProject.ext.isModule)) {
//main模塊
implementation project(':module_main')
implementation project(':module_market')
implementation project(':module_wan_android')
}
}
...
-
common_base模塊
功能組件主要負責(zé)封裝公共部分,如第三方庫加載擒抛、網(wǎng)絡(luò)請求推汽、數(shù)據(jù)存儲、自定義控件歧沪、各種工具類等歹撒。
為了防止重復(fù)依賴問題,所有的第三方庫都放在該模塊加載诊胞,業(yè)務(wù)模塊不在做任何的第三方庫依賴暖夭,只做common_base庫的依賴即可。
common模塊無論在什么情況下都是以library
的形式存在厢钧,所有的業(yè)務(wù)組件都必須依賴于common
其結(jié)構(gòu)如下:
common_base結(jié)構(gòu)圖.png
在commong的gradle
中引入項目中使用的所有第三方庫鳞尔,業(yè)務(wù)組件就不用再去逐一引入
apply plugin: 'com.android.library'
apply plugin: 'com.jakewharton.butterknife'
...
dependencies {
// 在項目中的libs中的所有的.jar結(jié)尾的文件,都是依賴
implementation fileTree(dir: 'libs', include: ['*.jar'])
//把implementation 用api代替,它是對外部公開的, 所有其他的module就不需要添加該依賴
api rootProject.ext.dependencies["appcompat_v7"]
api rootProject.ext.dependencies["constraint_layout"]
api rootProject.ext.dependencies["cardview-v7"]
api rootProject.ext.dependencies["recyclerview-v7"]
api rootProject.ext.dependencies["support-v4"]
api rootProject.ext.dependencies["design"]
api rootProject.ext.dependencies["support_annotations"]
//MultiDex分包方法
api rootProject.ext.dependencies["multidex"]
//黃油刀
annotationProcessor rootProject.ext.dependencies["butterknife_compiler"]
api rootProject.ext.dependencies["butterknife"]
//Arouter路由
annotationProcessor rootProject.ext.dependencies["arouter_compiler"]
api rootProject.ext.dependencies["arouter_api"]
api rootProject.ext.dependencies["arouter_annotation"]
//eventbus 發(fā)布/訂閱事件總線
api rootProject.ext.dependencies["eventbus"]
//網(wǎng)絡(luò)
api rootProject.ext.dependencies["novate"]
//日志
api rootProject.ext.dependencies["logger"]
//fastJson
api rootProject.ext.dependencies["fastjson"]
//沉浸欄
api rootProject.ext.dependencies["barlibrary"]
//banner
api rootProject.ext.dependencies["banner"]
//圖片加載
api rootProject.ext.dependencies["picasso"]
//lombok
api rootProject.ext.dependencies["lombok"]
api rootProject.ext.dependencies["lombokJavax"]
}
- 業(yè)務(wù)組件早直,在集成模式下它以
library
的形式存在寥假。在組件開發(fā)模式下它以application
的形式存在,可以單獨獨立運行霞扬。
業(yè)務(wù)組件完整的gradle
如下:
if (Boolean.valueOf(rootProject.ext.isModule)) {
apply plugin: 'com.android.application'
} else {
apply plugin: 'com.android.library'
}
apply plugin: 'com.jakewharton.butterknife'
...
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
//公用依賴包
implementation project(':common_base')
//Arouter路由
annotationProcessor rootProject.ext.dependencies["arouter_compiler"]
//黃油刀
annotationProcessor rootProject.ext.dependencies["butterknife_compiler"]
}
- 配置文件糕韧,對項目中的第三庫、app的版本等配置
/**
* 全局統(tǒng)一配置文件
*/
ext {
//true 每個業(yè)務(wù)Module可以單獨開發(fā)
//false 每個業(yè)務(wù)Module以lib的方式運行
//修改之后需要Sync方可生效
isModule = false
//版本號
versions = [
applicationId : "com.wss.amd", //應(yīng)用ID
versionCode : 1, //版本號
versionName : "1.0.0", //版本名稱
compileSdkVersion : 27,
buildToolsVersion : "27.0.3",
minSdkVersion : 17,
targetSdkVersion : 23,
androidSupportSdkVersion: "27.1.1",
constraintLayoutVersion : "1.1.1",
runnerVersion : "1.0.1",
espressoVersion : "3.0.1",
junitVersion : "4.12",
annotationsVersion : "24.0.0",
multidexVersion : "1.0.2",
butterknifeVersion : "8.4.0",
arouterApiVersion : "1.4.0",
arouterCompilerVersion : "1.2.1",
arouterannotationVersion: "1.0.4",
eventbusVersion : "3.0.0",
novateVersion : "1.5.5",
loggerVersion : "2.2.0",
fastjsonVersion : "1.1.54",
barlibraryVersion : "2.3.0",
picassoVersion : "2.71828",
bannerVersion : "1.4.10",
javaxVersion : "1.2",
lombokVersion : "1.16.6",
greendaoVersion : "3.2.2",
]
dependencies = ["appcompat_v7" : "com.android.support:appcompat-v7:${versions["androidSupportSdkVersion"]}",
"constraint_layout" : "com.android.support.constraint:constraint-layout:${versions["constraintLayoutVersion"]}",
"runner" : "com.android.support.test:runner:${versions["runnerVersion"]}",
"espresso_core" : "com.android.support.test.espresso:espresso-core:${versions["espressoVersion"]}",
"junit" : "junit:junit:${versions["junitVersion"]}",
"support_annotations" : "com.android.support:support-annotations:${versions["annotationsVersion"]}",
"design" : "com.android.support:design:${versions["androidSupportSdkVersion"]}",
"support-v4" : "com.android.support:support-v4:${versions["androidSupportSdkVersion"]}",
"cardview-v7" : "com.android.support:cardview-v7:${versions["androidSupportSdkVersion"]}",
"recyclerview-v7" : "com.android.support:recyclerview-v7:${versions["androidSupportSdkVersion"]}",
//方法數(shù)超過65535解決方法64K MultiDex分包方法
"multidex" : "com.android.support:multidex:${versions["multidexVersion"]}",
//路由
"arouter_api" : "com.alibaba:arouter-api:${versions["arouterApiVersion"]}",
"arouter_compiler" : "com.alibaba:arouter-compiler:${versions["arouterCompilerVersion"]}",
"arouter_annotation" : "com.alibaba:arouter-annotation:${versions["arouterannotationVersion"]}",
//黃油刀
"butterknife_compiler": "com.jakewharton:butterknife-compiler:${versions["butterknifeVersion"]}",
"butterknife" : "com.jakewharton:butterknife:${versions["butterknifeVersion"]}",
//事件訂閱
"eventbus" : "org.greenrobot:eventbus:${versions["eventbusVersion"]}",
//網(wǎng)絡(luò)
"novate" : "com.tamic.novate:novate:${versions["novateVersion"]}",
//日志
"logger" : "com.orhanobut:logger:${versions["loggerVersion"]}",
//fastJson
"fastjson" : "com.alibaba:fastjson:${versions["fastjsonVersion"]}.android",
//沉浸式狀態(tài)欄
"barlibrary" : "com.gyf.barlibrary:barlibrary:${versions["barlibraryVersion"]}",
//banner
"banner" : "com.youth.banner:banner:${versions["bannerVersion"]}",
//圖片加載
"picasso" : "com.squareup.picasso:picasso:${versions["picassoVersion"]}",
//lombok
"lombokJavax" : "javax.annotation:javax.annotation-api:${versions["javaxVersion"]}",
"lombok" : "org.projectlombok:lombok:${versions["lombokVersion"]}",
//數(shù)據(jù)庫
"greenDao" : "org.greenrobot:greendao:${versions["greendaoVersion"]}",
]
}
最后別忘記在工程的中build.gradle
引入該配置文件
apply from: "config.gradle"
修改isModule字段之后 需要Sysn才會生效
三.搭建過程中遇到的問題
1.Application
喻圃、全局Context
顶瞳、 Activity
管理問題
- 在功能組件即Demo中的
common_base
封裝BaseApplication
,在BaseApplication
對第三方庫初始化盛正、全局Context
的獲取等操作关摇。在BaseActivity
中對Activity
進行添加和移除的管理
//BaseApplicion
public class BaseApplication extends Application {
...
//全局唯一的context
private static BaseApplication application;
//Activity管理器
private ActivityManage activityManage;
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
application = this;
//MultiDex分包方法 必須最先初始化
MultiDex.install(this);
}
public void onCreate() {
super.onCreate();
activityManage = new ActivityManage();
initARouter();
initLogger();
}
/**
* 獲取全局唯一上下文
*
* @return BaseApplication
*/
public static BaseApplication getApplication() {
return application;
}
}
//BaseActivity
public abstract class BaseActivity extends Activity {
...
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//加入Activity管理器
BaseApplication.getApplication().getActivityManage().addActivity(this);
}
@Override
protected void onDestroy() {
super.onDestroy();
//將Activity從管理器移除
BaseApplication.getApplication().getActivityManage().removeActivityty(this);
}
}
2.AndroidManifest
的管理
我們知道APP在打包的時候最后會把所有的AndroidManifest
進行合并,所以每個業(yè)務(wù)組件的Activity
只需要在各自模塊的AndroidManifest
中注冊即可。如果業(yè)務(wù)組件需要獨立運行愚墓,則需要單獨配置一份AndroidManifest
予权,在gradle
的sourceSets
根據(jù)不同的模式加載不同的AndroidManifest
文件。
gradle
配置
...
android {
...
sourceSets {
main {
if (Boolean.valueOf(rootProject.ext.isModule)) {
manifest.srcFile 'src/main/module/AndroidManifest.xml'
} else {
manifest.srcFile 'src/main/AndroidManifest.xml'
java {
//排除java/debug文件夾下的所有文件
exclude '*module'
}
}
}
}
}
...
注意:在配置Gradle的時候 manifest.srcFile... manifest 是小寫的
其中集成模式加載的Manifest
中不能設(shè)置Application
和程序入口:
//集成模式下Manifest
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.wss.module.wan">
<application>
<activity android:name=".main.WanMainActivity" />
</application>
</manifest>
//組件模式下Manifest
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.wss.module.wan">
<application
android:name=".common.WanApplication"
android:allowBackup="true"
android:label="@string/app_name"
android:theme="@style/AdmTheme">
<activity android:name=".main.WanMainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
需要注意的是如果在組件開發(fā)模式下浪册,組件的Applicaion
必須繼承自BaseApplicaion
3.不同組件之間的跳轉(zhuǎn)
業(yè)務(wù)組件之間沒有依賴扫腺,不能通過常規(guī)的Intent
顯示的進行跳轉(zhuǎn),這個時候就需要引入路由的概念
利用阿里的ARouter對需要跳轉(zhuǎn)的頁面做配置
gradle
配置
android {
...
defaultConfig {
...
//Arouter路由配置
javaCompileOptions {
annotationProcessorOptions {
arguments = [AROUTER_MODULE_NAME: project.getName()]
includeCompileClasspath = true
}
}
}
}
dependencies{
...
//Arouter路由
annotationProcessor rootProject.ext.dependencies["arouter_compiler"]
}
目標(biāo)頁面配置
@Route(path = "/wan/WanMainActivity")
public class WanMainActivity extends ActionBarActivity<WanMainPresenter> implements IWanMainView, OnRcyItemClickListener {
...
}
跳轉(zhuǎn)
...
ARouter.getInstance()
.build("/wan/WanMainActivity")
.navigation();
...
4.不同組件之間通信
可以利用第三方 如EventBus對消息進行管理村象。在common_base
組件中的Base
類做了對消息的簡單封裝笆环,子類只需要重寫regEvent()
返回true
即可對事件的注冊,重寫onEventBus(Object)
即可對事件的接收厚者。
public abstract class BaseActivity extends Activity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
if (regEvent()) {
EventBus.getDefault().register(this);
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (regEvent()) {
EventBus.getDefault().unregister(this);
}
}
/**
* 子類接收事件 重寫該方法
*/
@Subscribe(threadMode = ThreadMode.MAIN)
public void onEventBus(Object event) {
}
/**
* 需要接收事件 重寫該方法 并返回true
*/
protected boolean regEvent() {
return false;
}
5.butterknife
的問題
在library
中使用butterknife
會存在找不到的問題躁劣。
推薦使用8.4.0
版本,用R2
代替R
,onClick
中使用if else
不要使用switch case
即可解決問題 籍救。
public class HomeFragment extends BaseMvpFragment<HomePresenter> implements IHomeView, OnRcyItemClickListener {
@BindView(R2.id.banner)
Banner banner;
@BindView(R2.id.recycle_view)
RecyclerView recyclerView;
...
@OnClick({R2.id.tv_title, R2.id.btn_open})
public void onClick(View v) {
if (v.getId() == R.id.tv_title) {
//do something
} else if (v.getId() == R.id.btn_open) {
//do something
}
}
}
6.資源文件沖突問題
目前沒有比較好的約束方式习绢,只能通過設(shè)置資源的前綴來防止資源文件沖突,然后在提交代碼的時候?qū)Υa進行檢查是否規(guī)范來控制蝙昙。
最后放上Demo地址闪萄,共同學(xué)習(xí),有什么不好的地方奇颠,歡迎大家指出败去!
參考文獻
移動架構(gòu)這么多,如何一次搞定所有
戲說移動江湖開發(fā)歷程
模塊化烈拒,組件化傻傻分不清圆裕?附帶組件化福利
寄Android開發(fā)Gradle你需要知道的知識
解決組件化開發(fā)butterknife 在 library中使用的坑`