美文网首页
Android--利用APT+kotlinpoet实现组件化开发

Android--利用APT+kotlinpoet实现组件化开发

作者: aruba | 来源:发表于2021-11-24 10:12 被阅读0次

    上一篇我们使用了一个全局Map缓存来所有的Activity类,显然这是非常麻烦的,一旦有所改动,就要手动修改该Map

    为此,我们希望将key和Activity类的映射关系,通过一定方式自动导入Map。利用注解解析器(APT)和代码生成器(kotlinpoet)可以根据注解在编译期间就生成相应的代码,业界称之为Router机制

    一、Gradle配置及架构分层

    在实现Router机制之前,我们还可以对项目的组织架构进行优化,将gradle中公用部分抽出来
    有了上一篇的基础,我们初步实现了架构分层,目前有三个module:


    其依赖关系为:app << libmodule_a << libase,但是每个module的gradle中都有重复的内容,如版本号、版本名、SDK版本、重复依赖等,我们可以利用groovy和gradle的知识,为它们设计成共用属性
    1.创建config.gradle

    在工程下新建一个config.gradle文件



    将重复的内容设置成全局属性:

    ext {
        isDebug = false
    
        kotlinVersion = "1.5.31"
    
        // 版本信息
        androidVersion = [
                compileSdk : 31,
                minSdk     : 21,
                targetSdk  : 31,
                versionCode: 1,
                versionName: '1.0'
        ]
    
        applicationId = [
                app     : "com.aruba.arouterapplication",
                module_a: "com.aruba.libmodule_a"
        ]
    
        androidxCore = 'androidx.core:core-ktx:1.3.2'
        androidAppCompat = 'androidx.appcompat:appcompat:1.2.0'
        androidMaterial = 'com.google.android.material:material:1.3.0'
        androidConstraintLayout = 'androidx.constraintlayout:constraintlayout:2.0.4'
    }
    
    2.在主工程Gradle中,引入config.gradle
    apply from: 'config.gradle'
    
    buildscript {
        repositories {
    ...
    
    3.改造module的gradle

    libbase的gradle改造为:

    plugins {
        id 'com.android.library'
        id 'kotlin-android'
    }
    
    //使用一个变量,减少代码量
    def config = rootProject.ext
    
    android {
        compileSdk config.androidVersion.compileSdk
    
        defaultConfig {
            minSdk config.androidVersion.minSdk
            targetSdk config.androidVersion.targetSdk
            versionCode config.androidVersion.versionCode
            versionName config.androidVersion.versionName
    
            testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
            consumerProguardFiles "consumer-rules.pro"
        }
    
        buildTypes {
            release {
                minifyEnabled false
                proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
            }
        }
        compileOptions {
            sourceCompatibility JavaVersion.VERSION_1_8
            targetCompatibility JavaVersion.VERSION_1_8
        }
        kotlinOptions {
            jvmTarget = '1.8'
        }
    }
    
    dependencies {
        api androidxCore
        api androidAppCompat
        api androidMaterial
        api androidConstraintLayout
        testImplementation 'junit:junit:4.+'
        androidTestImplementation 'androidx.test.ext:junit:1.1.2'
        androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
    }
    

    libmoudle_a的gradle改造为:

    def config = rootProject.ext
    
    if (!config.isDebug) {
        apply plugin: 'com.android.library'
    } else {
        apply plugin: 'com.android.application'
    }
    apply plugin: 'kotlin-android'
    
    android {
        compileSdk config.androidVersion.compileSdk
    
        defaultConfig {
            if (config.isDebug) {
                applicationId config.applicationId.module_a
            }
            minSdk config.androidVersion.minSdk
            targetSdk config.androidVersion.targetSdk
            versionCode config.androidVersion.versionCode
            versionName config.androidVersion.versionName
    
            testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        }
    
        //根据变量配置不同manifest
        sourceSets {
            main {
                if (!config.isDebug) {
                    manifest.srcFile 'src/main/manifest/AndroidManifest.xml'
                } else {
                    manifest.srcFile 'src/main/AndroidManifest.xml'
                }
            }
        }
    
        buildTypes {
            release {
                minifyEnabled false
                proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
            }
        }
        compileOptions {
            sourceCompatibility JavaVersion.VERSION_1_8
            targetCompatibility JavaVersion.VERSION_1_8
        }
        kotlinOptions {
            jvmTarget = '1.8'
        }
    }
    
    dependencies {
        api project(path: ':libbase')
        testImplementation 'junit:junit:4.+'
        androidTestImplementation 'androidx.test.ext:junit:1.1.2'
        androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
    }
    

    app的gradle改造为:

    plugins {
        id 'com.android.application'
        id 'kotlin-android'
    }
    
    def config = rootProject.ext
    
    android {
        compileSdk config.androidVersion.compileSdk
    
        defaultConfig {
            applicationId config.applicationId.app
            minSdk config.androidVersion.minSdk
            targetSdk config.androidVersion.targetSdk
            versionCode config.androidVersion.versionCode
            versionName config.androidVersion.versionName
    
            testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        }
    
        buildTypes {
            release {
                minifyEnabled false
                proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
            }
        }
        compileOptions {
            sourceCompatibility JavaVersion.VERSION_1_8
            targetCompatibility JavaVersion.VERSION_1_8
        }
        kotlinOptions {
            jvmTarget = '1.8'
        }
    }
    
    dependencies {
        implementation project(path: ':libmodule_a')
        testImplementation 'junit:junit:4.+'
        androidTestImplementation 'androidx.test.ext:junit:1.1.2'
        androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
    }
    
    4.归纳module路径

    项目后续可能会有很多个module,如果你想单独使用文件夹进行分类,比如基础的组件放入基础的文件夹下,可以在settings.gradle中为module重新设置新路径

    我将libbase放入了新建文件夹lib_comm下:



    修改settings.gradle配置:

    include ':libbase'
    
    project(':libbase').projectDir = new File('lib_comm/libbase')
    

    其他的module也可以用该方法归类

    二、定义注解

    要用到APT,那么肯定要自定义注解,来指定APT解析的注解

    1.新建一个AnnotationModule

    该module会被业务module和插件moudle依赖


    2.定义Router注解

    在需要跳转的Activity上使用该注解,使用group和path来区分需要跳转的目标

    /**
     * 表示一个跳转目标(Activity、fragment)需要加入路由表
     * Created by aruba on 2021/11/22.
     */
    @Target(AnnotationTarget.CLASS)
    @Retention(AnnotationRetention.BINARY)
    annotation class Router(
        val group: String,//表示组,不可为空
        val path: String//表示目标,不可重复
    )
    
    3.定义Router包装类

    包装类用来最后跳转使用,里面主要是存放着被注解的Activity类

    /**
     * 路由包装类
     * Created by aruba on 2021/11/22.
     */
    data class RouterMeta(
        val type: Type = Type.ACTIVITY,//类型
        val path: String,//路由注解的path
        val group: String,//路由注解的group
        val clazz: Class<*>? = null//类对象
    ) {
        var element: Element? = null//节点
    
        enum class Type {
            ACTIVITY,
            ISERVICE
        }
    }
    

    三、Router组件

    上面我们的注解类中定义了group和path,还有RouterMeta包装类,最后生成的Map的对应关系如下图:


    上面的是一个group对应一个IPathRouter生成类,IPathRouter是一个接口,我们会在Router组件中定义出来,下面是一个path对应一个RouterMeta包装类
    这两个Map,用来替换我们上一篇中,自己要手动导入Activity关系的Map,看到这你也许会懵,没关系往下看接口的定义

    1.新建Router Module

    Route组件是实现功能的中间件,业务组件通过调用该组件方法来进行跳转

    依赖以下包:

    dependencies {
        implementation androidAppCompat
        implementation kotlinCoroutine
        api project(path: ':librouter_annotation')
    }
    
    2.定义接口
    2.1 path-RouterMeta映射关系

    首先是需要往一个map里存入path-RouterMeta映射关系
    IRouterPath:

    /**
     * 根据path集合将RouterMeta存入缓存map
     * Created by aruba on 2021/11/23.
     */
    interface IRouterPath {
        public fun cacheInRouterMetaByPath(map: LinkedHashMap<String, RouterMeta>)
    }
    

    为了方便理解,写一个测试类来实现该接口,我们最后通过kotlinpoet生成的类也是参考该实现类:

    class RouterPathTest : IRouterPath {
        override fun cacheInRouterMetaByPath(map: LinkedHashMap<String, RouterMeta>) {
            map["path1"] = RouterMeta(
                type = RouterMeta.Type.ACTIVITY,
                path = "path1",
                group = "group1",
                clazz = TestActivity::class.java
            )
            map["path2"] = RouterMeta(
                type = RouterMeta.Type.ACTIVITY,
                path = "path2",
                group = "group1",
                clazz = Test2Activity::class.java
            )
           ...
        }
    }
    

    就是在上一篇中,我们手动将映射关系存入Map的操作

    2.2 group-IPathRouter映射关系

    为了方便管理和扩展,我们引入了group的概念,group类似IRouterPath实现类的代理,一个group对应一个IRouterPath实现类,最终我们通过group将IRouterPath实现类存入Map
    IRouterGroup:

    /**
     * 根据group将IRouterPath存入缓存map
     * Created by aruba on 2021/11/23.
     */
    interface IRouterGroup {
        public fun cacheInRouterPathByGroup(map: LinkedHashMap<String, Class<out IRouterPath>>)
    }
    

    同样的写一个测试类,IRouterGroup 的实现就简单了,只需要一对一的关系:

    class RouterGroupTest : IRouterGroup {
        override fun cacheInRouterPathByGroup(map: LinkedHashMap<String, Class<out IRouterPath>>) {
            map["group1"] = RouterPathTest::class.java
        }
    }
    
    3.定义全局缓存

    第二步我们需要往两个Map中存入映射关系,来获取跳转时对应的类,现在把它们定义出来

    /**
     * 映射关系缓存
     * Created by aruba on 2021/11/23.
     */
    object CacheMap {
        // 根据group可以获取到RouterPath
        val RouterPathByGroup: LinkedHashMap<String, Class<out IRouterPath>> = LinkedHashMap()
    
        // 根据path可以获取到RouterMeta
        val RouterMetaByPath: LinkedHashMap<String, RouterMeta> = LinkedHashMap()
    }
    

    四、APT+kotlinpoet自动生成类

    有了上面的接口和全局缓存,我们就需要自动生成两个实现类了

    1.新建插件Module
    2.配置Gradle

    需要支持APT和kotlinpoet

    plugins {
        id 'java-library'
        id 'kotlin'
        id 'kotlin-kapt'
    }
    
    java {
        sourceCompatibility = JavaVersion.VERSION_1_8
        targetCompatibility = JavaVersion.VERSION_1_8
    }
    
    dependencies {
        implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion"
        implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
        implementation project(path: ':librouter_annotation')
        // apt依赖
        compileOnly "com.google.auto.service:auto-service:1.0-rc7"
        kapt "com.google.auto.service:auto-service:1.0-rc7"
        // kotlinpoet
        implementation group: 'com.squareup', name: 'kotlinpoet', version: '1.10.2'
    }
    
    3.定义一些全局变量

    APT解析节点和kotlinpoet代码生成时需要用到:类的包名和类名、方法名、生成的文件名(也是类名)、生成的类的包路径等

    object Const {
        const val ACTIVITY = "android.app.Activity"
        const val ISERVICE = "android.app.Fragment"
    
        // 生成的类实现的接口
        const val IROUTER_GROUP = "com.aruba.librouter_router.`interface`.IRouterGroup"
        const val IROUTER_PATH = "com.aruba.librouter_router.`interface`.IRouterPath"
    
        // 接口的package
        const val IROUTER_PACKAGE = "com.aruba.librouter_router.`interface`"
    
        // 接口的simplename
        const val IROUTER_GROUP_SIMPLENAME = "IRouterGroup"
        const val IROUTER_PATH_SIMPLENAME = "IRouterPath"
    
        //接口需要实现的方法
        const val METHOD_IROUTER_PATH = "cacheInRouterMetaByPath"
        const val METHOD_IROUTER_GROUP = "cacheInRouterPathByGroup"
    
        //生成类名的string format
        const val FILENAME_FORMAT_PATH = "%s_%s_path"
        const val FILENAME_FORMAT_GROUP = "%s_%s_group"
        
        // 生成的类文件前缀
        const val FILENAME_PREFIX = "Router"
        // 生成的类的package
        const val GENERATE_PACKAGE = "com.aruba.router"
    }
    
    4.使用注解解释器及kotlinpoet

    每个使用了插件的业务module都会执行一次注解解释器的方法,我们对注解的处理主要分为两步:

    • 使用APT获取Router注解的类,并进行包装,最后存入一个group-RouterMeta列表的Map中
    • group-RouterMeta列表的Map进行处理,首先遍历RouterMeta列表,使用kotlinpoet生成IRouterPath的实现类,再根据group和IRouterPath实现类的文件名(类名),生成实现IRouterGroup的类
    /**
     * 注解解析器,每个模块都会执行一次该类中的方法
     * Created by aruba on 2021/11/22.
     */
    @AutoService(Processor::class)
    //指定要处理的注解
    @SupportedAnnotationTypes("com.aruba.librouter_annotation.Router")
    @SupportedSourceVersion(SourceVersion.RELEASE_8)
    class AnnotationProcessor : AbstractProcessor() {
        // 日志打印
        private lateinit var MSG: Messager
        private fun Messager.print(msg: String) {
            printMessage(Diagnostic.Kind.NOTE, msg);
        }
    
        // 节点工具类 (类、函数、属性都是节点) 用来获取节点
        private lateinit var mElementUtils: Elements
    
        // type(类信息)工具类   用来比对节点
        private lateinit var mTypeUtils: Types
    
        // 文件操作类  用来生成kotlin文件
        private lateinit var mFiler: Filer
    
        // Activity类的节点描述
        private val typeActivity by lazy { mElementUtils.getTypeElement(Const.ACTIVITY).asType() }
    
        // IService接口的节点描述
        private val typeIService by lazy { mElementUtils.getTypeElement(Const.ISERVICE).asType() }
    
        // 组名:RouterMeta列表的Map
        private val routerMetaByGroup: MutableMap<String, MutableList<RouterMeta>>
                by lazy { mutableMapOf() }
    
        // 组名:文件名列表的Map
        private val fileNameByGroup: MutableMap<String, String>
                by lazy { mutableMapOf() }
    
        private var isInit: Boolean = false
    
        override fun init(processingEnv: ProcessingEnvironment?) {
            super.init(processingEnv)
            //初始化
            processingEnv?.apply {
                MSG = messager
                MSG.print("init")
                mElementUtils = elementUtils
                mTypeUtils = typeUtils
                mFiler = filer
            }
        }
    
        /**
         * 开始执行
         */
        override fun process(
            typeElementSet: MutableSet<out TypeElement>?,
            roundEnvironment: RoundEnvironment?
        ): Boolean {
            if (isInit) return true
            isInit = true
    
            typeElementSet?.let {
                // 1.获取所有使用了Router注解的节点并遍历
                roundEnvironment?.getElementsAnnotatedWith(Router::class.java)?.forEach { element ->
                    // 2.获取注解
                    val router = element.getAnnotation(Router::class.java).apply {
                        if (path.isEmpty() || group.isEmpty()) {
                            MSG.print("$element group or path can not be empty")
                            throw RuntimeException()
                        }
                    }
                    // 3.获取包装类
                    getRouterMeta(element, router, mTypeUtils) {
                        MSG.print(it.toString())
                    }.apply {
                        MSG.print("group:${group} path:${path}")
                        // 4.加入缓存,即根据group分组
                        routerMetaByGroup.getOrPut(group) {
                            mutableListOf()
                        }.add(this)
                    }
                }
    
                MSG.print(routerMetaByGroup.toString())
                // 5.利用kotlinpoet生成代码
                generateClassByKotlinPoet()
            }
            return true
        }
    
        /**
         * 获取节点包装类方法
         * 我们定义Router注解只能使用在Activity类和实现了IService接口的类
         */
        private fun getRouterMeta(
            element: Element,
            router: Router,
            typeUtils: Types,
            mirror: (mirror: TypeMirror) -> Unit = {}
        ): RouterMeta {
            // 获取当前节点的描述
            val type = element.asType().apply {
                mirror(this)
            }
    
            // 根据描述是否是typeActivity或者typeIService生成包装类
            return when {
                typeUtils.isSubtype(type, typeActivity) -> {//是Activity子类
                    RouterMeta(path = router.path, group = router.group).apply {
                        this.element = element
                    }
                }
                typeUtils.isSubtype(type, typeIService) -> {//实现了IService接口
                    RouterMeta(
                        path = router.path, group = router.group,
                        type = RouterMeta.Type.ISERVICE
                    ).apply {
                        this.element = element
                    }
                }
                else -> {// 其他情况使用了Router注解,直接抛出异常
                    MSG.print("$element type:$type not support router")
                    throw RuntimeException()
                }
            }
        }
    
        /**
         * 利用kotlinpoet生成代码
         */
        private fun generateClassByKotlinPoet() {
            routerMetaByGroup.forEach { (group, routerMetas) ->
                // 分别生成两个类并实现上面两个接口,以便在Router中获取
                // 1.先来生成 根据path集合将RouterMeta包装类存入一个map  的类
                generateRouterPathByKotlinPoet(group, routerMetas)
            }
    
            fileNameByGroup.forEach { (group, clzName) ->
                // 2.再生成 根据group将1.生成的类存入一个map  的类
                generateRouterGroupByKotlinPoet(group, clzName)
            }
        }
    
        /**
         * 生成RouterPath类
         */
        private fun generateRouterPathByKotlinPoet(
            group: String,
            routerMetas: MutableList<RouterMeta>
        ) {
            // 可以自己先实现一个,再参考着写
    //        class RouterPathTest : IRouterPath {
    //            override fun cacheInRouterMetaByPath(map: LinkedHashMap<String, RouterMeta>) {
    //                map["path1"] = RouterMeta(
    //                    type = RouterMeta.Type.ACTIVITY,
    //                    path = "path1",
    //                    group = "group1",
    //                    clazz = TestActivity::class.java
    //                )
    //                map["path2"] = RouterMeta(
    //                    type = RouterMeta.Type.ACTIVITY,
    //                    path = "path2",
    //                    group = "group1",
    //                    clazz = Test2Activity::class.java
    //                )
    //            }
    //        }
    
            // 1.创建方法,方法名为 cacheInRouterMetaByPath
            val funcSpecBuilder = FunSpec.builder(Const.METHOD_IROUTER_PATH)
                .addModifiers(KModifier.OVERRIDE)// 方法标识override关键字
                .addParameter(//添加入参 map: LinkedHashMap<String, RouterMeta>
                    "map",
                    LinkedHashMap::class.parameterizedBy(String::class, RouterMeta::class)
                )
    
            // 2.为方法添加代码
    //        map["path1"] = RouterMeta(
    //            type = RouterMeta.Type.ACTIVITY,
    //            path = "path1",
    //            group = "group1",
    //            clazz = TestActivity::class.java
    //        )
            routerMetas.forEach { routerMeta ->
                funcSpecBuilder.addStatement(
                    """
                        |map[%S] = RouterMeta(
                        |   type = %T.%L,
                        |   path = %S,
                        |   group = %S,
                        |   clazz = %T::class.java
                        |)
                        |
                    """.trimMargin(),
                    routerMeta.path,//key
                    RouterMeta.Type::class,//type的类
                    routerMeta.type,//type的值
                    routerMeta.path,//path
                    routerMeta.group,//group
                    routerMeta.element!!.asType()//clazz
                )
            }
    
            // 3.创建类
            val superInter = ClassName(Const.IROUTER_PACKAGE, Const.IROUTER_PATH_SIMPLENAME)
            //生成的文件名 如:Router_module_a_path
            val fileName = String.format(Const.FILENAME_FORMAT_PATH, Const.FILENAME_PREFIX, group)
            val typeSpec = TypeSpec.classBuilder(fileName)
                .addFunction(funcSpecBuilder.build()) // 类中添加方法
                .addSuperinterface(superInter) // 实现IRouterPath接口:根据path集合将RouterMeta存入缓存
                .build()
    
            // 4.创建文件
            FileSpec.builder(Const.GENERATE_PACKAGE, fileName)//包名,文件名
                .addType(typeSpec)
                .build()
                .writeTo(mFiler) // 写入文件
    
            MSG.print("创建文件成功:${fileName}")
    
            // 加入缓存中,后续生成RouterGroup类用
            fileNameByGroup[group] = fileName
        }
    
        /**
         * 生成RouterGroup类
         */
        private fun generateRouterGroupByKotlinPoet(group: String, clzName: String) {
    //        class RouterGroupTest : IRouterGroup {
    //            override fun cacheInRouterPathByGroup(map: LinkedHashMap<String, Class<out IRouterPath>>) {
    //                map["group1"] = RouterPathTest::class.java
    //            }
    //        }
            // 1.创建方法,方法名为 cacheInRouterPathByGroup
            val routePathInter = ClassName(Const.IROUTER_PACKAGE, Const.IROUTER_PATH_SIMPLENAME)
            val funcSpecBuilder = FunSpec.builder(Const.METHOD_IROUTER_GROUP)
                .addModifiers(KModifier.OVERRIDE)// 方法标识override关键字
                .addParameter(//添加入参 map: LinkedHashMap<String, Class<out IRouterPath>>
                    "map",
                    LinkedHashMap::class.java.asClassName().parameterizedBy(
                        String::class.asTypeName(),//String
                        Class::class.java.asClassName().parameterizedBy(//Class<out IRouterPath>
                            WildcardTypeName.producerOf(routePathInter)
                        )
                    )
                )
    
    
            // 2.为方法添加代码  map["group1"] = RouterPathTest::class.java
            // 生成的RouterPath类
            val generatedRoutePath = ClassName(Const.GENERATE_PACKAGE, clzName)
            funcSpecBuilder.addStatement(
                "map[%S] = %T::class.java".trimMargin(),
                group,
                generatedRoutePath
            )
    
            // 3.创建类
            //生成的文件名 如:Router_module_a_group
            val routeGroupInter = ClassName(Const.IROUTER_PACKAGE, Const.IROUTER_GROUP_SIMPLENAME)
            val fileName = String.format(Const.FILENAME_FORMAT_GROUP, Const.FILENAME_PREFIX, group)
            val typeSpec = TypeSpec.classBuilder(fileName)
                .addFunction(funcSpecBuilder.build()) // 类中添加方法
                .addSuperinterface(routeGroupInter) // 实现IRouterGroup接口:根据group将IRouterPath存入缓存
                .build()
    
            // 4.创建文件
            FileSpec.builder(Const.GENERATE_PACKAGE, fileName)//包名,文件名
                .addType(typeSpec)
                .build()
                .writeTo(mFiler) // 写入文件
    
            MSG.print("创建文件成功:${fileName}")
        }
    }
    

    五、Router组件实现

    之前只实现了对外的接口,接下来实现真正的跳转功能,编译期的代码已经生成了,运行时我们需要获取到它,加载类并利用反射实例化

    1.获取生成类的工具类
    /**
     * 获取所有生成的代码全路径
     * Created by aruba on 2021/11/23.
     */
    object ClassUtils {
        /**
         * 获得程序所有的apk(instant run会产生很多split apk)
         */
        private fun getSourcePaths(context: Context): List<String> {
            context.packageManager.getApplicationInfo(
                context.packageName, 0
            ).apply {
                val sourcePaths: MutableList<String> = mutableListOf()
                sourcePaths.add(sourceDir)
                //instant run
                splitSourceDirs?.let {
                    sourcePaths.addAll(it)
                }
    
                return sourcePaths
            }
        }
    
        /**
         * 根据包名获取路由表
         *
         * @param context
         * @param packageName 包名
         * @return
         * @throws PackageManager.NameNotFoundException
         * @throws IOException
         * @throws InterruptedException
         */
        fun getFileNameByPackageName(context: Application, packageName: String): Set<String> =
            runBlocking {
                val classNames: MutableSet<String> = mutableSetOf()
                val paths = getSourcePaths(context)
                val coroutineScope = CoroutineScope(Dispatchers.IO);
                val jobs = mutableListOf<Job>()
                for (path in paths) {
                    coroutineScope.launch {
                        var dexfile: DexFile? = null
                        try {
                            // 加载 apk中的dex 并遍历 获得所有包名为 {packageName} 的类
                            dexfile = DexFile(path)
                            val dexEntries = dexfile.entries()
                            while (dexEntries.hasMoreElements()) {
                                val className = dexEntries.nextElement()
                                if (className.startsWith(packageName)) {
                                    classNames.add(className)
                                }
                            }
                        } catch (e: IOException) {
                            e.printStackTrace()
                        } finally {
                            if (null != dexfile) {
                                try {
                                    dexfile.close()
                                } catch (e: IOException) {
                                    e.printStackTrace()
                                }
                            }
                        }
                    }.apply {
                        jobs.add(this)
                    }
                }
                jobs.forEach {
                    it.join()
                }
                classNames
            }
    }
    
    2.Router实现

    首先我们获取到所有IRouterGroup的生成类,类加载并实例化后,调用方法,存入group-IRouterPath映射关系Map中

        /**
         * 初始化
         */
        fun init(context: Application) {
            // 获取类
            ClassUtils.getFileNameByPackageName(context, GENERATE_PACKAGE)
                .filter { className ->
                    className.endsWith("group")//只获取RouterGroup
                }.forEach { className ->
                    // 实例化并且调用方法
                    (Class.forName(className).getConstructor().newInstance() as IRouterGroup)
                        .cacheInRouterPathByGroup(CacheMap.RouterPathByGroup)// 加入缓存中
                }
        }
    

    再实现跳转功能,先从path-RouterMeta映射关系Map中获取,如果缓存中没有,那么利用group-RouterPath映射关系Map获取到IRouterPath类,同样进行类加载并实例化后,调用方法,存入path-RouterMeta映射关系Map

        /**
         * 跳转
         */
        fun navigation(context: Context, group: String, path: String) {
            // 获取缓存中的RouterMeta
            CacheMap.RouterMetaByPath.getOrPut(path) {
                // 缓存中没有,就利用RouterPath生成类实例化后,调用cacheInRouterMetaByPath加入
                (CacheMap.RouterPathByGroup[group]!!.getConstructor().newInstance() as IRouterPath)
                    .cacheInRouterMetaByPath(CacheMap.RouterMetaByPath)
                CacheMap.RouterMetaByPath[path]!!
            }.let { routerMeta ->
                context.startActivity(Intent(context, routerMeta.clazz))
            }
        }
    

    完整ARouter代码:

    /**
     * 路由中间件
     * Created by aruba on 2021/11/23.
     */
    class ARouter private constructor() {
    
        companion object {
            val INSTANCE: ARouter by lazy { ARouter() }
    
            // 生成的类的package
            private val GENERATE_PACKAGE = "com.aruba.router"
        }
    
        /**
         * 初始化
         */
        fun init(context: Application) {
            // 获取类
            ClassUtils.getFileNameByPackageName(context, GENERATE_PACKAGE)
                .filter { className ->
                    className.endsWith("group")//只获取RouterGroup
                }.forEach { className ->
                    // 实例化并且调用方法
                    (Class.forName(className).getConstructor().newInstance() as IRouterGroup)
                        .cacheInRouterPathByGroup(CacheMap.RouterPathByGroup)// 加入缓存中
                }
        }
    
        /**
         * 跳转
         */
        fun navigation(context: Context, group: String, path: String) {
            // 获取缓存中的RouterMeta
            CacheMap.RouterMetaByPath.getOrPut(path) {
                // 缓存中没有,就利用RouterPath生成类实例化后,调用cacheInRouterMetaByPath加入
                (CacheMap.RouterPathByGroup[group]!!.getConstructor().newInstance() as IRouterPath)
                    .cacheInRouterMetaByPath(CacheMap.RouterMetaByPath)
                CacheMap.RouterMetaByPath[path]!!
            }.let { routerMeta ->
                context.startActivity(Intent(context, routerMeta.clazz))
            }
        }
    }
    

    六、测试跳转功能

    1.使用Router插件

    在libmodule_a中使用Router插件:

    dependencies {
        api project(path: ':libbase')
        implementation project(path: ':librouter_router')
        kapt project(':librouter_complier')
    }
    

    app中也进行依赖:

    dependencies {
        implementation project(path: ':libmodule_a')
        implementation project(path: ':librouter_router')
        kapt  project(':librouter_complier')
    }
    
    2.使用Router注解

    libmodule_a的ModuleAActivity使用注解:

    @Router(group = "module_a", path = "ModuleAActivity")
    class ModuleAActivity : AppCompatActivity() {
    

    app的MainActivity使用注解:

    @Router(group = "app", path = "MainActivity")
    class MainActivity : AppCompatActivity() {
    
    3.Application中初始化
    class App : Application() {
        override fun onCreate() {
            super.onCreate()
            ARouter.INSTANCE.init(this)
        }
    }
    
    4.使用Router进行跳转

    MainActivity:

    @Router(group = "app", path = "MainActivity")
    class MainActivity : AppCompatActivity() {
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
        }
    
        fun toModule(view: android.view.View) {
            ARouter.INSTANCE.navigation(this, "module_a", "ModuleAActivity")
        }
    
    }
    

    ModuleAActivity:

    @Router(group = "module_a", path = "ModuleAActivity")
    class ModuleAActivity : AppCompatActivity() {
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_module_aactivity)
        }
    
        fun toMain(view: android.view.View) {
            ARouter.INSTANCE.navigation(this, "app", "MainActivity")
        }
    }
    

    最终效果:



    最后细心的人可能已经发现,不同组件之间,group是不能重复的,一个moudle中可以有多个group

    最后附上一张结构图:

    项目地址:https://gitee.com/aruba/arouter-application.git

    相关文章

      网友评论

          本文标题:Android--利用APT+kotlinpoet实现组件化开发

          本文链接:https://www.haomeiwen.com/subject/klovtrtx.html