当前位置: 首页 > news >正文

【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的特定调用的路由。

现在,您可以在HomeScreenProfileScreen中获取通过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主要有三个关键功能:

  1. 通过ServiceLoader获取接口的所有实现类。
  2. 将特定的目标类与URI进行匹配。
  3. 从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&#xff08;Kotlin-Router&#xff09;是一个简单而轻量级的Kotlin路由框架。 具体来说&#xff0c;KRouter是一个通过URI来发现接口实现类的框架。它的使用方式如下&#xff1a; val homeScreen KRouter.route&l…...

时间管理类书籍阅读笔记

背景 这段时间看了时间管理方面的书籍&#xff0c;大部分和早晨时间利用相关。之所以有了利用早晨时间的想法&#xff0c;是某天下班后&#xff0c;感觉很疲惫&#xff0c;什么都不想做&#xff0c;于是就打了一晚上游戏&#xff0c;然后第二天重复着这样的生活。 突然意识到…...

CSS文字居中对齐学习

CSS使用text-align属性设置文字对齐方式&#xff1b;text-align:center&#xff0c;这样就设置了文字居中对齐&#xff1b; <!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)

技术栈&#xff1a; 项目搭建&#xff1a;React 官方脚手架 create-react-appreact hooks状态管理&#xff1a;redux 、 redux-thunkUI 组件库&#xff1a;antd-mobileajax请求库&#xff1a;axios路由&#xff1a;react-router-dom 以及 historyCSS 预编译器&#xff1a;sass…...

c#查看代码的执行耗时( Stopwatch )

我们如果需要看某段代码的执行耗时&#xff0c;会通过如下的方式进行查看 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网络爬虫库:轻松提取网页数据的利器

网络爬虫是一种自动化程序&#xff0c;它可以通过访问网页并提取所需的数据。Python是一种流行的编程语言&#xff0c;拥有许多强大的网络爬虫库。在本文中&#xff0c;我们将介绍几个常用的Python网络爬虫库以及它们的使用。 Requests库 Requests是一个简单而优雅的HTTP库&…...

YOLOv5算法改进(15)— 更换Neck之AFPN

前言&#xff1a;Hello大家好&#xff0c;我是小哥谈。在YOLOv5中添加AFPN&#xff08;Adaptive Feature Pyramid Network&#xff09;可以提高目标检测的准确性。AFPN是一种用于目标检测任务的功能增强模块&#xff0c;它能够自适应地融合来自不同层级的特征图&#xff0c;以提…...

Vue2项目练手——通用后台管理项目第七节

Vue2项目练手——通用后台管理项目 用户管理分页使用的组件Users.vuemock.js 关键字搜索区Users.vue 权限管理登录页面样式修改Login.vue 登录权限使用token对用户鉴&#xff0c;使用cookie对当前信息保存&#xff08;类似localstorage&#xff09;Login.vuerouter/index.js 登…...

《Web安全基础》04. 文件操作安全

web 1&#xff1a;文件操作安全2&#xff1a;文件上传漏洞2.1&#xff1a;简介2.2&#xff1a;防护与绕过2.3&#xff1a;WAF 绕过2.3.1&#xff1a;数据溢出2.3.2&#xff1a;符号变异2.3.3&#xff1a;数据截断2.3.4&#xff1a;重复数据 3&#xff1a;文件包含漏洞4&#xf…...

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无法启动

报错环境&#xff1a; Linux MySQL 具体报错&#xff1a; Cant connect to local MySQL server through socket /var/run/mysqld/mysqld.sock 排错思路&#xff1a; 当尝试启动MySQL服务时&#xff0c;如果出现无法连接到MySQL服务的错误&#xff0c;可能是由于MySQL服务未正确…...

Vue中表单手机号验证与手机号归属地查询

下面是一篇关于Vue中如何进行表单手机号验证与手机号归属地查询的Markdown格式的文章&#xff0c;包含代码示例。 Vue中表单手机号验证与手机号归属地查询 手机号验证和归属地查询是许多Web应用程序中常见的功能之一。在Vue.js中&#xff0c;我们可以轻松地实现这两个功能。本…...

初高(重要的是高中)中数学知识点综合

1. 集合 1.1 集合的由来和确定性 确定对象构成的整体称为集合&#xff08;组成集合的元素必须是确定的 &#xff09;&#xff0c;每个集合内的对象个体成为元素(Element)。确定性&#xff1a; 给定一个集合&#xff0c;任何一个对象是不是这个集合内的元素&#xff0c;就已经确…...

Fiddler 系列教程(二) Composer创建和发送HTTP Request跟手机抓包

Fiddler Composer介绍 Composer的官方帮助文档&#xff1a;http://www.fiddler2.com/fiddler/help/composer.asp Fiddler的作者把HTTP Request发射器取名叫Composer(中文意思是&#xff1a;乐曲的创造者), 很有诗意 Fiddler Composer的功能就是用来创建HTTP Request 然后发送…...

淘宝平台开放接口API接口

淘宝平台开放接口API接口是指淘宝平台提供给第三方开发者的一组接口&#xff0c;用于实现与淘宝平台的数据交互和功能扩展。通过API接口&#xff0c;第三方开发者可以获取淘宝平台上的商品信息、订单信息、用户信息等数据&#xff0c;也可以实现商品的发布、订单的创建和支付等…...

缓存夺命连环问

