android 組件化

Android組件化項目地址:Android組件化項目AndroidModulePattern

Android組件化之終極方案地址:http://blog.csdn.net/guiying712/article/details/78057120

1為什么要項目組件化

2如何組件化

3組件化實施流程

1組件模式和集成模式的轉換

2組件之間AndroidManifest合并問題

3全局Context的獲取及組件數據初始化

4library依賴問題

5組件之間調用和通信

6組件之間資源名沖突

4組件化項目的工程類型

1app殼工程

2功能組件和Common組件

2業務組件和Main組件

5組件化項目的混淆方案

6工程的buildgradle和gradleproperties文件

1組件化工程的buildgradle文件

2組件化工程的gradleproperties文件

7組件化項目Router的其他方案-ARouter

8結束語

1、為什么要項目組件化

隨著APP版本不斷的迭代,新功能的不斷增加,業務也會變的越來越復雜,APP業務模塊的數量有可能還會繼續增加,而且每個模塊的代碼也變的越來越多,這樣發展下去單一工程下的APP架構勢必會影響開發效率,增加項目的維護成本,每個工程師都要熟悉如此之多的代碼,將很難進行多人協作開發,而且Android項目在編譯代碼的時候電腦會非常卡,又因為單一工程下代碼耦合嚴重,每修改一處代碼后都要重新編譯打包測試,導致非常耗時,最重要的是這樣的代碼想要做單元測試根本無從下手,所以必須要有更靈活的架構代替過去單一的工程架構。

上圖是目前比較普遍使用的Android APP技術架構,往往是在一個界面中存在大量的業務邏輯,而業務邏輯中充斥著各種網絡請求、數據操作等行為,整個項目中也沒有模塊的概念,只有簡單的以業務邏輯劃分的文件夾,并且業務之間也是直接相互調用、高度耦合在一起的;

上圖單一工程模型下的業務關系,總的來說就是:你中有我,我中有你,相互依賴,無法分離。?

然而隨著產品的迭代,業務越來越復雜,隨之帶來的是項目結構復雜度的極度增加,此時我們會面臨如下幾個問題:

1、實際業務變化非常快,但是單一工程的業務模塊耦合度太高,牽一發而動全身;?

2、對工程所做的任何修改都必須要編譯整個工程;?

3、功能測試和系統測試每次都要進行;?

4、團隊協同開發存在較多的沖突.不得不花費更多的時間去溝通和協調,并且在開發過程中,任何一位成員沒辦法專注于自己的功能點,影響開發效率;?

5、不能靈活的對業務模塊進行配置和組裝;

為了滿足各個業務模塊的迭代而彼此不受影響,更好的解決上面這種讓人頭疼的依賴關系,就需要整改App的架構。

2、如何組件化

上圖是組件化工程模型,為了方便理解這張架構圖,下面會列舉一些組件化工程中用到的名詞的含義:

名詞含義

集成模式所有的業務組件被“app殼工程”依賴,組成一個完整的APP;

組件模式可以獨立開發業務組件,每一個業務組件就是一個APP;

app殼工程負責管理各個業務組件,和打包apk,沒有具體的業務功能;

業務組件根據公司具體業務而獨立形成一個的工程;

功能組件提供開發APP的某些基礎功能,例如打印日志、樹狀圖等;

Main組件屬于業務組件,指定APP啟動頁面、主界面;

Common組件屬于功能組件,支撐業務組件的基礎,提供多數業務組件需要的功能,例如提供網絡請求功能;

**?

Android APP組件化架構的目標是告別結構臃腫,讓各個業務變得相對獨立,業務組件在組件模式下可以獨立開發,而在集成模式下又可以變為arr包集成到“app殼工程”中,組成一個完整功能的APP;?

從組件化工程模型中可以看到,業務組件之間是獨立的,沒有關聯的,這些業務組件在集成模式下是一個個library,被app殼工程所依賴,組成一個具有完整業務功能的APP應用,但是在組件開發模式下,業務組件又變成了一個個application,它們可以獨立開發和調試,由于在組件開發模式下,業務組件們的代碼量相比于完整的項目差了很遠,因此在運行時可以顯著減少編譯時間。

這是組件化工程模型下的業務關系,業務之間將不再直接引用和依賴,而是通過“路由”這樣一個中轉站間接產生聯系,而Android中的路由實際就是對URL Scheme的封裝;?

如此規模大的架構整改需要付出更高的成本,還會涉及一些潛在的風險,但是整改后的架構能夠帶來很多好處:

1、加快業務迭代速度,各個業務模塊組件更加獨立,不再出現業務耦合情況;?

2、穩定的公共模塊采用依賴庫方式,提供給各個業務線使用,減少重復開發和維護工作量;?

3、迭代頻繁的業務模塊采用組件方式,各業務研發可以互不干擾、提升協作效率,并控制產品質量;?

4、為新業務隨時集成提供了基礎,所有業務可上可下,靈活多變;?

5、降低團隊成員熟悉項目的成本,降低項目的維護難度;?

6、加快編譯速度,提高開發效率;?

7、控制代碼權限,將代碼的權限細分到更小的粒度;

3、組件化實施流程

1)組件模式和集成模式的轉換

Android Studio中的Module主要有兩種屬性,分別為:

1、application屬性,可以獨立運行的Android程序,也就是我們的APP;

apply plugin: ‘com.android.application’

1

2、library屬性,不可以獨立運行,一般是Android程序依賴的庫文件;

apply plugin: ‘com.android.library’

1

Module的屬性是在每個組件的?build.gradle?文件中配置的,當我們在組件模式開發時,業務組件應處于application屬性,這時的業務組件就是一個 Android App,可以獨立開發和調試;而當我們轉換到集成模式開發時,業務組件應該處于 library 屬性,這樣才能被我們的“app殼工程”所依賴,組成一個具有完整功能的APP;

