【Flutter】抄一个路由轮子到Flutter上能有多难?

5,286 阅读11分钟

前言

注意看,这个男人叫小帅,常年鸽子精附身的他竟然又开始写起文章来,这一切的背后到底是道德的扭曲还是人性的沦丧?

原来又到了一年一度的封网时期;加了一年班的小帅,陷入了无事可做的地步,看到掘金的消息提醒的红点,突然有点惭愧,于是准备分享和造点目前项目痛点问题的轮子;

回正题,这篇文章要说的就是大家耳熟能详的东西——路由;

还是老样子,先放效果图

  1. 基础路由功能:

基础路由.webp

  1. 跨模块路由、跨模块通讯调用:

跨模块.webp

3.URI输入唤起(测试和Web端交互福音):

手动输入.webp

准备

路由这个概念,其实并没有什么好细说的,毕竟又不是多难的东西,再细说就老生常谈了,目前也有一大批成熟稳定的方案,以我的老本行为例,随便举几个:ARouter(最广泛的)、CC(渐进式组件化改造利器)、TheRouter(货拉拉的一套新方案);

随着时间的发展,其实现在这帮路由框架已经不能单纯的视为路由框架了,应该叫消息处理框架更合适?

但在Flutter这块,虽然路由框架不少,但是简单调研了一下,好像仅仅止步于初步路由,比如说,对于组件化的支持好像基本都没有;更别提更进一步的跨组件通讯了;

这次计划就在这难得的空闲时间内内,将android的几个方案结合一下,看看能不能用Flutter实现出来;(都不是什么难的东西,应该能完成,吧?)

目标

既然要选择实现一个支持组件化的路由,那不如直接按消息中心来实现;

总之先列举下要实现的目标:

  1. 能够支持Navigator、Get等的纯路由功能;
  2. 方便web端调用,甚至能做到纯传参调用跳转,不需要开发手动转一下路由
  3. 跨组件调用、通讯;实现组件化,并能够方便的支持组件单独编译测试

思考部分

路由部分

其实单纯的路由功能,并不是啥值得大谈特谈的东西,说白了仅仅通过注解生成一个存放了路由信息的映射即可,之前文章中提到的build_runner 就可以实现;

组件化部分;

但要实现跨组件的话,就需要一些小小改动;不过嘛,这里打算直接抄ARouter的WareHouse部分做数据模型部分;干净又卫生~~

但是Arouter最后依靠反射将各个组件的路由表注入到主组件的逻辑中心;虽然dart支持反射,但是flutter不支持啊,如何在APP运行时将路由表注入逻辑处理中心?

这里想了下,还是只能生成路由表代码,然后人工手动初始化;好像其他框架也是这么做的,没有什么便捷的方案;

设计

1. 路由

路由这块参考ARouter的方案,整体流程很简单,基本就是这样:

image.png

WareHouse中存放了当前主组件下的子组件、路由表的普通页面,组件通讯接口、拦截器;

PostCard中就是包括条状携带的参数、优先级等配置信息;

其次是生成的路由表;我是这么设计的:

路由表和仓库的结构有点类似;其实就是声明了当前组件内部的路由配置;在这里我是这么设计的:

注解RouterRegister用来表示组件主入口注册位置,路由表会存放在其 .g 文件夹内;

注解Router用来表示真正注册的路由页面,包括Widget甚至provider(包括Provider、Getx的controllers等部分),其注解参数部分还是参考Arouter的;

剩下的东西跟ARouter大同小异,没啥好说的;

2. 编译脚本

编译脚本所做的事就是生成路由表,再说直白点,就是扫描所有文件,将所有带注解的类提取出来组合成一份路由信息表;虽说是一句话就能总结完的东西,但是实现起来还是需要分析一下的;

首先技术框架选型;生成代码并不是难点,说白了new个File,往里面write就完事了;但是如何分析处理才是核心问题;在这点上,暂时定了两个方向:

  1. 直接用analyzer去分析,好多动态化的ast好像都是由其提供的,应该功能挺稳定功能强大;
  2. 用build_runner,很多生成代码的插件都用build_runner来做的,其内部也对整体项目做了各种缓存、分析之类的,挺全面的;

现在暂定用build_runner + code_builder 来做;

原因也没多复杂,就单纯的想看看build_runner 里面都是啥咋运行的……毕竟现在build_runner和code_builder连个文档都没有~~~~~~~~~

总体上先梳理一下步骤路径:

  1. 首先先获取一下注册了入口的模块(主工程是必需的,但是其他module中也可能存在入口;)

  2. 其次要获取到注册路由的页面和接口;按照第一步获取到的入口模块做下基本的路由表划分(如果单独的module中存在注册入口;那么路由表下沉到单独模块中,否则上传到最近的一个已经注册的模块上面;)同时生成引用文件,以供后面调用处理;

  3. 最后根据第二步中做的划分功能,生成最后的汇总路由表到指定位置;

现在根据这个步骤,先确定一下技术方案;

首先花了一天的时间看了下test和一些文档和源码(不得不说,没有文档,技术文档没有也就算了,使用文档都很少……好劝退啊……),现在暂时想了个临时方案,先把demo做出来看看:

首先因为是按三个步骤来的;所以现在的想法是直接建立三个Builder,分别用来处理上面的三个步骤所做的事;

