模塊化(1):基本思路

来源:https://www.cnblogs.com/ganchuanpu/archive/2018/08/17/9493397.html
-Advertisement-
Play Games

一.什麼是模塊化 什麼是模塊化呢?有一種定義是:模塊化是一種處理複雜系統分解為更好的可管理模塊的方式。由此可見,模塊化思路下構成的複雜系統是由各個可管理的子模塊構成的,每個子模塊之前相互獨立,並通過某種特定的方式進行通信。在工業上面,有模塊化汽車的概念,也有模塊化手機的概念,各個模塊根據一定的標準進 ...


一.什麼是模塊化

  什麼是模塊化呢?有一種定義是:模塊化是一種處理複雜系統分解為更好的可管理模塊的方式。由此可見,模塊化思路下構成的複雜系統是由各個可管理的子模塊構成的,每個子模塊之前相互獨立,並通過某種特定的方式進行通信。
在工業上面,有模塊化汽車的概念,也有模塊化手機的概念,各個模塊根據一定的標準進行生產,生產之後可以直接進行各個模塊的組裝,某個模塊出現問題之後,可以單獨對這個模塊進行替換。舉個例子,同樣一款汽車,有各中配置不同的版本,比如發動機不同。這些發動機都按照一定的標準生產,但是發送的輸出和能耗並不同。重要的是其介面標準一樣。從可替換這一點來講,和軟體開發中的可插拔是異曲同工的。

Android 開發中有兩個比較相似的概念:組件化和模塊化,這裡需要進行區分的。

組件化:指的是單一的功能組件,如地圖組件、支付組件、路由組件(Router)等等;
模塊化:獨立的業務模塊,模塊相對於組件來講粒度更大。

模塊化的好處是顯而易見的。

• 多團隊並行開發測試;
• 模塊間解耦、重用;
• 可單獨編譯打包某一模塊,提升開發效率。

Android 插件化 ——指將一個程式劃分為不同的部分,比如一般 App的皮膚樣式就可以看成一個插件

Android 組件化 ——這個概念實際跟上面相差不那麼明顯,組件和插件較大的區別就是:組件是指通用及復用性較高的構件,比如圖片緩存就可以看成一個組件被多個 App共用

插件的方式只有三種:1,apk安裝,2,apk不安裝,3,dex包

 

二.模塊Debug和Release處理

  對於模塊化項目,每個單獨的 Business Module 都可以單獨編譯成 APK。在開發階段需要單獨打包編譯,項目發佈的時候又需要它作為項目的一個 Module 來整體編譯打包。簡單的說就是開發時是 Application,發佈時是 Library。因此需要在 Business Module 的 build.gradle 中加入如下代碼:

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 :

 1 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
 2    package="com.dajiazhongyi.dajia.pedumodule">
 3 
 4    <uses-permission android:name="android.permission.INTERNET" />
 5    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
 6    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
 7    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
 8    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
 9 
10    <application
11        ...
12        >
13 
14        <activity
15            android:name="com.dajiazhongyi.dajia.loginmodule.ui.DaJiaLauncher"
16            android:exported="true"
17            android:screenOrientation="portrait">
18            <intent-filter>
19                <action android:name="android.intent.action.MAIN" />
20                <category android:name="android.intent.category.LAUNCHER" />
21            </intent-filter>
22            <intent-filter>
23                <action android:name="android.intent.action.VIEW" />
24 
25                <category android:name="android.intent.category.DEFAULT" />
26                <category android:name="android.intent.category.BROWSABLE" />
27 
28                <data android:scheme="dajia" />
29            </intent-filter>
30        </activity>
31 
32        <activity
33            android:name=".ui.MainActivity"
34            android:screenOrientation="portrait"/>
35 
36    </application>
37 
38 </manifest>

realease 模式下的 AndroidManifest.xml :

 1 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
 2    package="com.dajiazhongyi.dajia.pedumodule">
 3 
 4    <application
 5        android:allowBackup="true"
 6        android:supportsRtl="true">
 7 
 8        <activity
 9            android:name="com.dajiazhongyi.dajia.pedumodule.ui.PEducationListActivity"
10            android:screenOrientation="portrait"/>
11 
12        <activity
13            android:name="com.dajiazhongyi.dajia.pedumodule.ui.syslib.SystemEduDetailListActivity"
14            android:screenOrientation="portrait"/>
15 
16        <activity
17            android:name="com.dajiazhongyi.dajia.pedumodule.ui.syslib.SystemEduListActivity"
18            android:screenOrientation="portrait"/>
19 
20    </application>
21 
22 </manifest>

三.模塊化分層設計

合理的模塊化分層設計是非常重要的,就像一個房子一樣,合理的框架設計是成功的保證。
模塊化分層設計需要達到以下幾個目標:

  1. 模塊職責明確;

  2. 模塊代碼邊界清晰;

  3. 模塊通信

四.模塊職責明確

根據職責進行分層設計是合理有效的,以下是在項目實踐中採用的分層設計。

①.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層來實現。

五.代碼邊界

