系列文章:前端
(一)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這樣的技術生成插件。