基於Arouter實現的組件化方案說明

組件化思想架構圖

基於Arouter實現的組件化方案說明:

組件化背景

一個項目,隨着業務的發展,模塊會變的愈來愈多,代碼量也會變的異常龐大,進而可能開發的人會愈來愈多,這種狀況下若是仍是基於單一工程架構,那就須要每個開發者都熟悉全部的代碼,並且代碼之間耦合嚴重,一個模塊穿插着大量其餘業務模塊的邏輯,嚴重的話可能使項目處於牽一髮而動全身,不想輕易修改的局面;並且龐大的單一工程項目會致使編譯速度極慢,開發者長時間等待編譯結果,很是不利於開發工做。因此,就須要一個靈活的架構來解決這些問題,組件化架構思想應運而生。java

總體結構

  • common:基礎組件部分,與業務無關,須要全部組件共同依賴的部分,如:網絡請求封裝、圖片加載封裝、ui相關基類、工具集合等(固然這些內容能夠依據分層原則放在不一樣的基礎module中)
  • router-comp:路由驅動組件,承載整個項目的路由工做
  • comp1:業務組件1,如視頻組件,可獨立運行
  • comp2:業務組件2,如新聞組件,可獨立運行
  • comp3:業務組件3,如視頻組件,可獨立運行
  • app:殼工程,用於將各個組件組裝成一個完成app

組件化所面臨的問題:

  • 集成模式與組件模式轉換(熱插拔)
  • 組件之間頁面跳轉(路由)
  • 組件之間通訊、調用彼此服務
  • 打包混淆

組件化的實現

針對上面所說的幾個問題,下面咱們逐個說明它們的解決方案,當解決完這些問題,你會發現,你已經搭建了一個基於組件化的項目。下圖是一個完整的組件化項目結構:common是基礎組件module,做爲library存在,須要全部組件依賴;comp一、comp2做爲組件存在,可配置成library或可獨立運行的module;app是個殼,經過組裝組件實現其價值。android

項目總體結構

集成模式與組件模式轉換(熱插拔)

Android工程經過gradle構建,經過配置每一個module的gradle,來實現module的不一樣表現。Android Studio的module有兩種屬性,分別是:api

  • application屬性:可獨立運行,也就是咱們的app
  • library屬性:不可獨立運行,被app依賴的庫

module屬性經過其目錄下的gradle文件配置,當module屬性爲application時,該module做爲完整的app存在,能夠獨自運行,方便編譯和調試;當module屬性爲library時,該module做爲一個依賴庫,被殼工程依賴並組裝成一個app。那麼如何讓這兩種模式能夠自動轉換呢?若是每次切換模式的時候,都手動去修改每一個組件的配置,組件少的狀況下還能夠接受,組件多了會很是不方便,下面就讓咱們來聊聊如何實現兩種模式的自動轉換。網絡

  1. 首先,聲明全局配置變量,來標識module的屬性(app or library),如在工程目錄下的build.gradle文件中聲明布爾變量ext.isModule,true表明組件做爲可獨立運行的app,false表明組件做爲被依賴的library,以下所示
buildscript {
    ext.kotlin_version = '1.3.21'
    ext.isModule = true  //true-每一個組件都是單獨的module,可獨立運行  false-組件做爲library存在
    repositories {
        google()
        jcenter()
    }
   
}
複製代碼
  1. 配置組件的build.gradle文件
    
//1
if (rootProject.ext.isModule) {
    //可獨立運行的app
    apply plugin: 'com.android.application'
} else{
    //被依賴的library
    apply plugin: 'com.android.library'
}
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'

android {
    compileSdkVersion 28
    defaultConfig {
      
      //applicationId "com.study.comp1" //2 若是沒有,默認包名爲applicationId
        minSdkVersion 19
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_7
        targetCompatibility JavaVersion.VERSION_1_7
    }
    //3
    sourceSets {
        main {
            if(rootProject.ext.isModule){
                manifest.srcFile 'src/main/java/module/AndroidManifest.xml'
            } else{
                manifest.srcFile 'src/main/java/library/AndroidManifest.xml'
                java {//移除module包下的代碼
                    exclude 'module'
                }
            }
        }
    }
}
    複製代碼