但是我們如何讓組件在這兩種模式之間自動轉換呢?總不能每次需要轉換模式的時候去每個業務組件的 Gralde 文件中去手動把 Application 改成 library 吧?如果我們的項目只有兩三個組件那么這個辦法肯定是可行的,手動去改一遍也用不了多久,但是在大型項目中我們可能會有十幾個業務組件,再去手動改一遍必定費時費力,這時候就需要程序員發揮下懶的本質了。

試想,我們經常在寫代碼的時候定義靜態常量,那么定義靜態常量的目的什么呢?當一個常量需要被好幾處代碼引用的時候,把這個常量定義為靜態常量的好處是當這個常量的值需要改變時我們只需要改變靜態常量的值,其他引用了這個靜態常量的地方都會被改變,做到了一次改變,到處生效;根據這個思想,那么我們就可以在我們的代碼中的某處定義一個決定業務組件屬性的常量,然后讓所有業務組件的build.gradle都引用這個常量,這樣當我們改變了常量值的時候,所有引用了這個常量值的業務組件就會根據值的變化改變自己的屬性;可是問題來了?靜態常量是用Java代碼定義的,而改變組件屬性是需要在Gradle中定義的,Gradle能做到嗎?

Gradle自動構建工具有一個重要屬性,可以幫助我們完成這個事情。每當我們用AndroidStudio創建一個Android項目后,就會在項目的根目錄中生成一個文件?gradle.properties,我們將使用這個文件的一個重要屬性:在Android項目中的任何一個build.gradle文件中都可以把gradle.properties中的常量讀取出來;那么我們在上面提到解決辦法就有了實際行動的方法,首先我們在gradle.properties中定義一個常量值?isModule(是否是組件開發模式,true為是,false為否)

# 每次更改“isModule”的值后,需要點擊"Sync Project"按鈕isModule=false

然后我們在業務組件的build.gradle中讀取?isModule,但是 gradle.properties 還有一個重要屬性:?gradle.properties 中的數據類型都是String類型,使用其他數據類型需要自行轉換;也就是說我們讀到 isModule 是個String類型的值,而我們需要的是Boolean值,代碼如下:

if(isModule.toBoolean()) {? ? apply plugin:'com.android.application'}else{? ? apply plugin:'com.android.library'}

這樣我們第一個問題就解決了,當然了?每次改變isModule的值后,都要同步項目才能生效;

2)組件之間AndroidManifest合并問題

在 AndroidStudio 中每一個組件都會有對應的 AndroidManifest.xml,用于聲明需要的權限、Application、Activity、Service、Broadcast等,當項目處于組件模式時,業務組件的 AndroidManifest.xml 應該具有一個 Android APP 所具有的的所有屬性,尤其是聲明 Application 和要 launch的Activity,但是當項目處于集成模式的時候,每一個業務組件的 AndroidManifest.xml 都要合并到“app殼工程”中,要是每一個業務組件都有自己的 Application 和 launch的Activity,那么合并的時候肯定會沖突,試想一個APP怎么可能會有多個 Application 和 launch 的Activity呢?

但是大家應該注意到這個問題是在組件開發模式和集成開發模式之間轉換引起的問題,而在上一節中我們已經解決了組件模式和集成模式轉換的問題,另外大家應該都經歷過將 Android 項目從 Eclipse 切換到 AndroidStudio 的過程,由于 Android 項目在 Eclipse 和 AndroidStudio開發時 AndroidManifest.xml 文件的位置是不一樣的,我們需要在build.gradle 中指定下 AndroidManifest.xml 的位置,AndroidStudio 才能讀取到 AndroidManifest.xml,這樣解決辦法也就有了,我們可以為組件開發模式下的業務組件再創建一個 AndroidManifest.xml,然后根據isModule指定AndroidManifest.xml的文件路徑,讓業務組件在集成模式和組件模式下使用不同的AndroidManifest.xml,這樣表單沖突的問題就可以規避了。

上圖是組件化項目中一個標準的業務組件目錄結構,首先我們在main文件夾下創建一個module文件夾用于存放組件開發模式下業務組件的 AndroidManifest.xml,而 AndroidStudio 生成的 AndroidManifest.xml 則依然保留,并用于集成開發模式下業務組件的表單;然后我們需要在業務組件的 build.gradle 中指定表單的路徑,代碼如下:

sourceSets {? ? ? ? main {if(isModule.toBoolean()) {? ? ? ? ? ? ? ? manifest.srcFile'src/main/module/AndroidManifest.xml'}else{? ? ? ? ? ? ? ? manifest.srcFile'src/main/AndroidManifest.xml'}? ? ? ? }? ? }

這樣在不同的開發模式下就會讀取到不同的 AndroidManifest.xml ,然后我們需要修改這兩個表單的內容以為我們不同的開發模式服務。

首先是集成開發模式下的 AndroidManifest.xml,前面我們說過集成模式下,業務組件的表單是絕對不能擁有自己的 Application 和 launch 的 Activity的,也不能聲明APP名稱、圖標等屬性,總之app殼工程有的屬性,業務組件都不能有,下面是一份標準的集成開發模式下業務組件的 AndroidManifest.xml:

我在這個表單中只聲明了應用的主題,而且這個主題還是跟app殼工程中的主題是一致的,都引用了common組件中的資源文件,在這里聲明主題是為了方便這個業務組件中有使用默認主題的Activity時就不用再給Activity單獨聲明theme了。

然后是組件開發模式下的表單文件:

組件模式下的業務組件表單就是一個Android項目普通的AndroidManifest.xml,這里就不在過多介紹了。

3)全局Context的獲取及組件數據初始化

當Android程序啟動時,Android系統會為每個程序創建一個 Application 類的對象,并且只創建一個,application對象的生命周期是整個程序中最長的,它的生命周期就等于這個程序的生命周期。在默認情況下應用系統會自動生成 Application 對象,但是如果我們自定義了 Application,那就需要在 AndroidManifest.xml 中聲明告知系統,實例化的時候,是實例化我們自定義的,而非默認的。

