元件化下EventBus的訊息型別自動編譯

juexingzhe發表於2019-03-06

今天這個題目很難取,元件化範疇的內容,又是EventBus的相關內容,但是通過gradle外掛化的形式來做,簡單就說成元件化下EventBus的訊息型別自動編譯,有更好主意的小夥伴可以推薦。先說下背景,元件化過程中肯定涉及到資料的傳遞問題,EventBus這個庫是比較流行的方式,用過EventBus的小夥伴都知道需要先定義一個訊息型別。在元件化工程裡面一般都會把訊息型別定義到公共模組,這樣跨元件才能傳遞。這樣做能實現目的,但是有兩個弊端:

  • 需要手動去更改公共庫,這就給公共庫帶來一定的風險,越來越多的程式碼往公共庫中新增會造成後期很難維護,元件化的邊界難於界定,公共庫需要保持穩定
  • 各元件之間的拆分也不是很乾淨,如果修改元件通訊內容的同時需要修改公共庫中的內容,如果元件模組A的開發看不到公共庫中原始碼呢?就捉瞎了

所以我們的目的是,元件需要接受或者傳送的訊息型別直接在自己元件範圍內定義,不需要去修改元件外的任何程式碼,修改越少,bug就越少,就能越早下班不是嗎?

1.結果

先看下整個專案結構

專案結構.png
分成app/event/modulea/moduleb四個部分 其中app中傳送訊息,a和b可以接收到,a和b中的內容大同小異,就只看下a中的程式碼,一個 ADisplay用於顯示接收到的訊息,一個ModuleaMsg用於定義訊息型別。

螢幕快照 2019-03-05 下午4.56.41.png

看下 ADisplay中的程式碼,要接受的是ModuleaMsg訊息:

@Subscribe(threadMode = ThreadMode.MAIN)
public void onMessageEvent(ModuleaMsg msg) {
      System.out.println("Module A received msg = " + msg.msg);
      EventBus.getDefault().unregister(this);
}
複製程式碼

ModuleaMsg就是訊息型別?在a元件中登記,寫一個以.event結尾的檔案,在裡面寫入程式碼就可

螢幕快照 2019-03-05 下午4.58.14.png

這樣就完成了元件需要完成的全部工作了,完全不需要修改其他的元件。

在傳送的地方,我們這裡直接在app元件中傳送訊息,

        ModuleaMsg moduleaMsg = new ModuleaMsg();
        moduleaMsg.msg = "send from app";
        EventBus.getDefault().post(moduleaMsg);

        ModulebMsg modulebMsg = new ModulebMsg();
        modulebMsg.num = 10086;
        EventBus.getDefault().post(modulebMsg);
複製程式碼

modulea和moduleb接收到訊息:

Module A received msg = send from app
Module B received msg = 10086
複製程式碼

上面就是全部的使用方式了,如果還有興趣的小夥伴接著往下看實現原理了。

2.實現方式

其實就是通過gradle外掛的方式實現,需要了解怎麼定義gradle外掛的可以參考我之前的一步步自定義Gradle外掛,這裡就不再贅述,我們直接看下外掛裡面的程式碼, 主要任務都在project.afterEvaluate中完成,

project.afterEvaluate {

            def eventModule = project.rootProject.childProjects.get(project.EventPluginExt.eventModuleName)
            if (eventModule == project) {
                throw new GradleException('event module should not be itself!')
            }

            if (eventModule == null || !eventModule.projectDir.exists()) {
                throw new GradleException('event module is not exists')
            }

            def cleanEventTask = project.task("eventClean", type: Delete) {
                delete("${project.rootProject.projectDir}/${eventModule.name}/src/main/java")
            }

            project.tasks.getByName("clean").dependsOn cleanEventTask
            ...
}
複製程式碼

首先判斷工程中是否有project.EventPluginExt.eventModuleName (= event)這個模組,在第一張圖中可以看到有個event這個模組,裡面包含了所有的訊息型別

螢幕快照 2019-03-05 下午5.13.12.png

然後定義一個清除元件下舊的.event字尾的檔案,優先於gradle cleantask。

接著往下看,接著會拿到所有構件體,分成兩種,lib模組或者application模組

getVariants().all { variant ->
                def variantName = Utils.captureName(variant.name)
                System.out.println("variantName is " + variantName)
                if (isLibrary()) {
                } else {
                }
}

    private boolean isLibrary() {
        return project.android.hasProperty('libraryVariants')
    }
複製程式碼

先看下lib模組下面的程式碼,分成三部分,先看下第一部分:

def intoAsset
def assetDir = Utils.getOneAssetDir(variant)
if (assetDir == null) {
     intoAsset = "${project.projectDir}/src/main/assets/$eventFolder"
} else {
     intoAsset = assetDir.absolutePath + "/$eventFolder"
}

def intoEventSdk = "${project.rootProject.projectDir}/${eventModule.name}/src/main/java"

List list = new ArrayList()
variant.sourceSets.each { sourceSet ->
sourceSet.java.srcDirs.each { file ->
list.add(file)
}
}

def javaSrcList = list as String[]
複製程式碼

首先拿到lib裡面的assets目錄,是為了後面把所有lib中的.event檔案都儲存到apk中的assets目錄中的eventFolder資料夾;然後定義根目錄下event元件下的檔案位置,最後拿到lib下的所有程式碼檔案。

