隨着移動平臺的不斷髮展,軟件慢慢變的愈來愈複雜,業務繁多,體積臃腫;爲了下降大型軟件複雜性和耦合度,同時也爲了適應模塊重用、多團隊並行開發測試等等需求,Android社區提出了兩種解決方案:模塊化和插件化。插件化暫且按下不提,本文主要講述模塊化。從基本思路上來說,模塊化的實現大致上來說都是差很少的,本文將着重講述基本思路。此外,在實踐的過程當中也有特別的地方:Databinding在模塊化中的坑,Dagger2在模塊化中的應用,頁面統一跳轉,模塊化通訊方式設計,模塊層級架構設計等。這些問題將在本文和後面的系列文章中一一介紹。android
什麼是模塊化呢?有一種定義是:模塊化是一種處理複雜系統分解爲更好的可管理模塊的方式。因而可知,模塊化思路下構成的複雜系統是由各個可管理的子模塊構成的,每一個子模塊以前相互獨立,並經過某種特定的方式進行通訊。 在工業上面,有模塊化汽車的概念,也有模塊化手機的概念,各個模塊根據必定的標準進行生產,生產以後能夠直接進行各個模塊的組裝,某個模塊出現問題以後,能夠單獨對這個模塊進行替換。舉個例子,一樣一款汽車,有各中配置不一樣的版本,好比發動機不一樣。這些發動機都按照必定的標準生產,可是發送的輸出和能耗並不一樣。重要的是其接口標準同樣。從可替換這一點來說,和軟件開發中的可插拔是殊途同歸的。git
Android 開發中有兩個比較類似的概念:組件化和模塊化,這裏須要進行區分的。 組件化:指的是單一的功能組件,如地圖組件、支付組件、路由組件(Router)等等; 模塊化:獨立的業務模塊,模塊相對於組件來說粒度更大。github
模塊化的好處是顯而易見的。 • 多團隊並行開發測試; • 模塊間解耦、重用; • 可單獨編譯打包某一模塊,提高開發效率。安全
對於模塊化項目,每一個單獨的 Business Module 均可以單獨編譯成 APK。在開發階段須要單獨打包編譯,項目發佈的時候又須要它做爲項目的一個 Module 來總體編譯打包。簡單的說就是開發時是 Application,發佈時是 Library。所以須要在 Business Module 的 build.gradle 中加入以下代碼:bash
if(isBuildModule.toBoolean()){
apply plugin: 'com.android.application'
}else{
apply plugin: 'com.android.library'
}
複製代碼
isBuildModule 在項目根目錄的 gradle.properties 中定義:微信
isBuildModule=false網絡
一樣 Manifest.xml 也須要有兩套:架構
sourceSets {
main {
if (isBuildModule.toBoolean()) {
manifest.srcFile 'src/main/debug/AndroidManifest.xml'
} else {
manifest.srcFile 'src/main/release/AndroidManifest.xml'
}
}
}
複製代碼
debug 模式下的 AndroidManifest.xml :app
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.dajiazhongyi.dajia.pedumodule">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<application
...
>
<activity
android:name="com.dajiazhongyi.dajia.loginmodule.ui.DaJiaLauncher"
android:exported="true"
android:screenOrientation="portrait">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="dajia" />
</intent-filter>
</activity>
<activity
android:name=".ui.MainActivity"
android:screenOrientation="portrait"/>
</application>
</manifest>
複製代碼
realease 模式下的 AndroidManifest.xml :框架
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.dajiazhongyi.dajia.pedumodule">
<application
android:allowBackup="true"
android:supportsRtl="true">
<activity
android:name="com.dajiazhongyi.dajia.pedumodule.ui.PEducationListActivity"
android:screenOrientation="portrait"/>
<activity
android:name="com.dajiazhongyi.dajia.pedumodule.ui.syslib.SystemEduDetailListActivity"
android:screenOrientation="portrait"/>
<activity
android:name="com.dajiazhongyi.dajia.pedumodule.ui.syslib.SystemEduListActivity"
android:screenOrientation="portrait"/>
</application>
</manifest>
複製代碼
合理的模塊化分層設計是很是重要的,就像一個房子同樣,合理的框架設計是成功的保證。 模塊化分層設計須要達到如下幾個目標:
根據職責進行分層設計是合理有效的,如下是在項目實踐中採用的分層設計。
SDK SDK層包括的內容如圖所示,須要強調的是並非全部的第三方Libraries都放到SDK,必須是通用的基礎級別的。
組件庫 咱們將各個業務模塊公用的組件整合到組件庫中,組件庫並不必定是一個module,它也能夠是多個module,實際使用的時候更多的被業務模塊依賴。
BaseCore 這是最重要的一個層級,APP核心的部分就是它,BaseCore能夠用通用的定義如下幾個部分:
CoreAccount: APP帳號管理,帳號登陸、註銷、Profile信息獲取等; CoreNetwork: 以Retrofit2爲例,CoreNetwork並不提供業務模塊的API,只是提供基礎的網絡狀態管理、網絡錯誤管理; CoreStorage: 處理SQLite、Preferences; CoreCommunication:模塊之間的通訊主要有三種:事件通知、頁面跳轉(Activity、Service)、接口調用。模塊通訊是最重要的層次,後面會重點講
此外,這個層次是最容易代碼越界的層次,隨着業務的不斷複雜,業務模塊中的代碼是極有可能下沉到BaseCore的,從而致使Core層代碼愈來愈冗餘。清晰合理的代碼邊界規範是重要的。
業務模塊 業務模塊的拆分粒度須要把控,過小的粒度並非很合理。其中App(Release)是最終發佈出去的版本,它是對其餘模塊1...N 的整合。各個業務模塊在debug'階段,能夠獨立打包成apk進行調試,在release階段,則做爲APP的module被引用。各個業務模塊之間不進行相互調用,它們之間的通訊經過BaseCore層來實現。
合理的代碼邊界約定能夠保證層次的清晰、避免架構變得冗餘,雖然無法徹底保證,畢竟按期的重構是沒法避免的。
ServiceManager.regist(PluginService.class);
ServiceManager.get(PluginService.class).execute();
複製代碼
模塊通訊須要解決三大問題:
這裏介紹一款頁面路由神器:ARouter github.com/alibaba/ARo…
本着能用、夠用、好用的原則,這款神器支持如下功能:
其調用方式以下:
1. 添加註解
@Route(path = "/test/activity")
public class YourActivity extend Activity {
...
}
2. 初始化SDK
if (isDebug()) { // 這兩行必須寫在init以前,不然這些配置在init過程當中將無效
ARouter.openLog(); // 打印日誌
ARouter.openDebug(); // 開啓調試模式(若是在InstantRun模式下運行,必須開啓調試模式!線上版本須要關閉,不然有安全風險)
}
ARouter.init(mApplication); // 儘量早,推薦在Application中初始化
3. 發起路由操做
// 1\. 應用內簡單的跳轉(經過URL跳轉在'進階用法'中)
ARouter.getInstance().build("/test/activity").navigation();
// 2\. 跳轉並攜帶參數
ARouter.getInstance().build("/test/1")
.withLong("key1", 666L)
.withString("key3", "888")
.withObject("key4", new Test("Jack", "Rose"))
.navigation();
複製代碼
實際應用中,在BaseCore中實現一個RouterManager,管理路由初始化,跳轉等事宜:
public class RouterManager {
/**
* Router Path
*/
public static final String URL_WELCOME = "/loginModule/welcome";
public static final String URL_LOGIN = "/loginModule/login";
public static final String URL_MAIN_LOGIN = "/loginModule/main";
public static final String URL_MAIN_PEDU = "/peduModule/main";
...
/**
* Module application name
*/
public static final String MODULE_LOGIN = "loginmodule";
public static final String MODULE_PEDU = "pedumodule";
public static void initRouter(Application application) {
if (BuildConfig.DEBUG) {
ARouter.openLog(); // 打印日誌
ARouter.openDebug(); // 開啓調試模式(若是在InstantRun模式下運行,必須開啓調試模式!線上版本須要關閉,不然有安全風險)
}
ARouter.init(application);
}
public static void gotoNewPage(Context context, String pageUrl) {
ARouter.getInstance().build(pageUrl).navigation();
}
public static void goWelcome(Context context) {
ARouter.getInstance().build(URL_WELCOME).navigation();
}
public static void goLogin(Context context) {
ARouter.getInstance().build(URL_LOGIN).navigation();
}
public static void goHome(Context context) {
String packageName = context.getApplicationInfo().packageName;
LogUtils.logD(packageName);
String suffix = packageName.substring(packageName.lastIndexOf(".") + 1);
switch (suffix) {
case MODULE_LOGIN:
ARouter.getInstance().build(URL_MAIN_LOGIN).navigation();
break;
case MODULE_PEDU:
ARouter.getInstance().build(URL_MAIN_PEDU).navigation();
break;
}
}
...
}
複製代碼
更多使用方法能夠參考github該庫的詳細介紹
因爲篇幅緣由,事件通知、接口調用將在後續文章中介紹!!
對於多個 Bussines Module 中資源名衝突的問題,能夠經過在 build.gradle 定義前綴的方式解決:
defaultConfig {
...
resourcePrefix "module_name_"
...
}
複製代碼
而對於 Module 中有些資源不想被外部訪問的,咱們能夠建立 res/values/public.xml,添加到 public.xml 中的 resource 則可被外部訪問,未添加的則視爲私有:
<resources>
<public name="module1_str" type="string"/>
</resources>
複製代碼
更多模塊化實踐經驗,請關注後續文章的推出!!歡迎你們一塊兒交流!!
原文地址: http://mp.weixin.qq.com/s/ktbIDjOUzM-gL0uEhowFBw
歡迎一塊兒學習和交流
若是你以爲此文對您有所幫助,歡迎入羣 QQ交流羣 :644196190 微信公衆號:終端研發部