自动检测ARouter路由地址分组使用冲突问题

背景

项目中使用ARouter进行路由,因为不一样上层业务模块均可能会使用到同一目标的路由地址,所以,将全部业务模块的路由地址以一种相似静态常量的方式设置在Base模块中。这样,在实际目前上加上对应此地址的注解,就能够将其对应加入到路由中。使用方经过ARouter对应的地址方式去路由,便可访问到对应的目标。java

以Activity路由为例,经过注解,编译后在对应模块路径下生成的文件名为Arouter$$Group$$GroupName1.java文件。其中GroupName1为分组名。 具体路径为:android

/build/generated/source/kapt/变体/com/alibaba/android/arouter/routes/
复制代码

原则上,不一样模块应该注解到不一样的路由地址分组。不然不一样的模块下编译后会生成相同的Arouter$$Group$$GroupName1.java文件,在项目构建安装后,会发生不可预期的路由地址失败问题(如其中一个Arouter$$Group$$GroupName1.java文件中的路由生效,另外一个直接路由失败)。git

网上查了下,发现一样问题,其余人也有遇到,具体问题描述GitHub上ARouter项目中issues等: github.com/alibaba/ARo…
github.com/alibaba/ARo…
github.com/alibaba/ARo…github

近期,项目中在进行模块化改进时,因为部分注解了路由地址的目标文件被从一个模块移动到另外一个模块,导入出现一样问题发。缘由在于,若是直接经过Android Studio中的三角形绿色图标直接run android app时,对于不一样模块下生成的同名java文件在编译及打包组装过程当中是不会提示以下信息的:api

* 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构建,使得此问题直接被隐藏。bash


分析与解决

对比二者实际上构建流程上的差别,发现经过./gradlew命令方式构建,在执行taskapp:transformClassesWithJarMergingForDevDebug时会抛出duplicate entry class错误,但Android Studio直接run则没有执行。app

为了兼容Android Studio直接run形式,在开发人员开发阶段早些发现此类问题,能够本身实现一个task,加入到构建过程当中的适当阶段,以自动检测在不一样模块下ARouter生成的文件重复问题。模块化

具体思路:
分别统计项目中各个模块中ARouter生成的java文件,并计次数(须要区分构建类型,一个构建类型算惟一的一次便可,不然对于多个变体状况下会重复计算),对于同一构建类型,一样的文件名,生成的文件次数多余1,显然应该直接构建失败,并给出具体提示。gradle

实现:
1,首先自定义task,实现次数检测和统计:ui

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列表,最终决定将名称为“assembleDevDebug”的task依赖自定义的checkARouterDuplicatedJavaFilestask,并将自定义的task依赖名称为“transformClassesWithCom.alibaba.arouterForDevDebug”的task。 具体实现为:

project.tasks.whenTaskAdded { Task task ->
    if (task.name == "assembleDevDebug") {
        task.dependsOn(checkARouterDuplicatedJavaFiles)
    } else if (task.name == "transformClassesWithCom.alibaba.arouterForDevDebug") {
        checkARouterDuplicatedJavaFiles.dependsOn(task)
    }
}
复制代码

其中,DevDebug为咱们开发环境下默认的构建变体。

最终能够确保自定义的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$$GroupName1.java fileCount: 2 ,路由地址设置有误!
	at build_4p6esrqwzg61igroldd1aht2w$_run_closure5$_closure42.doCall(/Users/corn/AndroidStudioProjects/MyCorn/app/build.gradle:395)
	...

复制代码

构建失败,并给出提示。


结语

ARouter官方建议在不一样的模块下本就不该该使用一样的分组,分组名可使用模块名或其余名称,但分组名与模块本质上是一种映射关系,不管如何设置,考虑到其余模块须要使用到此路由地址,当这种映射关系与模块化相结合时,这一可能存在冲突的矛盾直接在分组名的定义上,经过技术手段是没法直接彻底隔离开的。所以,考虑经过自定义task并加入到构建过程当中的适当阶段,以自动检测潜在的可能的人为失误,是一种有效方案。

相关文章
相关标签/搜索