1. 为什么要用缓存&#xff1f; 用缓存&#xff0c;主要有两个用途&#xff1a;高性能、高并发。 高性能 假设这么个场景&#xff0c;你有个操作&#xff0c;一个请求过来&#xff0c;吭哧吭哧你各种乱七八糟操作 MySQL&#xff0c;半天查出来一个结果&#xff0c;耗时 600m…...

模型生成自动化测试用例

自动产生的测试用例本就应该由程序自动执行&#xff0c;这其实也就是NModel推荐的模式。先回过头来看看文章中制作的模型&#xff0c;模型里面将登录、注销、用户名以及密码等要素都抽象出来了&#xff0c;而NModel是以这些抽象出来的动作&#xff08;登录、注销&#xff09;和…...

归并排序-面试例子

小数和问题 描述 在一个数组中&#xff0c;一个数左边比它小的数的总和&#xff0c;叫数的小和&#xff0c;所有数的小和累加起来&#xff0c;叫数组小和。求数组小和。 例子 5 2 6 1 7 小和原始的求法是&#xff1a;任何一个数左边比它小的数累加起来。 5左边比它小数累加…...

docker 生成镜像的几个问题

docker 生成镜像的几个问题 根据jdk8.tar.gz 打包Jdk8 镜像失败运行镜像报错差不多是网络ip错误,在网上说重启docker即可解决运行mysql5.7.25 镜像失败向daemon.json文件添加内容导致docker重启失败docker run 命令常用参数根据jdk8.tar.gz 打包Jdk8 镜像失败 首选做准备工作…...

云计算时代的采集利器

大家好&#xff01;在今天的知识分享中&#xff0c;我们将探讨一个在云计算环境中的爬虫应用利器——独享IP。如果你是一名爬虫程序员&#xff0c;或者对数据采集和网络爬虫有浓厚的兴趣&#xff0c;那么这篇文章将向你展示独享IP在云计算环境下的应用价值。 1. 什么是独享IP&…...

【Unity编辑器扩展】| Inspector监视器面板扩展

前言【Unity编辑器扩展】| Inspector监视器面板扩展一、ContextMenu和ContextMenuItem二、Custom Editors 自定义编辑器三、Property Drawer 属性绘制器总结前言 前面我们介绍了Unity中编辑器扩展的一些基本概念及基础知识,还有编辑器扩展中用到的相关特性Attribute介绍。后面…...

Redis配置

关系型数据库和非关系型数据库 ①了解关系和非关系 关系型数据库 一个结构化的数据库&#xff0c;创建在关系模型基础上&#xff0c;一般面向于记录&#xff0c;包括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. 效果 需求:定义组件实现以下功能&#xff1a; 让指定的文本做显示 / 隐藏的渐变动画从完全可见&#xff0c;到彻底消失&#xff0c;耗时2S点击“不活了”按钮从界面中卸载组件 <!DOCTYPE html> <html lang"e…...

各种UI库使用总结

各种UI库使用总结 工作了这么年&#xff0c;使用了一些UI库&#xff0c;简单的总结一下&#xff0c;UI库也是五花八门&#xff0c;根据自己的产品&#xff0c;应用场景吧&#xff0c;没有绝对合适的&#xff0c;各有各的应用场景吧&#xff01; QT 这几年前后在一些嵌入式上…...

2023Web前端开发面试手册

​​​​​​​​ HTML基础 1. HTML 文件中的 DOCTYPE 是什么作用&#xff1f; HTML超文本标记语言: 是一个标记语言, 就有对应的语法标准 DOCTYPE 即 Document Type&#xff0c;网页文件的文档类型标准。 主要作用是告诉浏览器的解析器要使用哪种 HTML规范 或 XHTML规范…...

一文了解数据科学Notebook

编者按&#xff1a; 主要介绍什么是Notebook&#xff0c;Notebook在数据科学领域的应用的重要性与优势&#xff0c;以及数据科学家/算法团队在选择Notebook时需考虑哪些关键因素。同时&#xff0c;基于Notebook的筛选考量维度&#xff0c;对常见的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是一款非常有用的工具吗&#xff1f; A: 我认为ChatGPT是一款非常有用的工具。它可以帮助人们解决各种问题&#xff0c;包括技术问题、心理问题、生活问题等等。同时&#xff0c;ChatGPT也可以成为人们分享想法和交流的平台&#xff0c;增强人与人之间…...

如何用群晖做自己的网站/移动端seo关键词优化

SQL循环语句declare i intset i1while i<30begininsert into test (userid) values(i)set ii1end---------------while 条件begin执行操作set ii1endWHILE设置重复执行 SQL 语句或语句块的条件。只要指定的条件为真&#xff0c;就重复执行语句。可以使用 BREAK 和 CONTINUE …...

网站关键词描述字数/aso优化贴吧

104. 二叉树的最大深度 没什么好办法&#xff0c;深搜或者宽搜暴力遍历吧 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:在统计学中&#xff0c;Bootstrap从原始数据中抽取子集&#xff0c;然后分别求取各个子集的统计特征&#xff0c;最终将统计特征合并。例如求取某国人民的平均身高&#xff0c;不可能测量每一个人的身高&#xff0c;但却可以在10个省市&#xff0c;分别招募10…...

wordpress生成静态html页面/青岛seo杭州厂商

台湾都有哪些国家公园&#xff1f;共八个&#xff0c;详情如下&#xff1a;南区&#xff1a;垦丁国家公园&#xff1b;1984年01月01日成立&#xff1b;屏东县恒春镇。中区&#xff1a;玉山国家公园&#xff1b;1985年04月10日成立&#xff1b;南投&#xff0c;高雄&#xff0c;…...

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辅助功能的权限...