進階高工必備技能:Android元件化架構全解析!(附視訊+電子書+學習筆記分享)

安卓老猴子發表於2020-12-14

前言

現在大多數的App都會在重構的時候想到元件化或者說模組化,特別是在大廠或者一些有規模的公司顯得尤為重要,目的是為了方便App解耦和優化。在我的理解裡面元件化即將每個功能相同的模組一個個的裝起來,然後以library的形式供我們的主app模組呼叫,而在主app模組中不會去進行任何的業務邏輯,只管打包好了,而除了主app模組,其他模組各回各家,各找各媽,幹自己的業務邏輯去,簡單的說元件化就是讓library和application之間可以互相切換,libray可以作為單獨的App執行也可以作為依賴庫給主app呼叫。這種構建思想能大大的提升開發效率,減低程式碼的維護成本,減少程式碼的耦合度,讓他人簡單易懂。

整體結構

  • common:基礎元件部分,與業務無關,需要所有元件共同依賴的部分,如:網路請求封裝、圖片載入封裝、ui相關基類、工具集合等(當然這些內容可以依據分層原則放在不同的基礎module中)
  • router-comp:路由驅動元件,承載整個專案的路由工作
  • comp1:業務元件1,如視訊元件,可獨立執行
  • comp2:業務元件2,如新聞元件,可獨立執行
  • comp3:業務元件3,如視訊元件,可獨立執行
  • app:殼工程,用於將各個元件組裝成一個完成app

元件化所面臨的問題

  • 整合模式與元件模式轉換(熱插拔)
  • 元件之間頁面跳轉(路由)
  • 元件之間通訊、呼叫彼此服務
  • 打包混淆

元件化的實現

針對上面所說的幾個問題,下面我們逐個說明它們的解決方案,當解決完這些問題,你會發現,你已經搭建了一個基於元件化的專案。下圖是一個完整的元件化專案結構:common是基礎元件module,作為library存在,需要所有元件依賴;comp1、comp2作為元件存在,可配置成library或可獨立執行的module;app是個殼,通過組裝元件實現其價值。

整合模式與元件模式轉換(熱插拔)

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

  • 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()
 }
}

2、配置元件的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的目錄

在需要切換module屬性的時候改變步驟1處宣告的變數值,然後重新編譯即可

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

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

1、依賴處理

在common元件中將Arouter依賴進來,並配置編譯引數;在業務元件中引入arouter編譯器外掛,同時配置編譯器引數,下面是Common元件gradle檔案的部分片段

//配置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'
}

2、初始化及編碼實現

在元件架構中,經常會遇到元件需要使用全域性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的基本使用而已。解決了元件間頁面跳轉的問題後,我們來看看元件之間通訊、呼叫彼此服務的實現。元件之間通訊、呼叫彼此服務元件間通訊功能和路由功能有著共通的地方,即都是利用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,這樣大大縮短了編譯時長,避免了漫長的等待編譯局面。

  • 結構清晰:在業務元件明確拆分的前提下,專案結構變的異常清晰,非常方便全域性掌控。

為了方便大家更加深入學習元件化架構原理及底層原始碼解讀,我這邊收集整理了一套視訊+電子書+學習筆記的元件化學習資料。視訊是由愛奇藝高工Lance老師主講的《從零開始學元件化》以及元件化學習電子書《Android元件化架構》,和一套《第三方開源框架解析學習筆記》,包含外掛化,元件化,熱修復等熱門第三方框架的解析學習筆記!

需要以上元件化學習資料的小夥伴,可以在點贊+任意評論後,點選此處免費獲取

元件化學習視訊
元件化電子書
開源框架解讀目錄及部分解析內容

需要以上元件化學習資料的小夥伴,可以在點贊+任意評論後,點選此處免費獲取

相關文章