基于Arouter實現(xiàn)的組件化方案說明

image.png

基于Arouter實現(xiàn)的組件化方案說明:

基于Arouter實現(xiàn)的組件化方案說明:

一個項目,隨著業(yè)務(wù)的發(fā)展,模塊會變的越來越多,代碼量也會變的異常龐大,進而可能開發(fā)的人會越來越多,這種情況下如果還是基于單一工程架構(gòu),那就需要每一個開發(fā)者都熟悉所有的代碼,而且代碼之間耦合嚴重,一個模塊穿插著大量其他業(yè)務(wù)模塊的邏輯,嚴重的話可能使項目處于牽一發(fā)而動全身,不想輕易修改的局面;而且龐大的單一工程項目會導(dǎo)致編譯速度極慢,開發(fā)者長時間等待編譯結(jié)果,非常不利于開發(fā)工作。所以,就需要一個靈活的架構(gòu)來解決這些問題,組件化架構(gòu)思想應(yīng)運而生。

整體結(jié)構(gòu)

  • common:基礎(chǔ)組件部分,與業(yè)務(wù)無關(guān),需要所有組件共同依賴的部分,如:網(wǎng)絡(luò)請求封裝、圖片加載封裝、ui相關(guān)基類、工具集合等(當然這些內(nèi)容可以依據(jù)分層原則放在不同的基礎(chǔ)module中)
  • router-comp:路由驅(qū)動組件,承載整個項目的路由工作
  • comp1:業(yè)務(wù)組件1,如視頻組件,可獨立運行
  • comp2:業(yè)務(wù)組件2,如新聞組件,可獨立運行
  • comp3:業(yè)務(wù)組件3,如視頻組件,可獨立運行
  • app:殼工程,用于將各個組件組裝成一個完成app

組件化所面臨的問題:

  • 集成模式與組件模式轉(zhuǎn)換(熱插拔)
  • 組件之間頁面跳轉(zhuǎn)(路由)
  • 組件之間通信、調(diào)用彼此服務(wù)
  • 打包混淆

組件化的實現(xiàn)

針對上面所說的幾個問題,下面我們逐個說明它們的解決方案,當解決完這些問題,你會發(fā)現(xiàn),你已經(jīng)搭建了一個基于組件化的項目。下圖是一個完整的組件化項目結(jié)構(gòu):common是基礎(chǔ)組件module,作為library存在,需要所有組件依賴;comp1、comp2作為組件存在,可配置成library或可獨立運行的module;app是個殼,通過組裝組件實現(xiàn)其價值。

在這里插入圖片描述

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

Android工程通過gradle構(gòu)建,通過配置每個module的gradle,來實現(xiàn)module的不同表現(xiàn)。Android Studio的module有兩種屬性,分別是:

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

module屬性通過其目錄下的gradle文件配置,當module屬性為application時,該module作為完整的app存在,可以獨自運行,方便編譯和調(diào)試;當module屬性為library時,該module作為一個依賴庫,被殼工程依賴并組裝成一個app。那么如何讓這兩種模式可以自動轉(zhuǎn)換呢?如果每次切換模式的時候,都手動去修改每個組件的配置,組件少的情況下還可以接受,組件多了會非常不方便,下面就讓我們來聊聊如何實現(xiàn)兩種模式的自動轉(zhuǎn)換。

  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的部分代碼,包含了組件化需要配置的所有內(nèi)容,每一點都進行了注釋

  • 注釋1:如上所述,根據(jù)isModule的值,來設(shè)置module的屬性,作為app or library
  • 注釋2:當module屬性為library時,不能設(shè)置applicationId;當為app時,如果未設(shè)置applicationId,默認包名為applicationId,所以為了方便,此處不設(shè)置applicationId
  • 注釋3:Android Studio會為每個module生成對應(yīng)的AndroidManifest.xml文件,聲明自身需要的權(quán)限、四大組件、數(shù)據(jù)等內(nèi)容;當module屬性為app時,其對應(yīng)的AndroidManifest.xml需要具備完整app所需要的所有配置,尤其是聲明Application和launch的Activity;當module屬性為library時,如果每個組件都聲明自己的Application和launch的Activity,那在合并的時候就會發(fā)生沖突,編譯也不會通過,所以,就需要為當前module重新定義一個AndroidManifest.xml文件,不聲明Application和launch的Activity,然后根據(jù)isModule的值指定AndroidManifest.xml的路徑,因此就有了注釋3處的寫法。為了避免集成模式下的命名沖突,每個文件都以自身module名為前綴來命名會是一個很好的方法。下圖是該module的目錄
