Flutter插件开发必备 原生SDK->Dart接口生成引擎`Fluttify`介绍

Fluttify

系列文章:java

(一)Flutter插件开发必备 原生SDK->Dart接口生成引擎Fluttify介绍git

(二)如何利用Fluttify开发一个新的Flutter插件github

(三)Fluttify输出Flutter插件工程详解web

(四)Fluttify编译器原理介绍objective-c

Fluttify网站:fluttify.commacos

Fluttify是什么?

Fluttify是一个能够为原生SDK生成Dart接口的一个工具。github地址:github.com/fluttify-pr…编程

Fluttify解决了什么问题?

这里要先说明一下插件开发的几种方式:xcode

  • 常规插件开发方式(Native厚,Dart薄)

  常规的开发方式是把功能实现下沉到原生端,而后再在Dart端对封装好的原生方法进行一层薄的抽象。这种开发方式在两端SDK接口设计一致时,碰到的阻力会比较小,好比Google Map的官方插件就是这样开发的。服务器

  去年(2018)年末我开发amap_base的时候,一开始也是按照Google Map插件的套路来开发的,Android端很是顺利,由于高德的Android的SDK接口大部分都是抄的Google Map。可是到iOS端时,就头大了,由于iOS端没有抄Google Map的接口设计(甚至同一个功能,须要调用的方法个数也不同!),致使想要把功能实现下沉到两端再在Dart作一层薄的抽象变成异常困难,且大量的字段须要在两个端之间对比,开发amap_base的过程让我心力憔悴。markdown

  • Fluttify插件开发方式(Native薄,Dart厚)

  amap_base的经验让我开始思考如何才能让插件开发的过程可以最小化的机械劳动。在一段时间的精神编程后,我认为为两端原生接口作抽象的工做必定要放到Dart端这边来作,而不是在原生端实现好接口,而后在Dart端作一层薄的抽象,应该是Dart端必定要厚,原生端必定要薄

  那么原生端要薄到什么程度呢?编码初期一直不停在调整这个度。一开始我只想为非model类的公开接口生成代码,model类逐句翻译到Dart。写着写着发现逐句翻译这件事情并不容易,并且如何判断一个类是model类仍是非model类呢?这是没法实现的。因此在这以后我以为为全部的公开类都生成对应的Dart类,而且只区分View类和非View类,由于View类须要生成对应的PlatformView,而后使用MethodChannel将Dart/Native链接在一块儿。

  Fluttify的目标是解决开发者只懂一个端或者两端都不懂时,能够借助Fluttify生成的Dart接口进行开发,从而屏蔽了原生代码。

原理介绍

反射

  在有了Fluttify的概念以后,我最开始想到的方法天然是把SDK依赖到一个模板工程,而后经过反射把原生SDK里的全部接口反射出来,而后进行生成代码的拼接。这个方案有几个瓶颈:

  1. Android端,在模板工程(普通gradle工程)的gradle中依赖目标jar,能够实现代码的反射,可是若是是aar怎么办?
  2. iOS要依赖framework就必须是一个xcode工程,那么就必需要macos环境,若是想要把Fluttify作成一个服务部署到服务器上还得部署到macos系统的服务器上?
  3. 后续实现Flutter for webFlutter Desktop彻底变成了未知的状况,并且还要去学对应平台的工程结构以及对应语言的反射机制。

在短暂试验了反射方案后,我认为反射不是答案。

代码解析

  在反射方案失败后,我开始想能不能直接解析源文件生成AST,这样的话限制会小不少,并且后续再有平台接入,也不会产生额外的学习成本。可是如何解析呢?不懂编译原理的我搜索了几天后就快要放弃,就在一天在github上搜索了language parse关键字以后,我找到了答案,那就是antlr

  antlr提供了很好的抽象层去遍历源文件,解析代码再也不是难题。并且antlr提供了很是多的语法文件,这其中包括了javaobjective-c,以及后续Flutter for webFlutter Desktop须要的语法文件,这为Fluttify的后续发展铺平了道路。

  Fluttify最核心的原理就是通过antlr解析以后,产生一个结构化的SDK表示,再根据这个SDK表示生成Flutter插件工程。甚至再扩展一下,利用这个结构化的SDK表示,也能够为React Native以及其余的框架生成插件。

案例分析

  这里提供一个高德地图SDK中比较典型的案例作一个分析。高德地图中,提供了在地图上显示标记的能力,在Android端这个标记叫Marker,在iOS端叫Annotation。这个添加标记的接口坑爹的地方在哪里呢?

在Android端,只须要一步,调用AMap::addMarker(MarkerOption)便可。全部的配置项都在MarkerOption中,而且添加完成后会返回对应的Marker对象供你操做。

在iOS端,须要三步:

  1. 调用MAMapView::addAnnotation(MAAnnotation)
  2. 调用MAMapView::delegate配置回调,通常都配置成self,由于delegate是弱引用;
  3. 实现MAMapViewDelegate::mapView:viewForAnnotation,根据第一步中的MAAnnotation配置MAPinAnnotationView并返回MAPinAnnotationView的实例,在Android中一次性配置的标记参数被分散在了MAAnnotation中和MAPinAnnotationView中;

  你说这样如何才能在Native端统一好抽象供Dart端调用?不是说作不到,而是须要额外的复杂度,一旦代码上了规模,就必须当心翼翼地处理这些复杂度。另外iOS端这种设计,如何才能跟Android端同样构造出一个标记对象返回给Dart端?我有时候以为高德地图的iOS SDK做者的脑回路也真的是清奇,怎么会想出这种设计的,跟高德Android SDK同样抄Google Map不香吗?最滑稽的是什么,百度地图的接口设计跟高德的一毛同样,我也不知道究竟是谁抄的谁。

  开发amap_base的时候我引入了一些多余的复杂度去努力统一Android和iOS的接口,可是效果仍然是不理想,好比原生接口返回类型是SDK内的非model类型时(addMarker方法),我没法把它(Marker对象)返回给Dart端,也就没法在Dart端去控制单个的Marker,除非再在Native端引入一些复杂度,好比把Marker对象放到一个全局的列表或者Map中去。

  在有了Fluttify以后,全部的这些不一致均可以放到Dart端这边来调度,Native代码只负责输出。

已知的限制以及临时解决方案

目前的Fluttify也不是什么接口都能生成的,一些是由于Flutter自己的限制,另外一些是面向对象的特性致使的。

Native调用Dart方法同步返回

当Native端调用Dart而且须要同步返回Dart端构造的对象时,理想的处理方式是经过MethodChannel调用Dart,在Dart端构造出对应的对象,而后返回给回调方法,可是由于Native调用Dart时,都是异步的,因此没办法同步返回给回调方法,具体案例就是MAMapViewDelegate::mapView:viewForAnnotation这个方法。解决方案是只能手写原生代码。github上有关于同步MethodChannel的issue,感兴趣的能够关注一下 github.com/flutter/flu…

Dart调用Native返回接口类型

因为在面向对象中,子类实例能够赋值给父类变量,当Native端返回一个接口父类给Dart端时,Dart端没法构造这个接口类。目前的解决方案是找出SDK中实现该接口的第一个类进行实例化。具体案例是MAMapViewDelegate::mapView:DidAnnotationViewTapped方法。

如何使用Fluttify

请邮件联系我(382146139@qq.com), 并说明来源。

路线图

  • (进行中)在高德地图SDK上实现大部分功能(选择高德只是由于用例比较全);
  • 创建一个网站(若是能够的话使用Flutter构建)开始进行内测;
  • 开放使用;

qq群

相关文章
相关标签/搜索