上面是截取的組件gradle的部分代碼,包含了組件化須要配置的全部內容,每一點都進行了註釋架構

  • 註釋1:如上所述,根據isModule的值,來設置module的屬性,做爲app or library
  • 註釋2:當module屬性爲library時,不能設置applicationId;當爲app時,若是未設置applicationId,默認包名爲applicationId,因此爲了方便,此處不設置applicationId
  • 註釋3:Android Studio會爲每一個module生成對應的AndroidManifest.xml文件,聲明自身須要的權限、四大組件、數據等內容;當module屬性爲app時,其對應的AndroidManifest.xml須要具有完整app所須要的全部配置,尤爲是聲明Application和launch的Activity;當module屬性爲library時,若是每一個組件都聲明本身的Application和launch的Activity,那在合併的時候就會發生衝突,編譯也不會經過,因此,就須要爲當前module從新定義一個AndroidManifest.xml文件,不聲明Application和launch的Activity,而後根據isModule的值指定AndroidManifest.xml的路徑,所以就有了註釋3處的寫法。爲了不集成模式下的命名衝突,每一個文件都以自身module名爲前綴來命名會是一個很好的方法。下圖是該module的目錄

組件路徑

  1. 在須要切換module屬性的時候改變步驟1處聲明的變量值,而後從新編譯便可

組件之間頁面跳轉(路由)

在組件化架構中,不一樣的組件之間是平衡的,不存在相互依賴的關係(可參考文章開頭的架構圖)。所以,假設在組件A中,想要跳轉到組件B中的頁面,若是使用Intent顯式跳轉就行不通了,並且你們都知道,Intent隱式跳轉管理起來很是不方便,因此Arouter出現了,而且有強大的技術團隊支持,能夠放心使用了。那麼如何在組件化架構中應用Arouter呢?下面詳細來聊一聊app

  1. 依賴處理

在common組件中將Arouter依賴進來,並配置編譯參數;在業務組件中引入arouter編譯器插件,同時配置編譯器參數,下面是Common組件gradle文件的部分片斷ide

    
//配置arouter編譯器參數,每一個組件都需配置
kapt {
    arguments {
        arg("AROUTER_MODULE_NAME", project.getName())
    }
}

dependencies {
    //arouter api,只需在common組件中引入一次
    api('com.alibaba:arouter-api:1.4.1') {
        exclude group: 'com.android.support'
    }
    //arouter編譯器插件,每一個組件都需引入
    kapt 'com.alibaba:arouter-compiler:1.2.2'
}
複製代碼
  1. 初始化及編碼實現

在組件架構中,常常會遇到組件須要使用全局Context的狀況,當組件屬性爲app時,能夠經過自定義Application實現;當組件屬性爲library時,因爲組件被app依賴,致使沒法調用app的Application實例,並且自身不存在Application;因此,這裏給出的方案是在common組件中建立一個BaseApplication,而後讓集成模式(組件模式)下的Application繼承BaseApplication,在BaseApplication中獲取全局Context,並作一些初始化的工做,這裏須要初始化Arouter,以下是在common組件中聲明的BaseApplication工具

abstract class BaseApplication : Application() {

    companion object {
        var _context: Application? = null
        //獲取全局Context
        fun getContext(): Application {
            return _context!!
        }
    }
    override fun onCreate() {
        super.onCreate()
        _context = this
        //初始化Arouter
        initARouter()
        //初始化其餘第三方庫
    }
    private fun initARouter() {
        if (BuildConfig.DEBUG) {
            ARouter.openDebug()
            ARouter.openLog()
        }
        ARouter.init(this)
    }
    override fun onTerminate() {
        super.onTerminate()
        //清理Arouter註冊表
        ARouter.getInstance().destroy()
    }
}
複製代碼

根據Arouter的路由特性,初始化以後,就能夠經過@Route註解註冊頁面,而後調用Arouter api實現頁面的跳轉了(這裏所謂的跨組件頁面跳轉是指在集成模式下,而非組件模式下),無關乎是否在同一個組件下面, 假設咱們要從組件1頁面攜帶參數跳轉到組件2頁面,請看下面示例組件化

