Android--利用APT+kotlinpoet实现组件化开发
上一篇我们使用了一个全局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