組件化實施流程
1組件模式和集成模式的轉換
Android Studio中的Module主要有兩種屬性,分別為:
application屬性,可以獨立運行的Android程序,也就是我們的APP;
apply plugin: ‘com.android.application’
library屬性,不可以獨立運行,一般是Android程序依賴的庫文件;
apply plugin: ‘com.android.library’
如何讓組件在這兩種模式之間自動轉換?
解決辦法
第一步 在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'
}
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呢?
解決辦法
為組件開發模式下的業務組件再創建一個 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:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.guiying.girls">
<application android:theme="@style/AppTheme">
<activity
android:name=".main.GirlsActivity"
android:screenOrientation="portrait" />
<activity
android:name=".girl.GirlActivity"
android:screenOrientation="portrait"
android:theme="@style/AppTheme.NoActionBar" />
</application>
</manifest>
我在這個表單中只聲明了應用的主題,而且這個主題還是跟app殼工程中的主題是一致的,都引用了common組件中的資源文件,在這里聲明主題是為了方便這個業務組件中有使用默認主題的Activity時就不用再給Activity單獨聲明theme了。
然后是組件開發模式下的表單文件:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.guiying.girls">
<application
android:name="debug.GirlsApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/girls_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity
android:name=".main.GirlsActivity"
android:screenOrientation="portrait">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".girl.GirlActivity"
android:screenOrientation="portrait"
android:theme="@style/AppTheme.NoActionBar" />
</application>
</manifest>
組件模式下的業務組件表單就是一個Android項目普通的AndroidManifest.xml。
3 全局Context的獲取及組件數據初始化
需求:任何一個業務組件中都要能獲取到全局的 Context,而且這個 Context 不管是在組件開發模式還是在集成開發模式都是生效的。
解決辦法:
在 組件化工程模型圖中,功能組件集合中有一個 Common 組件, 這個組件中主要封裝了項目中需要的基礎功能,并且每一個業務組件都要依賴Common組件,在Common組件中我們封裝了項目中用到的各種Base類,這些基類中就有BaseApplication 類。
BaseApplication 主要用于各個業務組件和app殼工程中聲明的 Application 類繼承用的,只要各個業務組件和app殼工程中聲明的Application類繼承了 BaseApplication,當應用啟動時 BaseApplication 就會被動實例化,這樣從 BaseApplication 獲取的 Context 就會生效,也就從根本上解決了我們不能直接從各個組件獲取全局 Context 的問題;
這時候大家肯定都會有個疑問?不是說了業務組件不能有自己的 Application 嗎,怎么還讓他們繼承 BaseApplication 呢?其實我前面說的是業務組件不能在集成模式下擁有自己的 Application,但是這不代表業務組件也不能在組件開發模式下擁有自己的Application,其實業務組件在組件開發模式下必須要有自己的 Application 類,一方面是為了讓 BaseApplication 被實例化從而獲取 Context,還有一個作用是,業務組件自己的 Application 可以在組件開發模式下初始化一些數據。
但是,實際上業務組件中的Application在最終的集成項目中是沒有什么實際作用的,組件自己的 Application 僅限于在組件模式下發揮功能,因此我們需要在將項目從組件模式轉換到集成模式后將組件自己的Application剔除出我們的項目;在 AndroidManifest 合并問題小節中介紹了如何在不同開發模式下讓 Gradle 識別組件表單的路徑,這個方法也同樣適用于Java代碼;
4 library依賴問題
我們在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 Support
compile "com.android.support:appcompat-v7:$rootProject.supportLibraryVersion"
compile "com.android.support:design:$rootProject.supportLibraryVersion"
compile "com.android.support:percent:$rootProject.supportLibraryVersion"
//網絡請求相關
compile "com.squareup.retrofit2:retrofit:$rootProject.retrofitVersion"
compile "com.squareup.retrofit2:retrofit-mock:$rootProject.retrofitVersion"
compile "com.github.franmontiel:PersistentCookieJar:$rootProject.cookieVersion"
//穩定的
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"
//router
compile "com.github.mzule.activityrouter:activityrouter:$rootProject.routerVersion"
}
5 組件之間調用和通信
解決方案:
ActivityRouter
ActivityRouter支持給Activity定義 URL,這樣就可以通過 URL 跳轉到Activity,并且支持從瀏覽器以及 APP 中跳入我們的Activity,而且還支持通過 url 調用方法。
6 組件之間資源名沖突
在項目中約定資源文件命名規約,比如強制使每個資源文件的名稱以組件名開始,這個可以根據實際情況和開發人員制定規則。當然了萬能的Gradle構建工具也提供了解決方法,通過在在組件的build.gradle中添加如下的代碼:
//設置了resourcePrefix值后,所有的資源名必須以指定的字符串做前綴,否則會報錯。
//但是resourcePrefix這個值只能限定xml里面的資源,并不能限定圖片資源,所有圖片資源仍然需要手動去修改資源名。
resourcePrefix "girls_"
但是設置了這個屬性后有個問題,所有的資源名必須以指定的字符串做前綴,否則會報錯,而且resourcePrefix這個值只能限定xml里面的資源,并不能限定圖片資源,所有圖片資源仍然需要手動去修改資源名;所以我并不推薦使用這種方法來解決資源名沖突。
組件化項目的工程類型
在組件化工程模型中主要有:app殼工程、業務組件和功能組件3種類型,而業務組件中的Main組件和功能組件中的Common組件比較特殊,下面將分別介紹。
1app殼工程
app殼工程是從名稱來解釋就是一個空殼工程,沒有任何的業務代碼,也不能有Activity,但它又必須被單獨劃分成一個組件,而不能融合到其他組件中,是因為它有如下幾點重要功能:
1、app殼工程中聲明了我們Android應用的 Application,這個 Application 必須繼承自 Common組件中的 BaseApplication),因為只有這樣,在打包應用后才能讓BaseApplication中的Context生效。
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中聲明的業務組件,并且不需要再依賴任何功能組件。
2 功能組件和Common組件
功能組件是為了支撐業務組件的某些功能而獨立劃分出來的組件,功能實質上跟項目中引入的第三方庫是一樣的,功能組件的特征如下:
1、功能組件的 AndroidManifest.xml 是一張空表,這張表中只有功能組件的包名;
2、功能組件不管是在集成開發模式下還是組件開發模式下屬性始終是: com.android.library,所以功能組件是不需要讀取 gradle.properties 中的 isModule 值的;另外功能組件的 build.gradle 也無需設置 buildTypes ,只需要 dependencies 這個功能組件需要的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應用的界面風格統一。
3 業務組件和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,以及依賴其他用到的功能組件。
Main組件除了有業務組件的普遍屬性外,還有一項重要功能:
工程的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 文件代碼如下:
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組件化方案
來源:https://www.cnblogs.com/ldq2016/
著作權歸作者所有,任何形式的轉載都請聯系作者獲得授權并注明出處。