合理的代碼邊界約定可以保證層次的清晰、避免架構變得冗餘,雖然沒法完全保證,畢竟定期的重構是無法避免的。

①各個業務模塊之間無依賴關係,模塊之間頁面的跳轉通過ARouter等頁面路由協議進行;

②模塊之間的事件通信採用EventBus,並依賴於BaseCore層的事件Manager進行管理;

③模塊之間的功能暴露全部通過介面,介面需要下沉到BaseCore層,介面使用前必須先註冊,調用方式形如下,後續文章會詳細介紹:

ServiceManager.regist(PluginService.class); 
ServiceManager.get(PluginService.class).execute();

④組件庫組件必須提供個性化定製,方便業務模塊使用;

⑤合理控制各組件和各業務模塊的拆分粒度,太小的公有模塊不足以構成單獨組件或者模塊的,我們先放到類似於 CommonModule 的組件中,在後期不斷的重構迭代中視情況進行進一步的拆分;

⑥上層的公有業務或者功能模塊可以逐步下放到下層,下放過程中按照層次職責歸類下放;

⑦各個模塊之間的橫向依賴關係,比如在使用PluginService2之前,需要先註冊PluginService1,這種依賴管理後續會詳細介紹

 

六.模塊通信

模塊通信需要解決三大問題:

  1. 頁面跳轉

  2. 事件通知

  3. 介面調用

頁面跳轉

這裡介紹一款頁面路由神器:ARouter https://github.com/alibaba/ARouter

本著能用、夠用、好用的原則,這款神器支持以下功能:

  1. 支持直接解析標準URL進行跳轉,並自動註入參數到目標頁面中
  2. 支持多模塊工程使用
  3. 支持添加多個攔截器,自定義攔截順序
  4. 支持依賴註入,可單獨作為依賴註入框架使用
  5. 支持InstantRun
  6. 支持MultiDex(Google方案)
  7. 映射關係按組分類、多級管理,按需初始化
  8. 支持用戶指定全局降級與局部降級策略
  9. 頁面、攔截器、服務等組件均自動註冊到框架
  10. 支持多種方式配置轉場動畫
  11. 支持獲取Fragment
  12. 完全支持Kotlin以及混編(配置見文末 其他#5)

其調用方式如下:

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>

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • redis cluster + sentinel詳細過程和錯誤處理三主三備三哨兵1、基本架構192.168.70.215 7001 Master + sentinel 27001192.168.70.216 7002 Master + sentinel 27002192.168.70.217 700 ...
  • 抓到視窗期,分享紅利,所有為快不破!!! 當年學習移動互聯網的程式員現在年薪都50萬了,抓住機遇創業的都成功了 如今會多種主流後端技術的複合型人才已成為市場標配,這就是Java大數據 Java開發、大數據 人才缺口達到20萬以上,每年以20%的速度在增長 後端伺服器開發最流行的是Java開發,而開發 ...
  • 一. 查詢緩存 1.開啟緩存 設置了緩存開啟,緩存最大限制128M,重啟服務後,再次查詢 2 測試緩存 現在是緩存2次,命中一次 上面是二個查詢sql語句,此時緩存數是4,如下圖所示: 此時緩存數是6,說明緩存區分where條件值的大小寫。同樣也會區分sql關鍵詞的大小寫。如下圖所示: 設置好que ...
  • 什麼是SYS_OP_C2C呢?官方的介紹如下: SYS_OP_C2C is an internal function which does an implicit conversion of varchar2 to national character set using TO_NCHAR func... ...
  • " " 思維導圖可在 "幕布" 找到 1. 基礎 如果在相對佈局里,控制項沒有指明相對位置,則預設都是在相對佈局的左上角: gravity 屬性用來設置容器內組件的對齊方式 效果為 2. 根據兄弟控制項定位 2.1 相對兄弟組件的位置 代碼示例 等屬性通過制定控制項的 來選擇需要參考的兄弟組件,即 : 顯 ...
  • Xutils這個框架非常全面,可以進行網路請求,可以進行圖片載入處理,可以數據儲存,還可以對view進行註解,使用這個框架非常方便,但是缺點也是非常明顯的,使用這個項目,會導致項目對這個框架依賴非常的嚴重,一旦這個框架出現問題,那麼對項目來說影響非常大的。、 OKhttp:Android開發中是可以 ...
  • 系統架構分析 體繫結構 安卓結構有四大層,五個部分, 分四層為: 應用層 ,應用框架層 ,系統運行層 和`Linux`內核層。 那麼我來講講應用層有什麼? 就是一些應用軟體,如首頁,聯繫人,電話,瀏覽器等等;應用框架如何理解? 應用框架層是用 寫的,有事件管理器, 管理器,內容提供,查看系統 ,消息 ...
  • 所謂“工欲善其事,必先利其器”。Android Studio 是谷歌推出一個Android集成開發工具,基於IntelliJ IDEA. 類似 Eclipse ADT,Android Studio 提供了集成的 Android 開發工具用於開發和調試。 在IDEA的基礎上,Android Studi ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...