前言
關於什麼是元件化、為什麼要進行元件化以及實施元件化的基本流程網上一搜一大把,這裡不做過多說明,不瞭解的話可以Google一下。這裡主要記錄一下元件化開發的一些心得和踩的一些坑。
先看一下專案結構圖
結構很簡單,有一個公共的基礎module類commonlibrary來處理一些公共的東西,比如第三方庫的依賴,基類封裝,工具類等。中間層是各個獨立的業務模組,各個模組之間互相隔離。最下面是app的殼,主要配置簽名打包什麼的。具體可以看一下demo。元件化實施步驟
1、設定module是否作為元件的開關
在gradle.properties檔案裡定義一個常量IsBuildApp = false,表示是否把元件module作為單獨的app執行。定義好了這個常量後,在專案的任何一個gradle檔案裡都可以讀取到這個值,那麼就用這個值來作為module元件是否需要單獨執行的開關。
// 在module元件的gradle裡配置如下,gradle.properties 中的資料型別都是String型別,這裡需要做一下轉換
if (IsBuildApp.toBoolean()){
apply plugin: 'com.android.application'
}else {
apply plugin: 'com.android.library'
}
複製程式碼
2、元件module的清單檔案AndroidManifest合併問題
我們知道android的四大元件、許可權等都是需要註冊的,當module單獨執行的時候,肯定需要一個清單檔案註冊元件和申請許可權,但是當module作為app的一個子元件存在的時候,清單檔案是要合併到app的殼工程中的,這個時候如果每個module都有自己的啟動頁面和自定義application的話,就會引起衝突。
為了解決這個問題,那就需要根據module是否需要單獨執行來配置不同的清單檔案。在java同級目錄新建independent目錄,在此目錄下建立專案module需要單獨執行的清單檔案和application。然後在module的gradle檔案裡指定清單檔案路徑,程式碼如下:
// 在android領域裡指定清單檔案的路徑
sourceSets {
main {
if (IsBuildApp.toBoolean()) {
// 單獨作為app執行的清單檔案,這裡可以新增啟動頁面、自定義application等。
manifest.srcFile 'src/main/independent/AndroidManifest.xml'
} else {
// 作為元件的清單檔案
manifest.srcFile 'src/main/AndroidManifest.xml'
//release模式下排除independent資料夾中的所有Java檔案
java {
exclude 'independent/**'
}
}
}
}
複製程式碼
這樣配置完成以後,作為元件的清單檔案是不能有自己的啟動頁面、application、appname等屬性的,下面看一下完整的配置:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.article.demos.vue">
<application android:theme="@style/AppTheme">
<activity android:name=".ui.VueActivity" />
</application>
</manifest>
複製程式碼
下面看一下獨立執行模式下的清單檔案:
// 作為獨立app執行的清單檔案,注意這裡我設定了主題,不然的話會報錯。
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.article.demos.main">
<application android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
複製程式碼
獨立執行的話,就和正常的app清單檔案一樣,要有啟動頁面,application標籤可以新增label、icon、自定義application等,就不多說啦。
3、全域性Application的問題
在commonlibrary中建立自定義application,因為其他的module都依賴這個module,所以其他的module都可以獲取到這個全域性的application。另外,元件在獨立執行模式下的application,繼承我們自定義這個BaseApplication就可以了。因為我們在release模式下,排除了所有independent資料夾下的java檔案,所以作為元件執行時,並不會產生application的衝突,配置如下:
sourceSets {
main {
if (IsBuildApp.toBoolean()) {
manifest.srcFile 'src/main/independent/AndroidManifest.xml'
} else {
manifest.srcFile 'src/main/AndroidManifest.xml'
//release模式下排除independent資料夾中的所有Java檔案
java {
exclude 'independent/**'
}
}
}
}
複製程式碼
4、重複依賴三方庫的問題
為了避免重複依賴三方庫的問題,我們的三方庫依賴統一放在commonlibrary的module中,這樣既可以避免重複依賴,又方便管理。然後我們在app的module裡,如下引用即可:
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
if (IsBuildApp.toBoolean()) {
implementation project(':commonlibrary')
} else {
implementation project(':androidmodule')
implementation project(':vuemodule')
implementation project(':kotlinmodule')
implementation project(':javamodule')
}
}
複製程式碼
5、資源衝突問題
資源衝突主要是指各個module裡的資原始檔名衝突的問題,如果命名一樣,合併的時候便會產生衝突。
解決衝突主要有兩個解決方案,一個是約定規則,比如資源名約定都以module名開頭。
方案二是通過gradle指令碼來設定,在各個元件的gradle檔案裡新增如下程式碼:
resourcePrefix "module名稱_"
複製程式碼
但是這種配置有限制,比如只能限定xml裡的資源,所以並不推薦這種方式。
6、元件間跳轉
因為元件是相互隔離的,我們並不能顯式跳轉,這裡我們選用阿里巴巴的Arouter路由跳轉,專案的地址github.com/alibaba/ARo…。
這裡需要特別說明一下,需要跳轉的目標module需要引入arouter的註解處理器,否則無法處理router註解會出現路徑不匹配的問題:
annotationProcessor 'com.alibaba:arouter-compiler:1.1.4'
複製程式碼
同時,改module的defaultconfig裡也別忘記配置moduleName
javaCompileOptions {
annotationProcessorOptions {
arguments = [ moduleName : project.getName() ]
}
}
複製程式碼
7、跨module互動
跨moduel互動一般是指module間通訊和module間的相互呼叫。module間通訊這裡選用eventbus,很簡單,就不過多說明了。
下面說一下同級module直接的通訊,比如我在任何一個頁面要呼叫loginModule裡的微信登入方法,因為各個module是互相獨立的,互不依賴,想要直接呼叫基本不可能。目前網上發現有兩種解決方案,一個是寫一個反射工具類,通過反射獲取到要呼叫的類,然後呼叫相應的方法。另一個是通過commonModule做一下橋接,瞭解更多可以參考這裡。不過感覺用Arouter能更優雅的實現,下面具體講一下利用arouter來實現。
首先,在公共module裡建立一個介面IService
public interface IService extends IProvider{
String wxLogin();
}
複製程式碼
介面裡定義一個微信登入的虛擬碼,然後在我們的登入元件裡,實現該介面並新增route註解
@Route(path = Constant.WX_LOGIN)
public class WxTest implements IService{
@Override
public void init(Context context) {
}
@Override
public String wxLogin() {
return "wxlogin";
}
}
複製程式碼
其中 Constant.WX_LOGIN是我定義的一個字串常量
public static final String WX_LOGIN = "/wx/login";
複製程式碼
以上兩步就把工作做完了,下面只需要在需要呼叫的頁面呼叫登入就行了。首先,我們獲取到IService
/**
* 推薦使用方式二來獲取IService
*/
// IService iService = (IService) ARouter.getInstance().build(Constant.WX_LOGIN).navigation();
IService iService = ARouter.getInstance().navigation(IService.class);
複製程式碼
拿到IService後,就可以放心大膽的呼叫登入方法就行了。
mBinding.btLogin.setOnClickListener(v -> {
String s = iService.wxLogin();
Toast.makeText(getContext(), s, Toast.LENGTH_SHORT).show();
});
複製程式碼
8、fragment的元件化
一般的專案首頁都是一個activity和多個fragment組成。由於元件間的隔離,我們在首頁裡怎麼獲取到其他元件裡的fragment呢?開篇的兩個參考文章分別使用了兩種不同的方式,有興趣的朋友可以看看。各有利弊吧,一個是查詢所有,太耗時。一個是直接反射獲取,但是好像有點違背元件隔離,需要知道fragment的全路徑。
這裡我參考了《Android元件化架構》一書,使用arouter來獲取。其實三種方式獲取的原理一樣,都是通過反射。我們看一下arouter的註解的原始碼就知道:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.CLASS)
public @interface Route {……}
複製程式碼
可以看到Route註解的retention是CLASS,也是通過反射來獲取。
9、遇到的一些坑
(1)使用dataBinding的話,每個module的gradle檔案裡都要加上dataBinding的支援,否則無法生成相應的binding類
// 每個module都加上dataBinding的支援,否則無法生成相應的binding類
dataBinding {
enabled = true
}
複製程式碼
(2)java8的支援一樣要每個module都要單獨配置
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
複製程式碼
(3)升級到as 3.1.2後,出現無法訪問TaskStackBuilder的問題
檢查一下你的support包,將你的support包更新到27或以上即可。
(4)如果使用有自定義註解annotation的話,如果編譯報錯 Annotation processors must be explicitly declared now...,那麼在commonlibrary的gradle檔案的defaultConfig裡新增如下程式碼:
// Annotation processors must be explicitly declared now
javaCompileOptions { annotationProcessorOptions { includeCompileClasspath = true } }
複製程式碼
(5)如果你元件化開發,子module中無法使用butterknife的話,網上自行搜解決方案吧(?♀️)
關於為何出現這個問題,推薦一篇博文R.java、R2.java是時候懂了
(6)其他問題本篇部落格會持續更新……
2018年6.15更新………………………………………………………… 編譯報錯
Multiple dex files define Lcom/alibaba/android/arouter/routes/ARouter$$Group$$module
複製程式碼
一般網上說是依賴版本衝突,其實這個問題是不同module之間有相同分組導致的問題,比如a模組 path = "/message/a",b模組 path = "/message/b",有相同的message分組,修改成不一樣的就可以了。
2018.8.20更新…………………………………………………………
最近在用kotlin和java混合開發,發現原有java頁面跳轉新寫的kotlin頁面 arouter 頁面跳轉的時候報異常提示 There is no route match the path……,此時參考官方文件即可解決,
// 在kotlin的module中新增外掛
apply plugin: 'kotlin-kapt'
// 依賴裡 使用kapt 引用
dependencies {
compile 'com.alibaba:arouter-api:x.x.x'
kapt 'com.alibaba:arouter-compiler:x.x.x'
...
}
複製程式碼
2018.8.22更新…………………………………………………………
遇到了一個很蛋疼的問題,在純java寫的module裡通過arouter跳轉到另一個module裡的kotlin頁面的時候,發現setcontentview方法無效,頁面什麼都不顯示。除錯了半天,發現是頁面的xml佈局檔案和一個空的xml佈局檔案重名了,導致kotlin頁面載入了空頁面的佈局,在此記錄一下,好尷尬。
最後附上完整的demo地址,如果對你有幫助麻煩start鼓勵一下,你的鼓勵是我前進的動力。