本篇文章已受權微信公衆號 hongyangAndroid (鴻洋)獨家發佈java
當一個項目通過N手人開發,N個產品經理的蹂躪,N長時間的維護,此時必定存在大量代碼冗餘、業務耦合、項目臃腫,資源文件大把重複等等,不堪重負。當須要增長新功能或者修改以前某個功能的時候,我相信不少同仁都說只敢增長,不敢隨意的去刪除、修改原有的代碼,由於不知道哪些有用,哪些沒有用。不但增長了維護成本,也在無形中增長了APK的體積,浪費了資源。 在此背景下,就衍生除了模塊化、組件化的概念。目前也已經有不少優秀的案例,我就踩在巨人的肩膀上搭建了符合組件業務的組件化框架。android
其基本理念就是,把經常使用的功能、控件、基礎類、第三方庫、權限等公共部分抽離封裝,把業務拆分紅N個模塊進行獨立(module)的管理,而全部的業務組件都依賴於封裝的基礎庫,業務組件之間不作依賴,這樣的目的是爲了讓每一個業務模塊能單獨運行。而在APP層對整個項目的模塊進行組裝,拼湊成一個完整的APP。藉助路由(Arouter)來對各個業務組件之間的跳轉,經過消息(eventbus)來作各個業務模塊之間的通訊。 模塊化的好處:github
先來一張整個項目構思圖 數據庫
根據項目構思圖搭建的項目結構圖 json
app模塊
app殼沒有任何功能主要就是集成每一個業務組件,最終打包成一個完整的APK app殼的gradle
作以下配置,根據配置文件中的isModule
字段來依賴不一樣的業務組件... 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模塊
功能組件主要負責封裝公共部分,如第三方庫加載、網絡請求、數據存儲、自定義控件、各類工具類等。 爲了防止重複依賴問題,全部的第三方庫都放在該模塊加載,業務模塊不在作任何的第三方庫依賴,只作common_base庫的依賴便可。
common模塊不管在什麼狀況下都是以library
的形式存在,全部的業務組件都必須依賴於common 其結構以下:
gradle
中引入項目中使用的全部第三方庫,業務組件就不用再去逐一引入apply plugin: 'com.android.library' apply plugin: 'com.jakewharton.butterknife' ... dependencies { // 在項目中的libs中的全部的.jar結尾的文件,都是依賴 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 發佈/訂閱事件總線 api rootProject.ext.dependencies["eventbus"] //網絡 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"] } 複製代碼
library
的形式存在。在組件開發模式下它以application
的形式存在,能夠單獨獨立運行。 業務組件完整的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"] } 複製代碼
/** * 全局統一配置文件 */ ext { //true 每一個業務Module能夠單獨開發 //false 每一個業務Module以lib的方式運行 //修改以後須要Sync方可生效 isModule = false //版本號 versions = [ applicationId : "com.wss.amd", //應用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"]}", //方法數超過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"]}", //網絡 "novate" : "com.tamic.novate:novate:${versions["novateVersion"]}", //日誌 "logger" : "com.orhanobut:logger:${versions["loggerVersion"]}", //fastJson "fastjson" : "com.alibaba:fastjson:${versions["fastjsonVersion"]}.android", //沉浸式狀態欄 "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"]}", //數據庫 "greenDao" : "org.greenrobot:greendao:${versions["greendaoVersion"]}", ] } 複製代碼
最後別忘記在工程的中build.gradle
引入該配置文件api
apply from: "config.gradle" 複製代碼
修改isModule字段以後 須要Sysn纔會生效
bash
Application
、全局Context
、 Activity
管理問題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); } } 複製代碼
AndroidManifest
的管理咱們知道APP在打包的時候最後會把全部的AndroidManifest
進行合併,因此每一個業務組件的Activity
只須要在各自模塊的AndroidManifest
中註冊便可。若是業務組件須要獨立運行,則須要單獨配置一份AndroidManifest
,在gradle
的sourceSets
根據不一樣的模式加載不一樣的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 是小寫的
markdown
其中集成模式加載的Manifest
中不能設置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> 複製代碼
須要注意的是若是在組件開發模式下,組件的Applicaion
必須繼承自BaseApplicaion
業務組件之間沒有依賴,不能經過常規的Intent
顯示的進行跳轉,這個時候就須要引入路由的概念
利用阿里的ARouter對須要跳轉的頁面作配置 gradle
配置
android { ... defaultConfig { ... //Arouter路由配置 javaCompileOptions { annotationProcessorOptions { arguments = [AROUTER_MODULE_NAME: project.getName()] includeCompileClasspath = true } } } } dependencies{ ... //Arouter路由 annotationProcessor rootProject.ext.dependencies["arouter_compiler"] } 複製代碼
目標頁面配置
@Route(path = "/wan/WanMainActivity") public class WanMainActivity extends ActionBarActivity<WanMainPresenter> implements IWanMainView, OnRcyItemClickListener { ... } 複製代碼
跳轉
... ARouter.getInstance() .build("/wan/WanMainActivity") .navigation(); ... 複製代碼
能夠利用第三方 如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; } 複製代碼
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 } } } 複製代碼
目前沒有比較好的約束方式,只能經過設置資源的前綴來防止資源文件衝突,而後在提交代碼的時候對代碼進行檢查是否規範來控制。
最後放上Demo地址,共同窗習,有什麼很差的地方,歡迎你們指出!