一個項目,隨着業務的發展,模塊會變的愈來愈多,代碼量也會變的異常龐大,進而可能開發的人會愈來愈多,這種狀況下若是仍是基於單一工程架構,那就須要每個開發者都熟悉全部的代碼,並且代碼之間耦合嚴重,一個模塊穿插着大量其餘業務模塊的邏輯,嚴重的話可能使項目處於牽一髮而動全身,不想輕易修改的局面;並且龐大的單一工程項目會致使編譯速度極慢,開發者長時間等待編譯結果,很是不利於開發工做。因此,就須要一個靈活的架構來解決這些問題,組件化架構思想應運而生。java
針對上面所說的幾個問題,下面咱們逐個說明它們的解決方案,當解決完這些問題,你會發現,你已經搭建了一個基於組件化的項目。下圖是一個完整的組件化項目結構:common是基礎組件module,做爲library存在,須要全部組件依賴;comp一、comp2做爲組件存在,可配置成library或可獨立運行的module;app是個殼,經過組裝組件實現其價值。android
Android工程經過gradle構建,經過配置每一個module的gradle,來實現module的不一樣表現。Android Studio的module有兩種屬性,分別是:api
module屬性經過其目錄下的gradle文件配置,當module屬性爲application時,該module做爲完整的app存在,能夠獨自運行,方便編譯和調試;當module屬性爲library時,該module做爲一個依賴庫,被殼工程依賴並組裝成一個app。那麼如何讓這兩種模式能夠自動轉換呢?若是每次切換模式的時候,都手動去修改每一個組件的配置,組件少的狀況下還能夠接受,組件多了會很是不方便,下面就讓咱們來聊聊如何實現兩種模式的自動轉換。網絡
buildscript {
ext.kotlin_version = '1.3.21'
ext.isModule = true //true-每一個組件都是單獨的module,可獨立運行 false-組件做爲library存在
repositories {
google()
jcenter()
}
}
複製代碼
//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的部分代碼,包含了組件化須要配置的全部內容,每一點都進行了註釋架構
在組件化架構中,不一樣的組件之間是平衡的,不存在相互依賴的關係(可參考文章開頭的架構圖)。所以,假設在組件A中,想要跳轉到組件B中的頁面,若是使用Intent顯式跳轉就行不通了,並且你們都知道,Intent隱式跳轉管理起來很是不方便,因此Arouter出現了,而且有強大的技術團隊支持,能夠放心使用了。那麼如何在組件化架構中應用Arouter呢?下面詳細來聊一聊app
在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'
}
複製代碼
在組件架構中,常常會遇到組件須要使用全局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點
/**
* 組件1對外提供的接口
*/
interface CompServer1 : IProvider {
fun showMsg(msg: String)
}
複製代碼
@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!!
}
}
複製代碼
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殼工程下,各個組件的代碼混淆均放在該混淆文件中。
以上,咱們已經逐一解決了組件化所面對的各個問題,至此,咱們已經搭建了一個簡單的組件化架構的項目,這一塊兒感受是在不知不覺中就實現了,並非很難哦!如今,咱們總結一下組件化的優點了