但是我們在組件化開發的時候,可能為了數據的問題每一個組件都會自定義一個Application類,如果我們在自己的組件中開發時需要獲取?全局的Context,一般都會直接獲取 application 對象,但是當所有組件要打包合并在一起的時候就會出現問題,因為最后程序只有一個 Application,我們組件中自己定義的 Application 肯定是沒法使用的,因此我們需要想辦法再任何一個業務組件中都能獲取到全局的 Context,而且這個 Context 不管是在組件開發模式還是在集成開發模式都是生效的。

在 組件化工程模型圖中,功能組件集合中有一個?Common 組件, Common 有公共、公用、共同的意思,所以這個組件中主要封裝了項目中需要的基礎功能,并且每一個業務組件都要依賴Common組件,Common 組件就像是萬丈高樓的地基,而業務組件就是在 Common 組件這個地基上搭建起來我們的APP的,Common 組件會專門在一個章節中講解,這里只講 Common組件中的一個功能,在Common組件中我們封裝了項目中用到的各種Base類,這些基類中就有BaseApplication 類

BaseApplication 主要用于各個業務組件和app殼工程中聲明的 Application 類繼承用的,只要各個業務組件和app殼工程中聲明的Application類繼承了 BaseApplication,當應用啟動時 BaseApplication 就會被動實例化,這樣從 BaseApplication 獲取的 Context 就會生效,也就從根本上解決了我們不能直接從各個組件獲取全局 Context 的問題;

這時候大家肯定都會有個疑問?不是說了業務組件不能有自己的 Application 嗎,怎么還讓他們繼承 BaseApplication 呢?其實我前面說的是業務組件不能在集成模式下擁有自己的 Application,但是這不代表業務組件也不能在組件開發模式下擁有自己的Application,其實業務組件在組件開發模式下必須要有自己的 Application 類,一方面是為了讓 BaseApplication 被實例化從而獲取 Context,還有一個作用是,業務組件自己的 Application 可以在組件開發模式下初始化一些數據,例如在組件開發模式下,A組件沒有登錄頁面也沒法登錄,因此就無法獲取到 Token,這樣請求網絡就無法成功,因此我們需要在A組件這個 APP 啟動后就應該已經登錄了,這時候組件自己的 Application 類就有了用武之地,我們在組件的 Application的 onCreate 方法中模擬一個登陸接口,在登陸成功后將數據保存到本地,這樣就可以處理A組件中的數據業務了;另外我們也可以在組件Application中初始化一些第三方庫

但是,實際上業務組件中的Application在最終的集成項目中是沒有什么實際作用的,組件自己的 Application 僅限于在組件模式下發揮功能,因此我們需要在將項目從組件模式轉換到集成模式后將組件自己的Application剔除出我們的項目;在 AndroidManifest 合并問題小節中介紹了如何在不同開發模式下讓 Gradle 識別組件表單的路徑,這個方法也同樣適用于Java代碼;

我們在Java文件夾下創建一個 debug 文件夾,用于存放不會在業務組件中引用的類,例如上圖中的 NewsApplication ,你甚至可以在 debug 文件夾中創建一個Activity,然后組件表單中聲明啟動這個Activity,在這個Activity中不用setContentView,只需要在啟動你的目標Activity的時候傳遞參數就行,這樣就就可以解決組件模式下某些Activity需要getIntent數據而沒有辦法拿到的情況,代碼如下;

publicclassLauncherActivityextendsAppCompatActivity{@OverrideprotectedvoidonCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);? ? ? ? request();? ? ? ? Intent intent =newIntent(this, TargetActivity.class);? ? ? ? intent.putExtra("name","avcd");? ? ? ? intent.putExtra("syscode","023e2e12ed");? ? ? ? startActivity(intent);? ? ? ? finish();? ? }//申請讀寫權限privatevoidrequest() {? ? ? ? AndPermission.with(this)? ? ? ? ? ? ? ? .requestCode(110)? ? ? ? ? ? ? ? .permission(Manifest.permission.WRITE_EXTERNAL_STORAGE,? ? ? ? ? ? ? ? ? ? ? ? Manifest.permission.CAMERA, Manifest.permission.READ_PHONE_STATE)? ? ? ? ? ? ? ? .callback(this)? ? ? ? ? ? ? ? .start();? ? }}

接下來在業務組件的 build.gradle 中,根據 isModule 是否是集成模式將 debug 這個 Java代碼文件夾排除:

sourceSets {? ? ? ? main {if(isModule.toBoolean()) {? ? ? ? ? ? ? ? manifest.srcFile'src/main/module/AndroidManifest.xml'}else{? ? ? ? ? ? ? ? manifest.srcFile'src/main/AndroidManifest.xml'//集成開發模式下排除debug文件夾中的所有Java文件java {? ? ? ? ? ? ? ? ? ? exclude'debug/**'}? ? ? ? ? ? }? ? ? ? }? ? }

4)library依賴問題

在介紹這一節的時候,先說一個問題,在組件化工程模型圖中,多媒體組件和Common組件都依賴了日志組件,而A業務組件有同時依賴了多媒體組件和Common組件,這時候就會有人問,你這樣搞豈不是日志組件要被重復依賴了,而且Common組件也被每一個業務組件依賴了,這樣不出問題嗎?

其實大家完全沒有必要擔心這個問題,如果真有重復依賴的問題,在你編譯打包的時候就會報錯,如果你還是不相信的話可以反編譯下最后打包出來的APP,看看里面的代碼你就知道了。組件只是我們在代碼開發階段中為了方便叫的一個術語,在組件被打包進APP的時候是沒有這個概念的,這些組件最后都會被打包成arr包,然后被app殼工程所依賴,在構建APP的過程中Gradle會自動將重復的arr包排除,APP中也就不會存在相同的代碼了;

但是雖然組件是不會重復了,但是我們還是要考慮另一個情況,我們在build.gradle中compile的第三方庫,例如AndroidSupport庫經常會被一些開源的控件所依賴,而我們自己一定也會compile AndroidSupport庫 ,這就會造成第三方包和我們自己的包存在重復加載,解決辦法就是找出那個多出來的庫,并將多出來的庫給排除掉,而且Gradle也是支持這樣做的,分別有兩種方式:根據組件名排除或者根據包名排除,下面以排除support-v4庫為例:

dependencies {? ? compile fileTree(dir:'libs', include: ['*.jar'])? ? compile("com.jude:easyrecyclerview:$rootProject.easyRecyclerVersion") {? ? ? ? exclude module:'support-v4'//根據組件名排除exclude group:'android.support.v4'//根據包名排除}}

library重復依賴的問題算是都解決了,但是我們在開發項目的時候會依賴很多開源庫,而這些庫每個組件都需要用到,要是每個組件都去依賴一遍也是很麻煩的,尤其是給這些庫升級的時候,為了方便我們統一管理第三方庫,我們將給給整個工程提供統一的依賴第三方庫的入口,前面介紹的Common庫的作用之一就是統一依賴開源庫,因為其他業務組件都依賴了Common庫,所以這些業務組件也就間接依賴了Common所依賴的開源庫。

dependencies {? ? compile fileTree(dir:'libs', include: ['*.jar'])//Android Supportcompile"com.android.support:appcompat-v7:$rootProject.supportLibraryVersion"compile"com.android.support:design:$rootProject.supportLibraryVersion"compile"com.android.support:percent:$rootProject.supportLibraryVersion"http://網絡請求相關compile"com.squareup.retrofit2:retrofit:$rootProject.retrofitVersion"compile"com.squareup.retrofit2:retrofit-mock:$rootProject.retrofitVersion"compile"com.github.franmontiel:PersistentCookieJar:$rootProject.cookieVersion"http://穩定的compile"com.github.bumptech.glide:glide:$rootProject.glideVersion"compile"com.orhanobut:logger:$rootProject.loggerVersion"compile"org.greenrobot:eventbus:$rootProject.eventbusVersion"compile"com.google.code.gson:gson:$rootProject.gsonVersion"compile"com.github.chrisbanes:PhotoView:$rootProject.photoViewVersion"compile"com.jude:easyrecyclerview:$rootProject.easyRecyclerVersion"compile"com.github.GrenderG:Toasty:$rootProject.toastyVersion"http://routercompile"com.github.mzule.activityrouter:activityrouter:$rootProject.routerVersion"}

5)組件之間調用和通信

在組件化開發的時候,組件之間是沒有依賴關系,我們不能在使用顯示調用來跳轉頁面了,因為我們組件化的目的之一就是解決模塊間的強依賴問題,假如現在要從A業務組件跳轉到業務B組件,并且要攜帶參數跳轉,這時候怎么辦呢?而且組件這么多怎么管理也是個問題,這時候就需要引入“路由”的概念了,由本文開始的組件化模型下的業務關系圖可知路由就是起到一個轉發的作用。

這里我將介紹開源庫的“ActivityRouter”?,有興趣的同學情直接去ActivityRouter的Github主頁學習:ActivityRouter,ActivityRouter支持給Activity定義 URL,這樣就可以通過 URL 跳轉到Activity,并且支持從瀏覽器以及 APP 中跳入我們的Activity,而且還支持通過 url 調用方法。下面將介紹如何將ActivityRouter集成到組件化項目中以實現組件之間的調用;

1、首先我們需要在 Common 組件中的 build.gradle 將ActivityRouter 依賴進來,方便我們在業務組件中調用:

dependencies {? ? compile fileTree(dir:'libs', include: ['*.jar'])//routercompile"com.github.mzule.activityrouter:activityrouter:$rootProject.routerVersion"}

2、這一步我們需要先了解?APT這個概念,APT(Annotation Processing Tool)是一種處理注解的工具,它對源代碼文件進行檢測找出其中的Annotation,使用Annotation進行額外的處理。 Annotation處理器在處理Annotation時可以根據源文件中的Annotation生成額外的源文件和其它的文件(文件具體內容由Annotation處理器的編寫者決定),APT還會編譯生成的源文件和原來的源文件,將它們一起生成class文件。在這里我們將在每一個業務組件的 build.gradle 都引入ActivityRouter 的 Annotation處理器,我們將會在聲明組件和Url的時候使用,annotationProcessor是Android官方提供的Annotation處理器插件,代碼如下:

dependencies {? ? compile fileTree(dir:'libs', include: ['*.jar'])? ? annotationProcessor"com.github.mzule.activityrouter:compiler:$rootProject.annotationProcessor"}

3、接下來需要在?app殼工程的 AndroidManifest.xml 配置,到這里ActivityRouter配置就算完成了:

4、接下來我們將聲明項目中的業務組件,聲明方法如下:

@Module("girls")publicclassGirls{}

在每一個業務組件的java文件的根目錄下創建一個類,用?注解@Module?聲明這個業務組件;?

然后在“app殼工程”的?應用Application?中使用?注解@Modules?管理我們聲明的所有業務組件,方法如下:

@Modules({"main","girls","news"})publicclassMyApplicationextendsBaseApplication{}

到這里組件化項目中的所有業務組件就被聲明和管理起來了,組件之間的也就可以互相調用了,當然前提是要給業務組件中的Activity定義 URL。

5、例如我們給 Girls組件 中的 GirlsActivity 使用?注解@Router?定義一個 URL:“news”,方法如下:

@Router("girls")publicclassGirlsActivityextendsBaseActionBarActivity{privateGirlsView mView;privateGirlsContract.Presenter mPresenter;@OverrideprotectedintsetTitleId() {returnR.string.girls_activity_title;? ? }@OverrideprotectedvoidonCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);? ? ? ? mView =newGirlsView(this);? ? ? ? setContentView(mView);? ? ? ? mPresenter =newGirlsPresenter(mView);? ? ? ? mPresenter.start();? ? }}

然后我們就可以在項目中的任何一個地方通過 URL地址 : module://girls, 調用 GirlsActivity,方法如下:

Routers.open(MainActivity.this,"module://girls");

組件之間的調用解決后,另外需要解決的就是組件之間的通信,例如A業務組件中有消息列表,而用戶在B組件中操作某個事件后會產生一條新消息,需要通知A組件刷新消息列表,這樣業務場景需求可以使用Android廣播來解決,也可以使用第三方的事件總線來實現,比如EventBus

6)組件之間資源名沖突

因為我們拆分出了很多業務組件和功能組件,在把這些組件合并到“app殼工程”時候就有可能會出現資源名沖突問題,例如A組件和B組件都有一張叫做“ic_back”的圖標,這時候在集成模式下打包APP就會編譯出錯,解決這個問題最簡單的辦法就是在項目中約定資源文件命名規約,比如強制使每個資源文件的名稱以組件名開始,這個可以根據實際情況和開發人員制定規則。當然了萬能的Gradle構建工具也提供了解決方法,通過在在組件的build.gradle中添加如下的代碼:

//設置了resourcePrefix值后,所有的資源名必須以指定的字符串做前綴,否則會報錯。//但是resourcePrefix這個值只能限定xml里面的資源,并不能限定圖片資源,所有圖片資源仍然需要手動去修改資源名。resourcePrefix"girls_"


但是設置了這個屬性后有個問題,所有的資源名必須以指定的字符串做前綴,否則會報錯,而且resourcePrefix這個值只能限定xml里面的資源,并不能限定圖片資源,所有圖片資源仍然需要手動去修改資源名;所以我并不推薦使用這種方法來解決資源名沖突。

4、組件化項目的工程類型

在組件化工程模型中主要有:app殼工程、業務組件和功能組件3種類型,而業務組件中的Main組件和功能組件中的Common組件比較特殊,下面將分別介紹。

1)app殼工程

app殼工程是從名稱來解釋就是一個空殼工程,沒有任何的業務代碼,也不能有Activity,但它又必須被單獨劃分成一個組件,而不能融合到其他組件中,是因為它有如下幾點重要功能:

1、app殼工程中聲明了我們Android應用的 Application,這個 Application 必須繼承自 Common組件中的 BaseApplication(如果你無需實現自己的Application可以直接在表單聲明BaseApplication),因為只有這樣,在打包應用后才能讓BaseApplication中的Context生效,當然你還可以在這個 Application中初始化我們工程中使用到的庫文件,還可以在這里解決Android引用方法數不能超過 65535 的限制,對崩潰事件的捕獲和發送也可以在這里聲明。

2、app殼工程的 AndroidManifest.xml 是我Android應用的根表單,應用的名稱、圖標以及是否支持備份等等屬性都是在這份表單中配置的,其他組件中的表單最終在集成開發模式下都被合并到這份 AndroidManifest.xml 中。

3、app殼工程的 build.gradle 是比較特殊的,app殼不管是在集成開發模式還是組件開發模式,它的屬性始終都是:com.android.application,因為最終其他的組件都要被app殼工程所依賴,被打包進app殼工程中,這一點從組件化工程模型圖中就能體現出來,所以app殼工程是不需要單獨調試單獨開發的。另外Android應用的打包簽名,以及buildTypes和defaultConfig都需要在這里配置,而它的dependencies則需要根據isModule的值分別依賴不同的組件,在組件開發模式下app殼工程只需要依賴Common組件,或者為了防止報錯也可以根據實際情況依賴其他功能組件,而在集成模式下app殼工程必須依賴所有在應用Application中聲明的業務組件,并且不需要再依賴任何功能組件。

下面是一份 app殼工程 的 build.gradle文件

apply plugin:'com.android.application'staticdef buildTime() {returnnewDate().format("yyyyMMdd");}android {? ? signingConfigs {? ? ? ? release {? ? ? ? ? ? keyAlias'guiying712'keyPassword'guiying712'storeFile file('/mykey.jks')? ? ? ? ? ? storePassword'guiying712'}? ? }? ? compileSdkVersion rootProject.ext.compileSdkVersion? ? buildToolsVersion rootProject.ext.buildToolsVersion? ? defaultConfig {? ? ? ? applicationId"com.guiying.androidmodulepattern"minSdkVersion rootProject.ext.minSdkVersion? ? ? ? targetSdkVersion rootProject.ext.targetSdkVersion? ? ? ? versionCode rootProject.ext.versionCode? ? ? ? versionName rootProject.ext.versionName? ? ? ? multiDexEnabledfalse//打包時間resValue"string","build_time", buildTime()? ? }? ? buildTypes {? ? ? ? release {//更改AndroidManifest.xml中預先定義好占位符信息//manifestPlaceholders = [app_icon: "@drawable/icon"]// 不顯示LogbuildConfigField"boolean","LEO_DEBUG","false"http://是否zip對齊zipAlignEnabledtrue// 縮減resource文件shrinkResourcestrue//ProguardminifyEnabledtrueproguardFiles getDefaultProguardFile('proguard-android.txt'),'proguard-rules.pro'//簽名signingConfig signingConfigs.release? ? ? ? }? ? ? ? debug {//給applicationId添加后綴“.debug”applicationIdSuffix".debug"http://manifestPlaceholders = [app_icon: "@drawable/launch_beta"]buildConfigField"boolean","LOG_DEBUG","true"zipAlignEnabledfalseshrinkResourcesfalseminifyEnabledfalsedebuggabletrue}? ? }}dependencies {? ? compile fileTree(dir:'libs', include: ['*.jar'])? ? annotationProcessor"com.github.mzule.activityrouter:compiler:$rootProject.annotationProcessor"if(isModule.toBoolean()) {? ? ? ? compile project(':lib_common')? ? }else{? ? ? ? compile project(':module_main')? ? ? ? compile project(':module_girls')? ? ? ? compile project(':module_news')? ? }}

2)功能組件和Common組件

功能組件是為了支撐業務組件的某些功能而獨立劃分出來的組件,功能實質上跟項目中引入的第三方庫是一樣的,功能組件的特征如下:

1、功能組件的 AndroidManifest.xml 是一張空表,這張表中只有功能組件的包名;

2、功能組件不管是在集成開發模式下還是組件開發模式下屬性始終是: com.android.library,所以功能組件是不需要讀取 gradle.properties 中的 isModule 值的;另外功能組件的 build.gradle 也無需設置 buildTypes ,只需要 dependencies 這個功能組件需要的jar包和開源庫。

下面是一份 普通 的功能組件的 build.gradle文件

apply plugin:'com.android.library'android {? ? compileSdkVersion rootProject.ext.compileSdkVersion? ? buildToolsVersion rootProject.ext.buildToolsVersion? ? defaultConfig {? ? ? ? minSdkVersion rootProject.ext.minSdkVersion? ? ? ? targetSdkVersion rootProject.ext.targetSdkVersion? ? ? ? versionCode rootProject.ext.versionCode? ? ? ? versionName rootProject.ext.versionName? ? }}dependencies {? ? compile fileTree(dir:'libs', include: ['*.jar'])}


Common組件除了有功能組件的普遍屬性外,還具有其他功能

1、Common組件的 AndroidManifest.xml 不是一張空表,這張表中聲明了我們 Android應用用到的所有使用權限 uses-permission 和 uses-feature,放到這里是因為在組件開發模式下,所有業務組件就無需在自己的 AndroidManifest.xm 聲明自己要用到的權限了。

2、Common組件的 build.gradle 需要統一依賴業務組件中用到的 第三方依賴庫和jar包,例如我們用到的ActivityRouter、Okhttp等等。

3、Common組件中封裝了Android應用的 Base類和網絡請求工具、圖片加載工具等等,公用的 widget控件也應該放在Common 組件中;業務組件中都用到的數據也應放于Common組件中,例如保存到 SharedPreferences 和 DataBase 中的登陸數據;

4、Common組件的資源文件中需要放置項目公用的 Drawable、layout、sting、dimen、color和style 等等,另外項目中的 Activity 主題必須定義在 Common中,方便和 BaseActivity 配合保持整個Android應用的界面風格統一。

下面是一份 Common功能組件的 build.gradle文件

apply plugin:'com.android.library'android {? ? compileSdkVersion rootProject.ext.compileSdkVersion? ? buildToolsVersion rootProject.ext.buildToolsVersion? ? defaultConfig {? ? ? ? minSdkVersion rootProject.ext.minSdkVersion? ? ? ? targetSdkVersion rootProject.ext.targetSdkVersion? ? ? ? versionCode rootProject.ext.versionCode? ? ? ? versionName rootProject.ext.versionName? ? }}dependencies {? ? compile fileTree(dir:'libs', include: ['*.jar'])//Android Supportcompile"com.android.support:appcompat-v7:$rootProject.supportLibraryVersion"compile"com.android.support:design:$rootProject.supportLibraryVersion"compile"com.android.support:percent:$rootProject.supportLibraryVersion"http://網絡請求相關compile"com.squareup.retrofit2:retrofit:$rootProject.retrofitVersion"compile"com.squareup.retrofit2:retrofit-mock:$rootProject.retrofitVersion"compile"com.github.franmontiel:PersistentCookieJar:$rootProject.cookieVersion"http://穩定的compile"com.github.bumptech.glide:glide:$rootProject.glideVersion"compile"com.orhanobut:logger:$rootProject.loggerVersion"compile"org.greenrobot:eventbus:$rootProject.eventbusVersion"compile"com.google.code.gson:gson:$rootProject.gsonVersion"compile"com.github.chrisbanes:PhotoView:$rootProject.photoViewVersion"compile"com.jude:easyrecyclerview:$rootProject.easyRecyclerVersion"compile"com.github.GrenderG:Toasty:$rootProject.toastyVersion"http://routercompile"com.github.mzule.activityrouter:activityrouter:$rootProject.routerVersion"}

2)業務組件和Main組件

業務組件就是根據業務邏輯的不同拆分出來的組件,業務組件的特征如下:

1、業務組件中要有兩張AndroidManifest.xml,分別對應組件開發模式和集成開發模式,這兩張表的區別請查看?組件之間AndroidManifest合并問題?小節。

2、業務組件在集成模式下是不能有自己的Application的,但在組件開發模式下又必須實現自己的Application并且要繼承自Common組件的BaseApplication,并且這個Application不能被業務組件中的代碼引用,因為它的功能就是為了使業務組件從BaseApplication中獲取的全局Context生效,還有初始化數據之用。

3、業務組件有debug文件夾,這個文件夾在集成模式下會從業務組件的代碼中排除掉,所以debug文件夾中的類不能被業務組件強引用,例如組件模式下的 Application 就是置于這個文件夾中,還有組件模式下開發給目標 Activity 傳遞參數的用的 launch Activity 也應該置于 debug 文件夾中;

4、業務組件必須在自己的 Java文件夾中創建業務組件聲明類,以使?app殼工程 中的 應用Application能夠引用,實現組件跳轉,具體請查看?組件之間調用和通信?小節;

5、業務組件必須在自己的 build.gradle 中根據 isModule 值的不同改變自己的屬性,在組件模式下是:com.android.application,而在集成模式下com.android.library;同時還需要在build.gradle配置資源文件,如 指定不同開發模式下的AndroidManifest.xml文件路徑,排除debug文件夾等;業務組件還必須在dependencies中依賴Common組件,并且引入ActivityRouter的注解處理器annotationProcessor,以及依賴其他用到的功能組件。

下面是一份普通業務組件的 build.gradle文件

if(isModule.toBoolean()) {? ? apply plugin:'com.android.application'}else{? ? apply plugin:'com.android.library'}android {? ? compileSdkVersion rootProject.ext.compileSdkVersion? ? buildToolsVersion rootProject.ext.buildToolsVersion? ? defaultConfig {? ? ? ? minSdkVersion rootProject.ext.minSdkVersion? ? ? ? targetSdkVersion rootProject.ext.targetSdkVersion? ? ? ? versionCode rootProject.ext.versionCode? ? ? ? versionName rootProject.ext.versionName? ? }? ? sourceSets {? ? ? ? main {if(isModule.toBoolean()) {? ? ? ? ? ? ? ? manifest.srcFile'src/main/module/AndroidManifest.xml'}else{? ? ? ? ? ? ? ? manifest.srcFile'src/main/AndroidManifest.xml'//集成開發模式下排除debug文件夾中的所有Java文件java {? ? ? ? ? ? ? ? ? ? exclude'debug/**'}? ? ? ? ? ? }? ? ? ? }? ? }//設置了resourcePrefix值后,所有的資源名必須以指定的字符串做前綴,否則會報錯。//但是resourcePrefix這個值只能限定xml里面的資源,并不能限定圖片資源,所有圖片資源仍然需要手動去修改資源名。//resourcePrefix "girls_"}dependencies {? ? compile fileTree(dir:'libs', include: ['*.jar'])? ? annotationProcessor"com.github.mzule.activityrouter:compiler:$rootProject.annotationProcessor"compile project(':lib_common')}

Main組件除了有業務組件的普遍屬性外,還有一項重要功能

1、Main組件集成模式下的AndroidManifest.xml是跟其他業務組件不一樣的,Main組件的表單中聲明了我們整個Android應用的launch Activity,這就是Main組件的獨特之處;所以我建議SplashActivity、登陸Activity以及主界面都應屬于Main組件,也就是說Android應用啟動后要調用的頁面應置于Main組件。

5、組件化項目的混淆方案

組件化項目的Java代碼混淆方案采用在集成模式下集中在app殼工程中混淆,各個業務組件不配置混淆文件。集成開發模式下在app殼工程中build.gradle文件的release構建類型中開啟混淆屬性,其他buildTypes配置方案跟普通項目保持一致,Java混淆配置文件也放置在app殼工程中,各個業務組件的混淆配置規則都應該在app殼工程中的混淆配置文件中添加和修改。

之所以不采用在每個業務組件中開啟混淆的方案,是因為?組件在集成模式下都被 Gradle 構建成了 release 類型的arr包,一旦業務組件的代碼被混淆,而這時候代碼中又出現了bug,將很難根據日志找出導致bug的原因;另外每個業務組件中都保留一份混淆配置文件非常不便于修改和管理,這也是不推薦在業務組件的 build.gradle 文件中配置 buildTypes (構建類型)的原因。

6、工程的build.gradle和gradle.properties文件

1)組件化工程的build.gradle文件

在組件化項目中因為每個組件的 build.gradle 都需要配置 compileSdkVersion、buildToolsVersion和defaultConfig 等的版本號,而且每個組件都需要用到 annotationProcessor,為了能夠使組件化項目中的所有組件的 build.gradle 中的這些配置都能保持統一,并且也是為了方便修改版本號,我們統一在Android工程根目錄下的build.gradle中定義這些版本號,當然為了方便管理Common組件中的第三方開源庫的版本號,最好也在這里定義這些開源庫的版本號,然后在各個組件的build.gradle中引用Android工程根目錄下的build.gradle定義的版本號,組件化工程的 build.gradle 文件代碼如下:

buildscript {? ? repositories {? ? ? ? jcenter()? ? ? ? mavenCentral()? ? }? ? dependencies {//classpath "com.android.tools.build:gradle:$localGradlePluginVersion"http://$localGradlePluginVersion是gradle.properties中的數據classpath"com.android.tools.build:gradle:$localGradlePluginVersion"}}allprojects {? ? repositories {? ? ? ? jcenter()? ? ? ? mavenCentral()//Add the JitPack repositorymaven { url"https://jitpack.io"}//支持arr包flatDir {? ? ? ? ? ? dirs'libs'}? ? }}task clean(type: Delete) {? ? delete rootProject.buildDir}// Define versions in a single place//時間:2017.2.13;每次修改版本號都要添加修改時間ext {// Sdk and tools//localBuildToolsVersion是gradle.properties中的數據buildToolsVersion = localBuildToolsVersion? ? compileSdkVersion =25minSdkVersion =16targetSdkVersion =25versionCode =1versionName ="1.0"javaVersion = JavaVersion.VERSION_1_8// App dependencies versionsupportLibraryVersion ="25.3.1"retrofitVersion ="2.1.0"glideVersion ="3.7.0"loggerVersion ="1.15"eventbusVersion ="3.0.0"gsonVersion ="2.8.0"photoViewVersion ="2.0.0"http://需檢查升級版本annotationProcessor ="1.1.7"routerVersion ="1.2.2"easyRecyclerVersion ="4.4.0"cookieVersion ="v1.0.1"toastyVersion ="1.1.3"}

2)組件化工程的gradle.properties文件

在組件化實施流程中我們了解到gradle.properties有兩個屬性對我們非常有用:

1、在Android項目中的任何一個build.gradle文件中都可以把gradle.properties中的常量讀取出來,不管這個build.gradle是組件的還是整個項目工程的build.gradle;

2、gradle.properties中的數據類型都是String類型,使用其他數據類型需要自行轉換;

利用gradle.properties的屬性不僅可以解決集成開發模式和組件開發模式的轉換,而且還可以解決在多人協同開發Android項目的時候,因為開發團隊成員的Android開發環境(開發環境指Android SDK和AndroidStudio)不一致而導致頻繁改變線上項目的build.gradle配置。

在每個Android組件的 build.gradle 中有一個屬性:buildToolsVersion,表示構建工具的版本號,這個屬性值對應 AndroidSDK 中的?Android SDK Build-tools,正常情況下 build.gradle 中的 buildToolsVersion 跟你電腦中 Android SDK Build-tools 的最新版本是一致的,比如現在 Android SDK Build-tools 的最新的版本是:25.0.3,那么我的Android項目中 build.gradle 中的 buildToolsVersion 版本號也是 25.0.3,但是一旦一個Android項目是由好幾個人同時開發,總會出現每個人的開發環境 Android SDK Build-tools 是都是不一樣的,并不是所有人都會經常升級更新 Android SDK,而且代碼是保存到線上環境的(例如使用 SVN/Git 等工具),某個開發人員提交代碼后線上Android項目中 build.gradle 中的 buildToolsVersion 也會被不斷地改變。

另外一個原因是因為Android工程的根目錄下的 build.gradle 聲明了 Android Gradle 構建工具,而這個工具也是有版本號的,而且?Gradle Build Tools?的版本號跟 AndroidStudio 版本號一致的,但是有些開發人員基本很久都不會升級自己的 AndroidStudio 版本,導致團隊中每個開發人員的 Gradle Build Tools 的版本號也不一致。

如果每次同步代碼后這兩個工具的版本號被改變了,開發人員可以自己手動改回來,并且不要把改動工具版本號的代碼提交到線上環境,這樣還可以勉強繼續開發;但是很多公司都會使用持續集成工具(例如Jenkins)用于持續的軟件版本發布,而Android出包是需要 Android SDK Build-tools 和 Gradle Build Tools 配合的,一旦提交到線上的版本跟持續集成工具所依賴的Android環境構建工具版本號不一致就會導致Android打包失敗。

為了解決上面問題就必須將Android項目中 build.gradle 中的 buildToolsVersion 和 GradleBuildTools 版本號從線上代碼隔離出來,保證線上代碼的 buildToolsVersion 和 Gradle Build Tools 版本號不會被人為改變。

具體的實施流程大家可以查看我的這篇博文?AndroidStudio本地化配置gradle的buildToolsVersion和gradleBuildTools

7、組件化項目Router的其他方案-ARouter

在組件化項目中使用到的跨組件跳轉庫ActivityRouter可以使用阿里巴巴的開源路由項目:阿里巴巴ARouter

ActivityRouter和ARouter的接入組件化項目的方式是一樣的,ActivityRouter提供的功能目前ARouter也全部支持,但是ARouter還支持依賴注入解耦,頁面、攔截器、服務等組件均會自動注冊到框架。對于大家來說,沒有最好的只有最適合的,大家可以根據自己的項目選擇合適的Router。

下面將介紹ARouter的基礎使用方法,更多功能還需大家去Github自己學習;

1、首先 ARouter 這個框架是需要初始化SDK的,所以你需要在“app殼工程”中的應用Application中加入下面的代碼,注意:在 debug 模式下一定要 openDebug

if(BuildConfig.DEBUG) {//一定要在ARouter.init之前調用openDebugARouter.openDebug();? ? ? ? ? ? ARouter.openLog();? ? ? }? ? ? ARouter.init(this);

2、首先我們依然需要在 Common 組件中的 build.gradle 將ARouter 依賴進來,方便我們在業務組件中調用:

dependencies {? ? compile fileTree(dir:'libs', include: ['*.jar'])//routercompile'com.alibaba:arouter-api:1.2.1.1'}

3、然后在每一個業務組件的 build.gradle 都引入ARouter 的 Annotation處理器,代碼如下:

android {? ? defaultConfig {? ? ...? ? javaCompileOptions {? ? ? ? annotationProcessorOptions {? ? ? ? arguments = [ moduleName : project.getName() ]? ? ? ? }? ? }? ? }}dependencies {? ? compile fileTree(dir:'libs', include: ['*.jar'])? ? annotationProcessor'com.alibaba:arouter-compiler:1.0.3'}

4、由于ARouter支持自動注冊到框架,所以我們不用像ActivityRouter那樣在各個組件中聲明組件,當然更不需要在Application中管理組件了。?我們給 Girls組件 中的 GirlsActivity 添加注解:@Route(path = “/girls/list”),需要注意的是這里的路徑至少需要有兩級,/xx/xx,之所以這樣是因為ARouter使用了路徑中第一段字符串(/*/)作為分組,比如像上面的”girls”,而分組這個概念就有點類似于ActivityRouter中的組件聲明 @Module ,代碼如下:

@Route(path ="/girls/list")publicclassGirlsActivityextendsBaseActionBarActivity{privateGirlsView mView;privateGirlsContract.Presenter mPresenter;@OverrideprotectedintsetTitleId() {returnR.string.girls_activity_title;? ? }@OverrideprotectedvoidonCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);? ? ? ? mView =newGirlsView(this);? ? ? ? setContentView(mView);? ? ? ? mPresenter =newGirlsPresenter(mView);? ? ? ? mPresenter.start();? ? }}

然后我們就可以在項目中的任何一個地方通過 URL地址 : /girls/list, 調用 GirlsActivity,方法如下:

ARouter.getInstance().build("/girls/list").navigation();

1

8、結束語

組件化相比于單一工程優勢是顯而易見的:

組件模式下可以加快編譯速度,提高開發效率;

自由選擇開發框架(MVC /MVP / MVVM /);

方便做單元測試;

代碼架構更加清晰,降低項目的維護難度;

適合于團隊開發;

最后貼出Android組件化Demo地址:Android組件化項目AndroidModulePattern

想要學習更多Android組件化知識,請查看 :Android組件化之終極方案

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容