自動檢測ARouter路由地址分組使用衝突問題

HappyCorn發表於2019-05-24

背景

專案中使用ARouter進行路由,由於不同上層業務模組都能都會使用到同一目標的路由地址,因此,將所有業務模組路由地址以一種類似靜態常量的方式設定在Base模組中。這樣,在實際目前上加上對應此地址的註解,就可以將其對應加入到路由中。使用方通過ARouter對應的地址方式去路由,即可訪問到對應的目標。

以Activity路由為例,通過註解,編譯後在對應模組路徑下生成的檔名為Arouter$$Group$$GroupName1.java檔案。其中GroupName1為分組名。 具體路徑為:

/build/generated/source/kapt/變體/com/alibaba/android/arouter/routes/
複製程式碼

原則上,不同模組應該註解到不同的路由地址分組。否則不同的模組下編譯後會生成相同的Arouter$$Group$$GroupName1.java檔案,在專案構建安裝後,會發生不可預期的路由地址失敗問題(如其中一個Arouter$$Group$$GroupName1.java檔案中的路由生效,另一個直接路由失敗)。

網上查了下,發現同樣問題,其他人也有遇到,具體問題描述GitHub上ARouter專案中issues等: github.com/alibaba/ARo…
github.com/alibaba/ARo…
github.com/alibaba/ARo…

近期,專案中在進行模組化改進時,由於部分註解了路由地址的目標檔案被同事從一個模組移動到另一個模組,匯入出現同樣問題發。原因在於,如果直接通過Android Studio中的三角形綠色圖示直接run android app時,對於不同模組下生成的同名java檔案在編譯及打包組裝過程中是不會提示如下資訊的:

* What went wrong:
Execution failed for task ':app:transformClassesWithJarMergingForDevDebug'.
> com.android.build.api.transform.TransformException: java.util.zip.ZipException: duplicate entry: com/alibaba/android/arouter/routes/ARouter$$Group$$trans_second.class
複製程式碼

但如果直接通過./gradlew命令方式構建,是可以直接出現如上錯誤提示的。大多數情況下,專案開發中直接通過run構建,使得此問題直接被隱藏。


分析與解決

對比兩者實際上構建流程上的差異,發現通過./gradlew命令方式構建,在執行taskapp:transformClassesWithJarMergingForDevDebug時會丟擲duplicate entry class錯誤,但Android Studio直接run則沒有執行。

為了相容Android Studio直接run形式,在開發人員開發階段早些發現此類問題,可以自己實現一個task,加入到構建過程中的適當階段,以自動檢測在不同模組下ARouter生成的檔案重複問題。

具體思路:
分別並統計專案中各個模組中ARouter生成的java檔案,並計次數(次要區分構建型別,一個構建型別算唯一的一次即可,否則對於多個變體情況下會重複計算),對於同一構建型別,對於同樣的檔名,生成的檔案次數多餘1,顯然應該直接構建失敗,並給出具體提示。

實現:
1,首先自定義task,實現次數檢測和統計:

task checkARouterDuplicatedJavaFiles {
    doLast {
        def fileMap = [:]
        def buildTypeList = []
        def hasPathBuildTypeList = []

        project.extensions.findByName("android").applicationVariants.all { variant ->
            def buildTypeName = variant.buildType.name
            if(!buildTypeList.contains(buildTypeName)) {
                buildTypeList.add(buildTypeName)
            }
        }

        project.rootProject.subprojects { subProject ->
            def subProjectBuildDir

            try {
                subProjectBuildDir = subProject.buildDir

                if (subProjectBuildDir == null) return

                subProjectBuildDir.eachFileRecurse(FileType.DIRECTORIES) { dir ->
                    if (dir.path.contains("/generated/source/kapt/") && dir.path.endsWith("com/alibaba/android/arouter/routes")) {
                        def filePrefix = ""
                        for (buildType in buildTypeList) {
                            if(dir.path.toLowerCase().contains(buildType + "/")
                                    && !hasPathBuildTypeList.contains(subProject.getName() + "/generated/source/kapt/" + buildType + "/")){

                                filePrefix = buildType + "/"
                                dir.eachFile(FileType.FILES) { file ->
                                    if (fileMap[filePrefix + file.name] == null) {
                                        fileMap[filePrefix + file.name] = 0
                                    }
                                    fileMap[filePrefix + file.name]++
                                }

                                hasPathBuildTypeList.add(subProject.getName() + "/generated/source/kapt/" + filePrefix)
                                return
                            }
                        }
                    }
                }
            } catch (Exception e) {
                // ignore
                println e.toString()
            }
        }

        fileMap.each { key, value ->
            if (value > 1) {
                throw new GradleException("ARouter: " + key + " fileCount: " + value + " ,路由地址設定有誤!")
            }
        }
    }
}
複製程式碼

上述程式碼中的hasPathBuildTypeList邏輯是因為app module中的變體與library module中的變體設定不一樣,以處理對應的相容邏輯。

2,將此task加入到構建流程的適當階段。通過對比實際的構建過程中執行的task列表,最終決定將名稱含有“package”的task依賴自定義的checkARouterDuplicatedJavaFilestask,並將自定義的task依賴名稱含有“transformClassesWithCom.alibaba.arouter”的task。 具體實現為:

project.tasks.whenTaskAdded { Task task ->
    if (task.name.contains("package")) {
        task.dependsOn(checkARouterDuplicatedJavaFiles)
    } else if (task.name.contains("transformClassesWithCom.alibaba.arouter")) {
        checkARouterDuplicatedJavaFiles.dependsOn(task)
    }
}
複製程式碼

最終可以確保自定義的checkARouterDuplicatedJavaFilestask可以在構建過程中完成對應的檢測。 如果通過./gradlew命令構建,依然可以達到以系統taskapp:transformClassesWithJarMergingForDevDebug為先。

通過Android Studio run,如果重現此類情形,最終效果為:

org.gradle.api.tasks.TaskExecutionException: Execution failed for task ':app:checkARouterDuplicatedJavaFiles'.
	at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeActions(ExecuteActionsTaskExecuter.java:100)
	...
	at java.lang.Thread.run(Thread.java:745)
Caused by: org.gradle.api.GradleException: ARouter: release/ARouter$$Group$$trans_second.java fileCount: 2 ,路由地址設定有誤!
	at build_4p6esrqwzg61igroldd1aht2w$_run_closure5$_closure42.doCall(/Users/corn/AndroidStudioProjects/MyCorn/app/build.gradle:395)
	...

複製程式碼

構建失敗,並給出提示。


結語

ARouter官方建議在不同的模組下本就不應該使用同樣的分組,分組名可以使用模組名或其他名稱,分組名與模組本質上是一種對映關係。但無論如何設定,考慮到其他模組需要使用到此路由地址,當這種對映關係與模組化結合時,這一可能存在衝突的矛盾直接在分組名的定義上,通過技術手段是無法直接隔離開的。因此,考慮通過自定義task並加入到構建過程中的適當階段,以完成潛在的可能的人為失誤,是一種有效方案。


作者:HappyCorn
連結:https://juejin.im/post/5ce7df33518825767072b5ef
來源:掘金
著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。

相關文章