/**
 * 在組件2中經過@Route註解註冊該頁面
 */
@Route(path = "/comp2/msg",name = "我是組件2的MSGActivity")
class Comp2MsgActivity : BaseActivity() {
    //傳遞過來的參數
    @Autowired(name = "msg")
    @JvmField
    var msg: String? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        //注入傳遞的參數
        ARouter.getInstance().inject(this)
        setContentView(R.layout.comp2_activity_msg)
        comp2_msg_msg.text = msg!!
    }
}

//在組件1中發起跳轉命令
ARouter.getInstance()
                    .build("/comp2/msg")
                    .withString("msg", "hello Im from Comp1")
                    .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
                    .navigation()
複製代碼

以上便完成了一次簡單的跨越組件的頁面跳轉,僅僅是Arouter的基本使用而已。解決了組件間頁面跳轉的問題後,咱們來看看組件之間通訊、調用彼此服務的實現。gradle

組件之間通訊、調用彼此服務

組件間通訊功能和路由功能有着共通的地方,即都是利用Arouter的基礎功能實現,在Arouter驅動層定義各個組件對外提供的接口,而後在組件自身模塊實現該接口,經過Arouter調用其餘組件服務。假設咱們在組件2中須要調用組件1中的服務,能夠總結爲如下3點

  • 定義接口:在common組件中定義組件1對外提供的接口CompServer1,注意該接口類型爲Arouter模板類型IProvider
/**
 * 組件1對外提供的接口
 */
interface CompServer1 : IProvider {
    fun showMsg(msg: String)
}
複製代碼
  • 實現接口:在comm1中實現上面定義的接口,並經過@Route註解註冊
@Route(path = "/comp1/server",name = "comp1對外提供的服務")
class CompServer : CompServer1 {
    var mContext: Context? = null
    override fun showMsg(msg: String) {
        Toast.makeText(mContext,msg,Toast.LENGTH_SHORT).show()
    }
    override fun init(context: Context?) {
        this.mContext = context!!
    }
}
複製代碼
  • 調用服務:在完成組件1接口的定義和實現以後,在組件2中須要的地方調用該接口便可
val server1 = ARouter.getInstance().build("/comp1/server").navigation() as CompServer1
server1.showMsg("我從comp2吊起了comp1的接口")
複製代碼

有沒有感受很簡單??沒錯,就是這麼簡單,趕忙去用吧!哈哈

打包混淆

說到混淆,有人可能會疑惑,若是在各個組件中混淆可不能夠?不建議這樣混淆!!由於組件在集成模式下被gradle構建成了release類型的aar包,若是在組件中進行混淆,一旦代碼出現了bug,這個時候就很難根據日誌去追蹤bug產生的緣由,並且不一樣組件分別進行混淆很是不方便維護和修改,這也是不推薦在業務組件中配置buildType(構建類型)的緣由。

因此,組件化項目的代碼混淆放在集成模式下的app殼工程,各個業務組件不配置混淆。集成模式下在app殼工程.gradle文件的release構建模式下開啓混淆,其餘buildType配置和普通項目相同,混淆文件放在app殼工程下,各個組件的代碼混淆均放在該混淆文件中。

小結

以上,咱們已經逐一解決了組件化所面對的各個問題,至此,咱們已經搭建了一個簡單的組件化架構的項目,這一塊兒感受是在不知不覺中就實現了,並非很難哦!如今,咱們總結一下組件化的優點了

  • 解耦:將業務組件代碼90%與工程解耦,只因此是90%而非100%,是由於業務組件須要在common組件中聲明對外開放的接口,那有沒有什麼方式能夠作到徹底解耦呢?目前尚未發現更好的辦法。。。
  • 提升開發效率:依賴解耦這一優點,團隊成員能夠只專一於本身負責的組件,開發效率更高;並且,組件開發過程當中只需編譯自身的module,這樣大大縮短了編譯時長,避免了漫長的等待編譯局面。
  • 結構清晰:在業務組件明確拆分的前提下,項目結構變的異常清晰,很是方便全局掌控。
相關文章
相關標籤/搜索