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羣

相關文章
相關標籤/搜索