Fluttify輸出的Flutter插件工程詳解

[TOC]

系列文章:

(一)Flutter插件開發(fā)必備 原生SDK->Dart接口生成引擎Fluttify介紹

(二)如何利用Fluttify開發(fā)一個新的Flutter插件

(三)Fluttify輸出的Flutter插件工程詳解

注:目前Fluttify本身并不對外開放,但是內(nèi)測階段可以免費為你生成插件,只要提供android端的jar/aar和ios端的framework/.h+.a,或者maven坐標和cocoapods名稱即可,聯(lián)系方法請看文末

工程結(jié)構(gòu)

Fluttify的輸出工程是標準的Flutter插件工程,其中輸出的原生語言是java(android)和objc(ios)。

android端使用java是因為從字節(jié)碼反編譯到j(luò)ava的時候,如果字節(jié)碼來自kotlin,那么會有一些特殊的標記,導致一些情況下(比如基礎(chǔ)類型和對應包裝類的混淆)需要多余的工作去適配,為了加強兼容性,所以后期選擇了java作為生成的原生語言。

ios端選擇objc也是類似的原因,objc的方法轉(zhuǎn)為swift的方法時,方法名會自動轉(zhuǎn)換,一些涉及到介詞的方法名都會被轉(zhuǎn)換為swift風格,這也導致了一些額外的工作去轉(zhuǎn)換objc方法名到swift,所以最終選擇了objc作為輸出語言。

dart端結(jié)構(gòu)

引用自上一篇文章

Fluttify的產(chǎn)物是一個標準的Flutter的插件工程,所以lib文件夾之上的結(jié)構(gòu)都和普通插件一樣。lib文件夾下會分成androidios文件夾,分別放置各平臺SDK中的類(枚舉/接口等)對應的Dart類(枚舉/接口等)。android/ios文件夾下還會各自生成:

  • function.g.dart文件:生成的所有頂層函數(shù);
  • type_op.g.dart文件:所有的asis方法,用來判斷類型和造型;
  • ios/android.export.g.dart文件:導出所有的ios/android類型;
  • platformview文件夾:生成的所有PlatformView

習慣上會在lib文件夾下再加一個dart文件夾,放置對各平臺進行抽象的代碼,并且最后對外export的時候,只export這個文件夾下的文件。

lib文件夾結(jié)構(gòu)概覽:
.
├── janalytics_fluttify.dart
└── src
├── android
│ ├── android.export.g.dart
│ ├── cn ... android端對應的dart接口
│ └── type_op.g.dart
├── dart
│ └── janalytics_service.dart
└── ios
├── JANALYTICSBrowseEvent.g.dart
├── ...其他生成文件
├── functions.g.dart
├── ios.export.g.dart
└── type_op.g.dart

原生端結(jié)構(gòu)

原生端生成的文件分成兩種。

第一種是PlatformViewFactory類,負責PlatformView的創(chuàng)建,F(xiàn)luttify會掃描到SDK內(nèi)所有的View類并為其生成PlatformViewFactory類。第二種是主Plugin類,負責所有的MethodChannel的調(diào)用處理。

示例的android端的文件夾結(jié)構(gòu),ios端類似:

.
└── me
    └── yohom
        └── amap_map_fluttify
            ├── AmapMapFluttifyPlugin.java // 主Plugin
            ├── DownloadProgressViewFactory.java // 以下都是PlatformViewFactory
            ├── MapViewFactory.java
            ├── TextureMapViewFactory.java
            └── WearMapViewFactory.java

語言元素的映射

java中的類一般都會有作為命名空間使用的包名,平時使用的時候都會先import,再使用簡稱來引用。Fluttify實現(xiàn)初期,生成的dart類也是直接使用java類的簡稱,但這很容易就會出現(xiàn)類名沖突,所以最終決定使用全類名來生成java對應的dart類。其規(guī)則為:
java:

package com.test;
class A {}

轉(zhuǎn)換為dart:

class com_test_A {}

在這點上objc就直接了很多,因為objc類本身就沒有命名空間,類名就是它的全名,所以objc這邊的類名不需要轉(zhuǎn)換直接用到dart類名上即可,規(guī)則為:

@interface TestClassA
@end

轉(zhuǎn)換為:

class TestClassA {}

接口

所謂接口在java和objc的語境下都是代表可以多重繼承的類型。雖然dart也有隱式接口,但是objc的接口(protocol)可以有實現(xiàn)且子類可以不實現(xiàn)所有的方法,而dart一旦implements了一個隱式接口,就必須實現(xiàn)所有的方法,所以dart的隱式接口不能作為objc的protocol的等價角色。

