【KRouter】一个简单且轻量级的Kotlin Routing框架
【KRouter】一个简单且轻量级的Kotlin Routing框架
KRouter(Kotlin-Router)是一个简单而轻量级的Kotlin路由框架。
具体来说,KRouter是一个通过URI来发现接口实现类的框架。它的使用方式如下:
val homeScreen = KRouter.route<Screen>("screen/home?name=zhangke")
之所以这样做,是因为在使用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.route<Screen>("screen/home?name=zhangke")
val profileScreen = KRouter.route<Screen>("screen/profile?name=zhangke")
现在,您可以通过KRouter获取这两个对象,并且这些对象中的路由属性将被分配给对KRouter.route
的特定调用的路由。
现在,您可以在HomeScreen
和ProfileScreen
中获取通过URI传递的参数,并且可以使用这些参数进行一些初始化和其他操作。
@Destination
@Destination
注解用于标记目的地(Destination),包含两个参数:
route
:目的地的唯一标识路由地址,必须是 URI 类型的字符串,不需要包含查询参数。type
:目的地的接口。如果类只有一个父类或接口,您无需设置此参数,它可以自动推断。但如果类有多个父类或接口,您需要通过 type 参数明确指定。
需要特别注意的是,被 @Destination
注解标记的类必须包含一个无参数构造函数,否则 ServiceLoader
无法创建对象。对于 Kotlin 类,您还需要确保构造函数的每个输入参数都具有默认值。
@Router
@Router
注解用于指定目的地类的哪个属性用于接收传入的路由参数,该属性必须是字符串类型。
使用此注解标记的属性将自动分配一个值,或者您可以不设置注解。例如,在上述示例中,当创建 HomeScreen
对象时,其 router 字段的值将自动设置为 screen/home?name=zhangke
。
特别要注意,如果被@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.requireAnnotation<Destination>()
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(): List<T> {val clazz = T::class.javareturn ServiceLoader.load(clazz, clazz.classLoader).iterator().asSequence().toList()
}
一旦你获取到它,你就可以开始与URL进行匹配。
这个匹配的方式是获取每个目标类的Destination注解中的路由字段,然后将其与路由进行比较。
fun findServiceByRouter(serviceClassList: List<Any>,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.findAnnotation<Destination>() ?: 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.findAnnotation<Router>() != 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.findAnnotation<Router>() != 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
相关文章:
【KRouter】一个简单且轻量级的Kotlin Routing框架
【KRouter】一个简单且轻量级的Kotlin Routing框架 KRouter(Kotlin-Router)是一个简单而轻量级的Kotlin路由框架。 具体来说,KRouter是一个通过URI来发现接口实现类的框架。它的使用方式如下: val homeScreen KRouter.route&l…...
时间管理类书籍阅读笔记
背景 这段时间看了时间管理方面的书籍,大部分和早晨时间利用相关。之所以有了利用早晨时间的想法,是某天下班后,感觉很疲惫,什么都不想做,于是就打了一晚上游戏,然后第二天重复着这样的生活。 突然意识到…...
CSS文字居中对齐学习
CSS使用text-align属性设置文字对齐方式;text-align:center,这样就设置了文字居中对齐; <!DOCTYPE html> <html><head><meta charset"UTF-8"><title>css 水平居中</title><style>.box …...
《论文阅读》CARE:通过条件图生成的共情回复因果关系推理 EMNLP 2022
《论文阅读》CARE:通过条件图生成的移情反应因果关系推理 前言简介基础知识TransformerVariational Graph Auto-Encoder 变分图自编码器`邻接矩阵(adjacency matrix)``图神经网络(GNN)``图卷积神经网络(GCN)``自编码器(Auto Encoder)``图自编码器(GAE)``变分图自编码…...
React 开发一个移动端项目(1)
技术栈: 项目搭建:React 官方脚手架 create-react-appreact hooks状态管理:redux 、 redux-thunkUI 组件库:antd-mobileajax请求库:axios路由:react-router-dom 以及 historyCSS 预编译器:sass…...
c#查看代码的执行耗时( Stopwatch )
我们如果需要看某段代码的执行耗时,会通过如下的方式进行查看 using System.Diagnostics; private void button1_Click(object sender, EventArgs e){Stopwatch sw Stopwatch.StartNew();//sw.Start();StringBuilder sb new StringBuilder();for(int i 0; i <…...
Python网络爬虫库:轻松提取网页数据的利器
网络爬虫是一种自动化程序,它可以通过访问网页并提取所需的数据。Python是一种流行的编程语言,拥有许多强大的网络爬虫库。在本文中,我们将介绍几个常用的Python网络爬虫库以及它们的使用。 Requests库 Requests是一个简单而优雅的HTTP库&…...
YOLOv5算法改进(15)— 更换Neck之AFPN
前言:Hello大家好,我是小哥谈。在YOLOv5中添加AFPN(Adaptive Feature Pyramid Network)可以提高目标检测的准确性。AFPN是一种用于目标检测任务的功能增强模块,它能够自适应地融合来自不同层级的特征图,以提…...
Vue2项目练手——通用后台管理项目第七节
Vue2项目练手——通用后台管理项目 用户管理分页使用的组件Users.vuemock.js 关键字搜索区Users.vue 权限管理登录页面样式修改Login.vue 登录权限使用token对用户鉴,使用cookie对当前信息保存(类似localstorage)Login.vuerouter/index.js 登…...
《Web安全基础》04. 文件操作安全
web 1:文件操作安全2:文件上传漏洞2.1:简介2.2:防护与绕过2.3:WAF 绕过2.3.1:数据溢出2.3.2:符号变异2.3.3:数据截断2.3.4:重复数据 3:文件包含漏洞4…...
docker-compose安装nginx
基于docker-compose安装nginx 目录 一、目录结构 1、docker-compose.yml 2、nginx.conf 3、default.conf 4、index.html 二、访问测试 一、目录结构 1、docker-compose.yml version: 3 services:nginx:image: registry.cn-hangzhou.aliyuncs.com/zhengqing/nginx:1.21.1…...
报错处理:MySQL无法启动
报错环境: Linux MySQL 具体报错: Cant connect to local MySQL server through socket /var/run/mysqld/mysqld.sock 排错思路: 当尝试启动MySQL服务时,如果出现无法连接到MySQL服务的错误,可能是由于MySQL服务未正确…...
Vue中表单手机号验证与手机号归属地查询
下面是一篇关于Vue中如何进行表单手机号验证与手机号归属地查询的Markdown格式的文章,包含代码示例。 Vue中表单手机号验证与手机号归属地查询 手机号验证和归属地查询是许多Web应用程序中常见的功能之一。在Vue.js中,我们可以轻松地实现这两个功能。本…...
初高(重要的是高中)中数学知识点综合
1. 集合 1.1 集合的由来和确定性 确定对象构成的整体称为集合(组成集合的元素必须是确定的 ),每个集合内的对象个体成为元素(Element)。确定性: 给定一个集合,任何一个对象是不是这个集合内的元素,就已经确…...
Fiddler 系列教程(二) Composer创建和发送HTTP Request跟手机抓包
Fiddler Composer介绍 Composer的官方帮助文档:http://www.fiddler2.com/fiddler/help/composer.asp Fiddler的作者把HTTP Request发射器取名叫Composer(中文意思是:乐曲的创造者), 很有诗意 Fiddler Composer的功能就是用来创建HTTP Request 然后发送…...
淘宝平台开放接口API接口
淘宝平台开放接口API接口是指淘宝平台提供给第三方开发者的一组接口,用于实现与淘宝平台的数据交互和功能扩展。通过API接口,第三方开发者可以获取淘宝平台上的商品信息、订单信息、用户信息等数据,也可以实现商品的发布、订单的创建和支付等…...
缓存夺命连环问
1. 为什么要用缓存? 用缓存,主要有两个用途:高性能、高并发。 高性能 假设这么个场景,你有个操作,一个请求过来,吭哧吭哧你各种乱七八糟操作 MySQL,半天查出来一个结果,耗时 600m…...
模型生成自动化测试用例
自动产生的测试用例本就应该由程序自动执行,这其实也就是NModel推荐的模式。先回过头来看看文章中制作的模型,模型里面将登录、注销、用户名以及密码等要素都抽象出来了,而NModel是以这些抽象出来的动作(登录、注销)和…...
归并排序-面试例子
小数和问题 描述 在一个数组中,一个数左边比它小的数的总和,叫数的小和,所有数的小和累加起来,叫数组小和。求数组小和。 例子 5 2 6 1 7 小和原始的求法是:任何一个数左边比它小的数累加起来。 5左边比它小数累加…...
docker 生成镜像的几个问题
docker 生成镜像的几个问题 根据jdk8.tar.gz 打包Jdk8 镜像失败运行镜像报错差不多是网络ip错误,在网上说重启docker即可解决运行mysql5.7.25 镜像失败向daemon.json文件添加内容导致docker重启失败docker run 命令常用参数根据jdk8.tar.gz 打包Jdk8 镜像失败 首选做准备工作…...
云计算时代的采集利器
大家好!在今天的知识分享中,我们将探讨一个在云计算环境中的爬虫应用利器——独享IP。如果你是一名爬虫程序员,或者对数据采集和网络爬虫有浓厚的兴趣,那么这篇文章将向你展示独享IP在云计算环境下的应用价值。 1. 什么是独享IP&…...
【Unity编辑器扩展】| Inspector监视器面板扩展
前言【Unity编辑器扩展】| Inspector监视器面板扩展一、ContextMenu和ContextMenuItem二、Custom Editors 自定义编辑器三、Property Drawer 属性绘制器总结前言 前面我们介绍了Unity中编辑器扩展的一些基本概念及基础知识,还有编辑器扩展中用到的相关特性Attribute介绍。后面…...
Redis配置
关系型数据库和非关系型数据库 ①了解关系和非关系 关系型数据库 一个结构化的数据库,创建在关系模型基础上,一般面向于记录,包括Oracle、MySQL、SQL Server、Microsoft Access、DB2、postgreSQL等 非关系型数据库 除了主流的关系型数据库…...
CSDN每日一练 |『小艺照镜子』『Ctrl+X,Ctrl+V』『括号上色』2023-09-11
CSDN每日一练 |『小艺照镜子』『Ctrl+X,Ctrl+V』『括号上色』2023-09-11 一、题目名称:小艺照镜子二、题目名称:Ctrl+X,Ctrl+V三、题目名称:括号上色一、题目名称:小艺照镜子 时间限制:1000ms内存限制:256M 题目描述: 已知字符串str。 输出字符串str中最长回文串的长度…...
React 全栈体系(四)
第二章 React面向组件编程 六、组件的生命周期 1. 效果 需求:定义组件实现以下功能: 让指定的文本做显示 / 隐藏的渐变动画从完全可见,到彻底消失,耗时2S点击“不活了”按钮从界面中卸载组件 <!DOCTYPE html> <html lang"e…...
各种UI库使用总结
各种UI库使用总结 工作了这么年,使用了一些UI库,简单的总结一下,UI库也是五花八门,根据自己的产品,应用场景吧,没有绝对合适的,各有各的应用场景吧! QT 这几年前后在一些嵌入式上…...
2023Web前端开发面试手册
HTML基础 1. HTML 文件中的 DOCTYPE 是什么作用? HTML超文本标记语言: 是一个标记语言, 就有对应的语法标准 DOCTYPE 即 Document Type,网页文件的文档类型标准。 主要作用是告诉浏览器的解析器要使用哪种 HTML规范 或 XHTML规范…...
一文了解数据科学Notebook
编者按: 主要介绍什么是Notebook,Notebook在数据科学领域的应用的重要性与优势,以及数据科学家/算法团队在选择Notebook时需考虑哪些关键因素。同时,基于Notebook的筛选考量维度,对常见的Notebook进初步对比分析&#…...
2020年12月 C/C++(二级)真题解析#中国电子学会#全国青少年软件编程等级考试
C/C++编程(1~8级)全部真题・点这里 第1题:数组指定部分逆序重放 将一个数组中的前k项按逆序重新存放。例如,将数组8,6,5,4,1前3项逆序重放得到5,6,8,4,1。 时间限制:1000 内存限制:65536 输入 输入为两行: 第一行两个整数,以空格分隔,分别为数组元素的个数n(1 < n…...
关于ChatGPT的个人的一些观点
问题 1 Q: 你认为ChatGPT是一款非常有用的工具吗? A: 我认为ChatGPT是一款非常有用的工具。它可以帮助人们解决各种问题,包括技术问题、心理问题、生活问题等等。同时,ChatGPT也可以成为人们分享想法和交流的平台,增强人与人之间…...
如何用群晖做自己的网站/移动端seo关键词优化
SQL循环语句declare i intset i1while i<30begininsert into test (userid) values(i)set ii1end---------------while 条件begin执行操作set ii1endWHILE设置重复执行 SQL 语句或语句块的条件。只要指定的条件为真,就重复执行语句。可以使用 BREAK 和 CONTINUE …...
网站关键词描述字数/aso优化贴吧
104. 二叉树的最大深度 没什么好办法,深搜或者宽搜暴力遍历吧 class Solution {public int maxDepth(TreeNode root) {if (root null) {return 0;}return Math.max(maxDepth(root.left), maxDepth(root.right)) 1;} } 转载于:https://www.cnblogs.com/acbingo/p/9…...
潍坊企业宣传片制作公司/杭州seo网站建设靠谱
Bootstrap Method:在统计学中,Bootstrap从原始数据中抽取子集,然后分别求取各个子集的统计特征,最终将统计特征合并。例如求取某国人民的平均身高,不可能测量每一个人的身高,但却可以在10个省市,分别招募10…...
wordpress生成静态html页面/青岛seo杭州厂商
台湾都有哪些国家公园?共八个,详情如下:南区:垦丁国家公园;1984年01月01日成立;屏东县恒春镇。中区:玉山国家公园;1985年04月10日成立;南投,高雄,…...
wordpress百度云/seo软件简单易排名稳定
Best Financing Time Limit: 20000/10000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 29 Accepted Submission(s): 3 Problem Description小A想通过合理投资银行理财产品达到收益最大化。已知小A在未来一段时间中的收入情况&#…...
网站制作公司 深圳/2022年明星百度指数排行
一、解决方案 关掉悬浮球才终于可以打开OPPO辅助功能的权限...