系列文章:前端
(一)Flutter插件开发必备 原生SDK->Dart接口生成引擎Fluttify
介绍java
(二)如何利用Fluttify开发一个新的Flutter插件android
(三)Fluttify输出Flutter插件工程详解ios
(四)Fluttify编译器原理介绍算法
Fluttify网站:fluttify.comjson
普通开发Flutter插件的方式既繁琐又容易出错,由于须要在dart和原生之间传递大量的数据,在这个过程当中须要手写大量模板代码。前段时间饿了么团队发布了一个插件dna,这个插件提供了一个通用的channel在dart和原生之间传递数据,避免了手写原生代码,过程当中使用反射来调用对应原生代码。和dna
不一样的是Fluttify提供了一个更静态的方案,即从原生出发,生成对应的dart绑定。后端
正如Fluttify的定位“编译器”所示,Fluttify的总体实现分红前端和后端,链接前端和后端的是一个表明SDK的中间表示。api
前端负责借助antlr从jar/aar和framework中解析出中间表示(目前使用的是json格式),后端则消费这个中间表示,把其转换成dart/java/objc代码。缓存
引用自Wiki:bash
ANTLR(全名:ANother Tool for Language Recognition)是基于LL(*)算法实现的语法解析器生成器(parser generator),用Java语言编写,使用自上而下(top-down)的递归降低LL剖析器方法。由旧金山大学的Terence Parr博士等人于1989年开始发展。
ANTLR自己是Java实现的,向ANTLR输入一个语法规则文件,可以生成对应语言源文件的解析代码,目前支持输出Java, C#, Python2|3, JavaScript, Go, C++, Swift代码,也就是说你能够用这些语言的代码解析任何语法规则对应的源代码。
好比说如今有一份A语言的语法文件A.g4,把这个文件做为参数传入ANTLR,ANTLR能够为你生成一份Swift代码,这份Swift代码能够遍历A语言的源代码,你能够解析出A语言代码里任何你感兴趣的部分。
咱们这里使用ANTLR默认的输出语言Java。
Fluttify支持从maven坐标直接生成插件工程,其中的难点即是怎么把maven坐标下载成真实的SDK,我找了不少maven相关的rest api服务,可是要么是少字段,要么是速度很慢,再要么就是商业接口要付费。
幸运的是Fluttify是基于gradle实现的,一顿google后,发现gradle api能够指定maven坐标直接下载artifact,后来才发现其实这跟在build.gradle里添加依赖是同样的。只不过平时都是写在build.gradle里,换成gradle api就懵逼了。
project.repositories.run { maven { it.url = URI("http://maven.aliyun.com/nexus/content/groups/public/") } jcenter() mavenCentral() } val config = project.configurations.create("targetJar") val dep = project.dependencies.create(ext.android.remote.run { "$org:$name:$version" }) config.dependencies.add(dep) config.files // 调用这句后,若是本地没有缓存,gradle就会去下载 复制代码
从cocoapods获取到源代码的方法就更trick一点,一开始也是各类找有没有开放的rest api,不少地方说cocoapods官方有开放api,可是试了以后都不能用。后来只能想一些偏门的方法,好比说直接读取cocoapods的本地索引。
cocoapods在用户目录下会有一个~/.cocoapods/repos/master/Specs
文件夹,一开始看见这个文件夹下的内容很容易会被劝退,由于它是这样的:
这些16进制数字文件夹会有三层,到第4层就是实际的pod了,每一个pod下面会有全部版本的podspec.json,剩下的工做就是解析这个json,获取到里面的下载连接,下载压缩包便可。
生成中间表示第一步须要拿到源代码,android端采用反编译jar的方式获取到源代码,ios端则直接拿到objc的头文件直接解析便可。
反编译使用的是intellij使用的Fernflower反编译器,反编译结果效果不错,目前没有碰到大问题。
第二步就是遍历源代码,这是整个编译器中最困(bu)难(dong)的部分。因为对objc语言的不熟悉,不少objc的语言元素的叫法分不清哪一个是哪一个,各类specifier,并且不少语法元素能够递归嵌套,很难从语法文件想象出本来的源代码的样貌。
个中细节再也不赘述,最终编译器会把SDK分解为7个Java类,分别是SDK
,Type
,Constructor
,Field
,Method
,Parameter
,Variable
。
一个SDK会被一个SDK
类表示,而后SDK
对象会被序列化,并写入一个文件中,供后端使用。
一个中间表示的部份内容:
{ "version": "0.0.1", "platform": "Android", "libs": [ { "name": "com", "types": [ { "platform": "Android", "name": "com.autonavi.ae.gmap.maploader.Pools$Pool", "genericTypes": [ "com.autonavi.ae.gmap.maploader.T" ], "typeType": "Interface", "isPublic": true, "isAbstract": true, "isInnerType": true, "isStaticType": true, "isJsonable": false, "superClass": "", "interfaces": [], "constructors": [], "fields": [], "methods": [ { "exactName": "acquire", "returnType": "com.autonavi.ae.gmap.maploader.T", "name": "acquire", "formalParams": [], "isStatic": false, "isAbstract": true, "isPublic": true, "className": "com.autonavi.ae.gmap.maploader.Pools$Pool", "platform": "Android", "isDeprecated": false, "isFunction": false, "isGenericMethod": false }, ... 复制代码
有了中间表示后,其实编译器后端的工做就相对轻松了。精力消耗都在摸索模板内容中。主要的工做就是怎么把(好比)Method对象转换为Dart/Java/Objc对应的代码。
因为Java,Objc和Dart之间的语法并不能一一对应,因此在编写模板的过程当中也遇到很多问题。
好比说高德地图的MAMapView
设置delegate
,因为delegate
是弱引用,因此任何新建立的MAMapViewDelegate
对象赋值给delegate
都会被当即回收,由于引用计数没有增长,因此delegate必须赋值为self
,一开始找不到合适的对象来当这个self,整个插件里只有主Plugin类和PlatformViewFactory类两种类型的对象,因此只能让PlatformViewFactory类来承当这个self
,也能让delegate和PlatformViewFactory的生命周期保持一致。
所谓编译
不过就是把一段文本转化成另外一段文本,创造Fluttify的过程当中,对于Fluttify究竟是一个什么东西的见解也一直在转变,这都源于个人知识匮乏,一开始以为它是一个生成器,因此定位成一个所谓的引擎
,后来xster大佬在Fluttify输出Flutter插件工程详解 下向我推荐了他们Flutter官方搞的一个相似的东西dartle,我才意识到Fluttify实际上是一个编译器,和大多数编译器同样,它有前端和后端,只不过它的目标代码再也不是二进制而是可读的代码,甚至理论上借助中间表示,也能够为React Native这样的技术生成插件。