萬幸的是dart支持mixin,mixin正好能夠處理objc的protocol特性。

示例
java:

package com.test;

interface InterfaceA {}
class ClassA implements InterfaceA {}

轉(zhuǎn)換為dart:

class com_test_ClassA extends java_lang_Object with com_test_Interface {}

objc:

@protocol TestInterfaceA
@end

@interface TestClassA
@end

轉(zhuǎn)換為dart:

class TestClassA extends NSObject with TestInterfaceA {}

方法

java,objc以及dart的方法在概念上基本一致,除了objc端的一些指針類型和值類型的區(qū)分,其他的都差不多。這里給一個例子闡述一下:
java:

package com.test;
class TestClassA {
  public String testMethod(int arg) { /* 方法內(nèi)容 */ }
}

轉(zhuǎn)換為dart:

class com_test_TestClassA {
  String testMethod(int arg) { /* 調(diào)用原生代碼 */ }
}

objc:

@interface TestClassA
- (NSString*) testMethod: (NSInteger) arg;
@end

轉(zhuǎn)換為dart:

class TestClassA {
  String testMethod(int arg) { /* 調(diào)用原生代碼 */}
}

函數(shù)

java沒有頂層函數(shù),所以沒有需要處理的。

objc的函數(shù)實際上就是c函數(shù),而dart也支持頂層函數(shù),且與objc的函數(shù)語義上沒有太大的出入。

常量

目前支持轉(zhuǎn)換java的類常量到dart的類常量。

回調(diào)

回調(diào)分為lambda和delegate,不過在Fluttify的生成代碼中的角色差不多。

回調(diào)的實現(xiàn)主要通過雙向的MethodChannel調(diào)用來實現(xiàn),比如說java端有一個方法:

void setCallback(Callback callback) { /* 代碼 */ }

生成的dart代碼會是這樣的:

Future<void> setCallback(Callback callback) async {
  await MethodChannel('some channel').invokeMethod('some method');
  
  // 這里會接收到native端的調(diào)用
  MethodChannel('some channel callback').setMethodHandler((methodResult) {
    // 處理原生的回調(diào)
    callback.onXXX();
  });
}

內(nèi)存管理

dart端

Dart端的所有SDK類都會間接繼承foundation_fluttify中定義的Ref類,這個類代表是一個引用類,內(nèi)部含有一個refId字段,保存的是原生端對應對象的id。

目前這個id的實現(xiàn)使用的是對象的hashCode。android端所有的對象都會有hashCode()方法,而ios端只有繼承NSObject的類才有hash字段,如果碰到有處理結(jié)構(gòu)體的需要,則用NSValue包裝結(jié)構(gòu)體后再調(diào)用其hash字段。

當調(diào)用SDK類的方法時,會把refId傳遞給原生,然后原生從全局HEAP中獲取到目標對象,然后再在目標對象上進行調(diào)用。

dart端還提供了一個kNativeObjectPool全局集合對象,這個集合對象保存了所有的原生對象的引用(即refId),在需要釋放對象時,可以對這個集合進行操作。

原生端

foundation_fluttify的原生端提供了一個HEAP全局集合,用來存放插件調(diào)用過程中產(chǎn)生的原生對象。當dart端開始一個方法調(diào)用時,原生端便會先從HEAP中獲取到目標對象,再調(diào)用對應方法。

如果需要把釋放一個對象需要把它從HEAP中刪除,不然HEAP會一直強引用對象導致一直占用內(nèi)存。從HEAP中刪除后,后續(xù)的內(nèi)存管理就交給系統(tǒng)來處理了。

結(jié)語

本文對Fluttify輸出的插件工程的結(jié)構(gòu)作了大致的介紹。這些其實也包含了很多我在實現(xiàn)Fluttify過程中遇到的困難,包括java/objc/dart這些語言在語法上的統(tǒng)一,如何實現(xiàn)回調(diào)等等,還有很多很多細節(jié)的問題,更有甚者還要給SDK作者的一些騷操作騷寫法擦屁股。

最后還是推薦一波,如果有想要生成插件的老鐵也可以聯(lián)系我(382146139@qq.com),目前Fluttify還處于內(nèi)測階段,不會收取任何費用,有任何反饋都可以往fluttify-feedback提issue,歡迎各位的反饋。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容