由于这三个步骤是一步一步依赖的来着;但第二步生成的引用文件,会在第三步中调用用来生成路由表;所以如何让文件跨Builder通讯,好像还是有点麻烦的问题;现在看了下方案,好像是可以通过Resource来实现;剩下的就放内存先吧………………

所以现在总结下:

第一个Builder专注于获取注册页面的注解,生成对应信息;并放到内存中,供第二步中生成引用文件;

第二个Builder获取注册入口的注解;根据上一步放到内存中注册信息,生成引用文件,同时根据注册入口配置,划分一下路由表归属,存到内存;

第三个Builder根据上面生成的路由表归属,在各个模块下面,生成具体的路由表;

具体实现+踩坑吐槽

具体实现怎么说呢,总之就是一言难尽,感觉如果不是为了纯折腾,自己用analysis分析语法树,通过dart脚本来生成文件,会比build_runner 好写的多…………执行时间上也会少很多很多;

build_runner 虽说里面各种Builder的挺完善的,但是直接使用的那个Builder限制太多了;写的太痛苦了~差点释放忍术;

回正题:

第一步:

第一个Builder非常简单;用普通的 LibraryBuilder+GeneratorForAnnotation 分析普通页面注解即可,然后将所有的注册页面简单分组;像这样:

image.png

在这里简单的根据模块关系做了区分,然后放到一个static的Map中;

第二步

第二个Builder稍微复杂点(相对):

需要根据第一步的Map,给所有注册过页面所在的模块加上引用文件;同时获取路由注册入口的注解,以供第三步中生成路由表来使用;

在这里我同样使用的是 LibraryBuilder+GeneratorForAnnotation;但是考虑到入口的注解很少,顶天了一个模块也注册一个,综合下来也不多,所以在这里加入了引用文件的生成部分;

关于引用文件的生成,步骤上也不难,毕竟上一步上都根据模块做了划分;那么有多少个key,就在对应module下生成多少个文件即可;

文件检查.png

文件检查

至于生成文件这块,最终经过各种debug看源码……发现还是可能直接New File省事点;别看BuildStep里面自带write方法,但那帮要么过不了合规性检测,要么直接隐藏给你放到build文件下……

image.png

文件生成

内容生成这块也不复杂,毕竟只是提供一个引用文件而已;

image.png

内容生成

经过这步,就可以看到文件夹下生成了引用文件了

image.png

不吐难受的吐槽部分

虽然上面内容不多,但我在这步卡了一天时间,主要是纠结在这里使用PostProcessBuilder还是普通的 Builder;

但是最后发现,各有各的优势和长处,也有不同的使用点;但是在这里,哪个都没法直接一步到位的使用………看了半天源码和 build工程 的 issue 的功夫全部木大;

当时,天真的我还以为这块最大的困难点,毕竟文件都给你整出来了,剩下的还能多难…………

第三步

第三个Builder 要做的事也不多,毕竟引用文件都给你整出来了,剩下的无非再根据第二步的入口配置,有多少个入口配置就生成多少个路由表;剩下的路由部分全部提交给 root module 部分;毕竟PakcageGraph都拿到了,依赖关系这块都是能拿捏的;

这么一说,好像没啥难点是吧;

首先第三个Builder我使用了自定义的 RouterMapPostProcessBuilder ,这步上就不需要再获取什么注解了;所以我将inputExtensions设置为 .yaml ,让其只去匹配 module ;

生成文件这块,也没啥好说的,同第二步算了;不靠BuildStep里面的write和reader,外人毕竟靠不住;

至于内容生成部分,就值得让人玩味了;

本来呢,我是打算直接用带特定占位符的字符串模板,替换占位符为所需数据的方式来实现;但是在看源码的过程中发现,build_runner 是通过code_builder这个东西来做的;

这玩意看上去不好用的样子,但是乍看,好像至少比替换字符串优雅…………说白了,够高大上的那种感觉;还能自动帮你管理import之类的东西;

至于这个code_builder 的学习过程,不提也罢,只能说,文档太少了,readMe就半页,wiki好像都没搭建完……只能看那坨test工程…………啃的确实不太容易;

最终看下生成效果:

Screenrecorder-2023-01-19-17-11-18-428 [MConverter.eu].webp

使用的话,还是大部分路由的使用方式,毕竟要想支持Navigator和GetX,使用方式也就那样,也没啥必要多提,毕竟实质上是生成了一份路由表,路由跳转和注入还是那些东西;

结语

到此,我们就有了一个集合了包括市面上基本组件化路由所需的所有东西;但也仅仅是所需部分,达到勉强能用,离好用还差不少;(其实就是包括路由动画、参数、自定义的构造器、路由动画这些都还没搞,更别提那帮成熟的路由方难大吹特吹的部分了~~~)

另外真的不得不吐槽的是,这build_runner没有文档和demo真的很劝退………………啥都要看源码…………看源码的注释还不一定能懂…………

快来个大佬来啃一下build_runner和code_builder这两个费力不讨好的冷门骨头~~~(白嫖瘾犯了)

那么,一只鸽子精的第一篇偏吐槽的水文就到此;

PS:刚接触API不知道怎么用?没有文档和demo,就只能等大佬或者等死么?

求人不如求AI,试试唯一的真神Github Copilot吧,一键Tab生成demo,就差自动写文档了~~~

PPS:另外,玩具性质的验证可能性的个人学习项目,没有开源地址的,或者可以理解为我又开了一个坑