在這里插入圖片描述
  1. 在需要切換module屬性的時候改變步驟1處聲明的變量值,然后重新編譯即可

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

在組件化架構(gòu)中,不同的組件之間是平衡的,不存在相互依賴的關(guān)系(可參考文章開頭的架構(gòu)圖)。因此,假設(shè)在組件A中,想要跳轉(zhuǎn)到組件B中的頁面,如果使用Intent顯式跳轉(zhuǎn)就行不通了,而且大家都知道,Intent隱式跳轉(zhuǎn)管理起來非常不方便,所以Arouter出現(xiàn)了,并且有強大的技術(shù)團隊支持,可以放心使用了。那么如何在組件化架構(gòu)中應(yīng)用Arouter呢?下面詳細來聊一聊

  1. 依賴處理

在common組件中將Arouter依賴進來,并配置編譯參數(shù);在業(yè)務(wù)組件中引入arouter編譯器插件,同時配置編譯器參數(shù),下面是Common組件gradle文件的部分片段


//配置arouter編譯器參數(shù),每個組件都需配置
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. 初始化及編碼實現(xiàn)

在組件架構(gòu)中,經(jīng)常會遇到組件需要使用全局Context的情況,當組件屬性為app時,可以通過自定義Application實現(xiàn);當組件屬性為library時,由于組件被app依賴,導(dǎo)致無法調(diào)用app的Application實例,而且自身不存在Application;所以,這里給出的方案是在common組件中創(chuàng)建一個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()
    }
}

根據(jù)Arouter的路由特性,初始化之后,就可以通過@Route注解注冊頁面,然后調(diào)用Arouter api實現(xiàn)頁面的跳轉(zhuǎn)了(這里所謂的跨組件頁面跳轉(zhuǎn)是指在集成模式下,而非組件模式下),無關(guān)乎是否在同一個組件下面,
假設(shè)我們要從組件1頁面攜帶參數(shù)跳轉(zhuǎn)到組件2頁面,請看下面示例


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

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

//在組件1中發(fā)起跳轉(zhuǎn)命令
ARouter.getInstance()
                    .build("/comp2/msg")
                    .withString("msg", "hello Im from Comp1")
                    .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
                    .navigation()

以上便完成了一次簡單的跨越組件的頁面跳轉(zhuǎn),僅僅是Arouter的基本使用而已。解決了組件間頁面跳轉(zhuǎn)的問題后,我們來看看組件之間通信、調(diào)用彼此服務(wù)的實現(xiàn)。

組件之間通信、調(diào)用彼此服務(wù)

組件間通信功能和路由功能有著共通的地方,即都是利用Arouter的基礎(chǔ)功能實現(xiàn),在Arouter驅(qū)動層定義各個組件對外提供的接口,然后在組件自身模塊實現(xiàn)該接口,通過Arouter調(diào)用其他組件服務(wù)。假設(shè)我們在組件2中需要調(diào)用組件1中的服務(wù),可以總結(jié)為以下3點

  • 定義接口:在common組件中定義組件1對外提供的接口CompServer1,注意該接口類型為Arouter模板類型IProvider

/**
 * 組件1對外提供的接口
 */