接著看下第二部分,主要是定義任務task:

  1. eventLibCopyTask是拷貝任務,把前面拿到的javaSrcList.event字尾的檔案複製到event模組下,並且修改字尾名為.java, 在執行preBuild前生成
  2. 拿到build過程的第一個task---preBuild
  3. 定義拷貝任務eventAssetsCopyTask,負責負責src/javasrc//.event->assets/cocoFolder//.event
  4. 獲取generateAssets任務
  5. 獲取mergeAssets任務
  6. 獲取當前variant編譯任務——如assembleDebug/assembleRease
  7. 定義刪除任務-刪除的是lib下assets中的delete assets/cocoFolder//.event檔案

Task eventLibCopyTask = project.task("eventLibCopyTaskFor" + variantName, type: Copy) {
                        includeEmptyDirs = false
                        from javaSrcList
                        into intoEventSdk
                        include "**/*.${project.EventPluginExt.postFix}"
                        rename "(.*)${project.EventPluginExt.postFix}", '$1java'
}


Task preBuildTask = project.tasks.getByName("pre" + variantName + "Build")

Task eventAssetsCopyTask = project.task("eventAssetsCopyTaskFor" + variantName, type: Copy) {
                        includeEmptyDirs = false
                        from javaSrcList
                        into intoAsset
                        include "**/*.${project.EventPluginExt.postFix}"
}

Task generateAssetsTask = project.tasks.getByName("generate" + variantName + "Assets")

Task mergeAssetsTask = variant.mergeAssets

Task assembleTask = variant.assemble

Task eventAssetsDeleteTask = project.task("eventAssetsDeleteFor" + variantName, type: Delete) {
                        delete intoAsset
}
複製程式碼

第三部分就是定義上面幾個任務的執行順序,第一步肯定是要拷貝.event檔案到event模組下面,幾個任務順序:

1) 拷貝lib中event到event中
2) preBuild
3) 拷貝lib中event到assets中
4) generateAssets
5) mergeAssets
6) 刪除assets中的event檔案
7) assemble
複製程式碼
preBuildTask.dependsOn eventLibCopyTask

generateAssetsTask.dependsOn eventAssetsCopyTask
mergeAssetsTask.finalizedBy eventAssetsDeleteTask
assembleTask.finalizedBy eventAssetsDeleteTask

// 新增event的預編譯依賴,避免event模組在編譯完成之後再執行當前project的event拷貝任務,導致類查詢不到的問題
def preEventBuildTask = null
try{
       preEventBuildTask = eventModule.tasks.getByName("preBuild")
} catch (Exception ignored) {

}

if(preEventBuildTask != null) {
       preEventBuildTask.mustRunAfter eventLibCopyTask
}
複製程式碼

上面就是lib模組的程式碼邏輯,接下來看下application模組的相關邏輯,application模組會有依賴的aar,aar裡面可能有.event的檔案,所以需要從依賴中迴圈拿到這些檔案拷貝到event模組中

分成兩個步驟,先看下第一個步驟,就是從aar依賴中把.event字尾的檔案移動到專案目錄的event 模組中

Task appUnpackTask = null
if (variant.hasProperty("compileConfiguration")) {
       // For Android Gradle plugin >= 2.5
      Attribute artifactType = Attribute.of("artifactType", String)
     FileCollection classPathConfig = variant.compileConfiguration.incoming.artifactView {
             attributes {
                 it.attribute(artifactType, "aar")
             }
}.files
System.out.println("classPathConfig = " + classPathConfig)

appUnpackTask = project.task("eventAppUnpack" + variantName, type: Copy) {
          includeEmptyDirs = false
          classPathConfig.each {
                from(project.zipTree(it))
          }
          into "${project.rootProject.projectDir}/${eventModule.name}/src/main/java"
          include "**/$eventFolder/**/*.${project.EventPluginExt.postFix}"
          rename "(.*)${project.EventPluginExt.postFix}", '$1java'
          eachFile {
                it.path = it.path.replaceFirst(".*$eventFolder", '')
          }
}
} 
複製程式碼

第二個步驟就是把app模組下的字尾檔案拷貝到event 模組中,邏輯和前面lib模組的邏輯一樣的,實現效果就是javasrc/*.event->event/src/javasrc/*/*.java就不重複介紹了

List list = new ArrayList()
variant.sourceSets.each { sourceSet ->
sourceSet.java.srcDirs.each { file ->
           list.add(file)
 }
}
def javaSrcList = list as String[]
System.out.println("javaSrcList is " + javaSrcList)

def intoEventSdk = "${project.rootProject.projectDir}/${eventModule.name}/src/main/java"

Task eventAppCopyTask = project.task("eventAppCopyTaskFor" + variantName, type: Copy) {
                        includeEmptyDirs = false
                        from javaSrcList
                        into intoEventSdk
                        include "**/*.${project.EventPluginExt.postFix}"
                        rename "(.*)${project.EventPluginExt.postFix}", '$1java'
}
if (appUnpackTask != null) {
     Task preBuildTask = project.tasks.getByName("pre" + variantName + "Build")
     preBuildTask.dependsOn(appUnpackTask)
     preBuildTask.dependsOn(eventAppCopyTask)

     eventModule.tasks.getByName("preBuild").mustRunAfter appUnpackTask
    eventModule.tasks.getByName("preBuild").mustRunAfter eventAppCopyTask
}
複製程式碼

3.總結

上面就是這個外掛的全部內容了,主要就是完成訊息型別的自動編譯,可以輔助元件化更好的解耦,元件開發人員不需要去約定或者修改基礎庫才能達到資料傳輸的目的。有個需要注意的地方就是目前只支援模組是aar的包,jar包還不支援。

本文也是元件化的一部分內容,後面會再更新一些在大廠中用到的元件化知識,歡迎關注。

完。

相關文章