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

上一篇我们使用了一个全局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实现类存入MapIRouterGroup:

/**
 * 根据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

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
作为前端使用React、TypeScript、React Router、Redux、Axios、Ant Design和Sass开发ERP软件项目的职责描述,主要包括以下几个方面: 1. 分析需求和设计界面:与产品经理、设计师等团队成员合作,分析用户需求和产品设计,设计符合用户需求的界面,并提供良好的用户体验。 2. 使用React和TypeScript开发组件:根据设计稿或需求文档,使用React和TypeScript开发可复用的组件利用类型检查提高代码的可靠性和可维护性。 3. 使用React Router实现路由管理:使用React Router进行页面之间的导航和路由管理,确保页面之间的跳转和参数传递的正常。 4. 使用Redux进行状态管理:使用Redux进行全局状态的管理,包括定义和处理数据流、异步操作、状态持久等,确保数据的一致性和可控性。 5. 使用Axios进行网络请求:使用Axios库发送HTTP请求与后端API进行数据交互,并处理请求的错误和异常情况。 6. 使用Ant Design进行UI开发:使用Ant Design提供的组件库进行界面开发,保证界面的一致性和美观性,并根据需求进行自定义样式。 7. 使用Sass进行样式管理:使用Sass预处理器编写可复用的样式代码,提高样式开发效率,并保持样式的可维护性。 8. 优性能和用户体验:通过前端优技术(如代码分割、懒加载、缓存等),提升ERP软件的性能和用户体验,确保页面加载速度快、操作流畅。 9. 跨浏览器兼容性测试:测试并确保ERP软件在各种主流浏览器(如Chrome、Firefox、Safari等)下的正常运行,并解决兼容性问题。 10. 代码版本管理和团队协作:使用版本管理工具(如Git)管理代码,与团队成员协作开发,参与代码评审和项目迭代。 11. 系统维护和故障排除:及时响应用户反馈并解决软件中出现的前端问题,修复bug,确保ERP软件的稳定运行。 总的来说,前端使用React、TypeScript、React Router、Redux、Axios、Ant Design和Sass开发ERP软件项目的职责是负责开发和维护ERP软件的前端界面和功能,与后端进行数据交互,优性能和用户体验,并与团队成员协作推动项目的成功交付。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值