网站推广费用ihanshi,中山低价网站建设,建设邯郸网站,洛阳市住房和城乡建设局网站【KRouter】一个简单且轻量级的Kotlin Routing框架
KRouter#xff08;Kotlin-Router#xff09;是一个简单而轻量级的Kotlin路由框架。
具体来说#xff0c;KRouter是一个通过URI来发现接口实现类的框架。它的使用方式如下#xff1a;
val homeScreen KRouter.routeKotlin-Router是一个简单而轻量级的Kotlin路由框架。
具体来说KRouter是一个通过URI来发现接口实现类的框架。它的使用方式如下
val homeScreen KRouter.routeScreen(screen/home?namezhangke)之所以这样做是因为在使用Voyager一段时间后我发现模块之间的通信不够灵活需要一些配置而且使用DeepLink有点奇怪所以我更喜欢使用路由来实现模块之间的通信于是我开发了这个库。
这个库主要通过KSP、ServiceLoader和反射来实现。
使用方法
上述代码基本上就是使用的全部内容。
如前所述这是用于发现接口实现类并通过URI匹配目标的库因此我们首先需要定义一个接口。
interface Screen然后我们有一个包含许多独立模块的项目这些模块实现了这个接口每个模块都不同我们需要通过它们各自的路由即URI来区分它们。
// HomeModule
Destination(screen/home)
class HomeScreen(Router val router: String ) : Screen// ProfileModule
Destination(screen/profile)
class ProfileScreen : Screen {Routerlateinit var router: String
}现在我们有两个独立的模块它们各自拥有自己的屏幕Screens并且它们都有自己的路由地址。
val homeScreen KRouter.routeScreen(screen/home?namezhangke)
val profileScreen KRouter.routeScreen(screen/profile?namezhangke)现在您可以通过KRouter获取这两个对象并且这些对象中的路由属性将被分配给对KRouter.route的特定调用的路由。
现在您可以在HomeScreen和ProfileScreen中获取通过URI传递的参数并且可以使用这些参数进行一些初始化和其他操作。
Destination
Destination 注解用于标记目的地Destination包含两个参数
route目的地的唯一标识路由地址必须是 URI 类型的字符串不需要包含查询参数。type目的地的接口。如果类只有一个父类或接口您无需设置此参数它可以自动推断。但如果类有多个父类或接口您需要通过 type 参数明确指定。
需要特别注意的是被 Destination 注解标记的类必须包含一个无参数构造函数否则 ServiceLoader 无法创建对象。对于 Kotlin 类您还需要确保构造函数的每个输入参数都具有默认值。
Router
Router 注解用于指定目的地类的哪个属性用于接收传入的路由参数该属性必须是字符串类型。
使用此注解标记的属性将自动分配一个值或者您可以不设置注解。例如在上述示例中当创建 HomeScreen 对象时其 router 字段的值将自动设置为 screen/home?namezhangke。
特别要注意如果被Router注解的属性不在构造函数中那么该属性必须声明为可修改的即在 Kotlin 中应为 var 修饰的可变属性。
KRouter 是一个 Kotlin Object 类它只包含一个函数
inline fun reified T : Any route(router: String): T?此函数接受一个泛型类型和一个路由地址。路由地址可以包含或不包含查询参数但在匹配目的地时查询参数将被忽略。匹配成功后将使用此 URI 构造对象并将 URI 传递给目标对象中的 router 注解字段。
集成
首先您需要在项目中集成 KSP。 https://kotlinlang.org/docs/ksp-overview.html 然后添加以下依赖项
// 模块的 build.gradle.kts
implementation(com.github.0xZhangKe.KRouter:core:0.1.5)
ksp(com.github.0xZhangKe.KRouter:compiler:0.1.5)由于使用了 ServiceLoader您还需要设置 SourceSet。
// 模块的 build.gradle.kts
kotlin {sourceSets.main {resources.srcDir(build/generated/ksp/main/resources)}
}可能还需要添加 JitPack 仓库
maven { setUrl(https://jitpack.io) }工作原理
正如前面所提到的KRouter 主要通过 ServiceLoader KSP 反射来实现。
这个框架由两个主要部分组成编译阶段和运行时阶段。
KSP 插件 与 KSP 插件相关的代码位于编译器模块中。
KSP 插件的主要任务是根据 Destination 注解生成 ServiceLoader 的服务文件。
KSP 代码的其余部分基本相同主要工作包括首先配置服务文件然后根据注解获取类最后通过 Visitor 进行迭代。您可以直接查看 KRouterVisitor 来了解更多细节。
override fun visitClassDeclaration(classDeclaration: KSClassDeclaration, data: Unit) {val superTypeName findSuperType(classDeclaration)writeService(superTypeName, classDeclaration)
}visitClassDeclaration 方法主要有两个主要功能第一是获取父类第二是编写或创建服务文件。
流程首先是获取指定类型的父类如果没有父类且只有一个父类时可以直接返回否则会引发异常。
// find super-type by type parameter
val routerAnnotation classDeclaration.requireAnnotationDestination()
val typeFromAnnotation routerAnnotation.findArgumentTypeByName(type)?.takeIf { it ! badTypeName }// find single-type
if (classDeclaration.superTypes.isSingleElement()) {val superTypeName classDeclaration.superTypes.iterator().next().typeQualifiedName?.takeIf { it ! badSuperTypeName }if (!superTypeName.isNullOrEmpty()) {return superTypeName}
}一旦获取到父类我们需要创建一个文件其文件名以接口或抽象类的权限作为所需的 ServiceLoader 文件名。
然后我们将已实现类的权限名称写入该文件。
val resourceFileName ServicesFiles.getPath(superTypeName)
val serviceClassFullName serviceClassDeclaration.qualifiedName!!.asString()
val existsFile environment.codeGenerator.generatedFile.firstOrNull { generatedFile -generatedFile.canonicalPath.endsWith(resourceFileName)}
if (existsFile ! null) {val services existsFile.inputStream().use { ServicesFiles.readServiceFile(it) }services.add(serviceClassFullName)existsFile.outputStream().use { ServicesFiles.writeServiceFile(services, it) }
} else {environment.codeGenerator.createNewFile(dependencies Dependencies(aggregating false, serviceClassDeclaration.containingFile!!),packageName ,fileName resourceFileName,extensionName ,).use {ServicesFiles.writeServiceFile(setOf(serviceClassFullName), it)}
}KRouter主要有三个关键功能
通过ServiceLoader获取接口的所有实现类。将特定的目标类与URI进行匹配。从URI构建目标类对象。 第一件事非常简单
inline fun reified T findServices(): ListT {val clazz T::class.javareturn ServiceLoader.load(clazz, clazz.classLoader).iterator().asSequence().toList()
}一旦你获取到它你就可以开始与URL进行匹配。
这个匹配的方式是获取每个目标类的Destination注解中的路由字段然后将其与路由进行比较。
fun findServiceByRouter(serviceClassList: ListAny,router: String,
): Any? {val routerUri URI.create(router).baseUrival service serviceClassList.firstOrNull {val serviceRouter getRouterFromClassAnnotation(it::class)if (serviceRouter.isNullOrEmpty().not()) {val serviceUri URI.create(serviceRouter!!).baseUriserviceUri routerUri} else {false}}return service
}private fun getRouterFromClassAnnotation(targetClass: KClass*): String? {val routerAnnotation targetClass.findAnnotationDestination() ?: return nullreturn routerAnnotation.router
}匹配策略是忽略查询字段只需通过baseUri进行匹配即可。
接下来的步骤是创建对象。有两种情况需要考虑
第一种情况是Router注解位于构造函数中在这种情况下需要再次使用构造函数创建对象。
第二种情况是Router注解位于普通属性中。在这种情况下可以直接使用ServiceLoader创建的对象然后将值分配给它。
如果Router注解位于构造函数中您可以首先获取routerParameter然后使用PrimaryConstructor重新创建对象。
private fun fillRouterByConstructor(router: String, serviceClass: KClass*): Any? {val primaryConstructor serviceClass.primaryConstructor?: throw IllegalArgumentException(KRouter Destination class must have a Primary-Constructor!)val routerParameter primaryConstructor.parameters.firstOrNull { parameter -parameter.findAnnotationRouter() ! null} ?: return nullif (routerParameter.type ! stringKType) errorRouterParameterType(routerParameter)return primaryConstructor.callBy(mapOf(routerParameter to router))
}如果它是一个普通的变量属性首先获取属性然后进行一些类型权限和其他检查然后调用setter方法分配值。
private fun fillRouterByProperty(router: String,service: Any,serviceClass: KClass*,
): Any? {val routerProperty serviceClass.findRouterProperty() ?: return nullfillRouterToServiceProperty(router router,service service,property routerProperty,)return service
}private fun KClass*.findRouterProperty(): KProperty*? {return declaredMemberProperties.firstOrNull { property -val isRouterProperty property.findAnnotationRouter() ! nullisRouterProperty}
}private fun fillRouterToServiceProperty(router: String,service: Any,property: KProperty*,
) {if (property !is KMutableProperty*) throw IllegalArgumentException(Router property must be non-final!)if (property.visibility ! KVisibility.PUBLIC) throw IllegalArgumentException(Router property must be public!)val setter property.setterval propertyType setter.parameters[1]if (propertyType.type ! stringKType) errorRouterParameterType(propertyType)property.setter.call(service, router)
}上面是关于KRouter的全部内容希望对你有所帮助
GitHub
https://github.com/0xZhangKe/KRouter