系列文章: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
常規的開發方式是把功能實現下沉到原生端,而後再在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裏的全部接口反射出來,而後進行生成代碼的拼接。這個方案有幾個瓶頸:
Fluttify
作成一個服務部署到服務器上還得部署到macos系統的服務器上?Flutter for web
和Flutter Desktop
徹底變成了未知的狀況,並且還要去學對應平臺的工程結構以及對應語言的反射機制。在短暫試驗了反射方案後,我認爲反射不是答案。
在反射方案失敗後,我開始想能不能直接解析源文件生成AST,這樣的話限制會小不少,並且後續再有平臺接入,也不會產生額外的學習成本。可是如何解析呢?不懂編譯原理的我搜索了幾天後就快要放棄,就在一天在github上搜索了language parse
關鍵字以後,我找到了答案,那就是antlr
。
antlr
提供了很好的抽象層去遍歷源文件,解析代碼再也不是難題。並且antlr
提供了很是多的語法文件,這其中包括了java
,objective-c
,以及後續Flutter for web
和Flutter Desktop
須要的語法文件,這爲Fluttify
的後續發展鋪平了道路。
Fluttify
最核心的原理就是通過antlr
解析以後,產生一個結構化的SDK表示,再根據這個SDK表示生成Flutter
插件工程。甚至再擴展一下,利用這個結構化的SDK表示,也能夠爲React Native
以及其餘的框架生成插件。
這裏提供一個高德地圖SDK中比較典型的案例作一個分析。高德地圖中,提供了在地圖上顯示標記的能力,在Android端這個標記叫Marker
,在iOS端叫Annotation
。這個添加標記的接口坑爹的地方在哪裏呢?
在Android端,只須要一步,調用AMap::addMarker(MarkerOption)
便可。全部的配置項都在MarkerOption
中,而且添加完成後會返回對應的Marker
對象供你操做。
在iOS端,須要三步:
MAMapView::addAnnotation(MAAnnotation)
;MAMapView::delegate
配置回調,通常都配置成self
,由於delegate
是弱引用;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而且須要同步返回Dart端構造的對象時,理想的處理方式是經過MethodChannel調用Dart,在Dart端構造出對應的對象,而後返回給回調方法,可是由於Native調用Dart時,都是異步的,因此沒辦法同步返回給回調方法,具體案例就是MAMapViewDelegate::mapView:viewForAnnotation
這個方法。解決方案是隻能手寫原生代碼。github上有關於同步MethodChannel的issue,感興趣的能夠關注一下 github.com/flutter/flu…
因爲在面向對象
中,子類實例能夠賦值給父類變量,當Native端返回一個接口父類給Dart端時,Dart端沒法構造這個接口類。目前的解決方案是找出SDK中實現該接口的第一個類進行實例化。具體案例是MAMapViewDelegate::mapView:DidAnnotationViewTapped
方法。
Fluttify
?請郵件聯繫我(382146139@qq.com), 並說明來源。