interface CompServer1 : IProvider {
    fun showMsg(msg: String)
}

  • 實現(xiàn)接口:在comm1中實現(xiàn)上面定義的接口,并通過@Route注解注冊

@Route(path = "/comp1/server",name = "comp1對外提供的服務(wù)")
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!!
    }
}

  • 調(diào)用服務(wù):在完成組件1接口的定義和實現(xiàn)之后,在組件2中需要的地方調(diào)用該接口即可

val server1 = ARouter.getInstance().build("/comp1/server").navigation() as CompServer1
server1.showMsg("我從comp2吊起了comp1的接口")

有沒有感覺很簡單??沒錯,就是這么簡單,趕緊去用吧!哈哈

打包混淆

說到混淆,有人可能會疑惑,如果在各個組件中混淆可不可以?不建議這樣混淆!!因為組件在集成模式下被gradle構(gòu)建成了release類型的aar包,如果在組件中進行混淆,一旦代碼出現(xiàn)了bug,這個時候就很難根據(jù)日志去追蹤bug產(chǎn)生的原因,而且不同組件分別進行混淆非常不方便維護和修改,這也是不推薦在業(yè)務(wù)組件中配置buildType(構(gòu)建類型)的原因。

所以,組件化項目的代碼混淆放在集成模式下的app殼工程,各個業(yè)務(wù)組件不配置混淆。集成模式下在app殼工程.gradle文件的release構(gòu)建模式下開啟混淆,其他buildType配置和普通項目相同,混淆文件放在app殼工程下,各個組件的代碼混淆均放在該混淆文件中。

小結(jié)

以上,我們已經(jīng)逐一解決了組件化所面對的各個問題,至此,我們已經(jīng)搭建了一個簡單的組件化架構(gòu)的項目,這一起感覺是在不知不覺中就實現(xiàn)了,并不是很難哦!現(xiàn)在,我們總結(jié)一下組件化的優(yōu)勢了

  • 解耦:將業(yè)務(wù)組件代碼90%與工程解耦,只所以是90%而非100%,是因為業(yè)務(wù)組件需要在common組件中聲明對外開放的接口,那有沒有什么方式可以做到完全解耦呢?目前還沒有發(fā)現(xiàn)更好的辦法。。。
  • 提高開發(fā)效率:依賴解耦這一優(yōu)勢,團隊成員可以只專注于自己負責(zé)的組件,開發(fā)效率更高;而且,組件開發(fā)過程中只需編譯自身的module,這樣大大縮短了編譯時長,避免了漫長的等待編譯局面。
  • 結(jié)構(gòu)清晰:在業(yè)務(wù)組件明確拆分的前提下,項目結(jié)構(gòu)變的異常清晰,非常方便全局掌控。

喜歡點擊+關(guān)注哦

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • MVVMHabitComponent 關(guān)于Android的組件化,相信大家并不陌生,網(wǎng)上談?wù)摻M件化的文章,多如過江...
    goldze閱讀 5,786評論 2 22
  • 不怕跌倒,所以飛翔 組件化開發(fā) 參考資源 Android組件化方案 為什么要組件化開發(fā) 解決問題 實際業(yè)務(wù)變化非常...
    筆墨Android閱讀 3,096評論 0 0
  • 問題 在已經(jīng)開發(fā)過幾個項目的童鞋,如果這時需要重新開發(fā)一個新項目,是否需要自己重新搭建框架呢,還是從老項目中拷貝粘...
    8ba406212441閱讀 43,563評論 84 381
  • 上集回顧 上一篇文章解說了模塊化以及組件,插件化的概念。模塊化是一種解決項目分層的思想,組件化和插件化分別是其不同...
    DevCW閱讀 4,007評論 6 45
  • 概述 組件化緣由 記得剛開始接觸Android開發(fā)的時候,只知道MVC分層架構(gòu),而且感覺Model,View以及C...
    wustor閱讀 1,987評論 0 12

友情鏈接更多精彩內(nèi)容