Fluttify輸出Flutter外掛工程詳解

小山包發表於2020-01-11

[TOC]

系列文章:

(一)Flutter外掛開發必備 原生SDK->Dart介面生成引擎Fluttify介紹

(二)如何利用Fluttify開發一個新的Flutter外掛

(三)Fluttify輸出Flutter外掛工程詳解

注:目前Fluttify本身並不對外開放,但是內測階段可以免費為你生成外掛,只要提供android端的jar/aar和ios端的framework/.h+.a,或者maven座標和cocoapods名稱即可,聯絡方法請看文末

工程結構

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

android端使用java是因為從位元組碼反編譯到java的時候,如果位元組碼來自kotlin,那麼會有一些特殊的標記,導致一些情況下(比如基礎型別和對應包裝類的混淆)需要多餘的工作去適配,為了加強相容性,所以後期選擇了java作為生成的原生語言。

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

dart端結構

引用自上一篇文章

Fluttify的產物是一個標準的Flutter的外掛工程,所以lib資料夾之上的結構都和普通外掛一樣。lib資料夾下會分成androidios資料夾,分別放置各平臺SDK中的類(列舉/介面等)對應的Dart類(列舉/介面等)。android/ios資料夾下還會各自生成:

  • function.g.dart檔案:生成的所有頂層函式;
  • type_op.g.dart檔案:所有的asis方法,用來判斷型別和造型;
  • ios/android.export.g.dart檔案:匯出所有的ios/android型別;
  • platformview資料夾:生成的所有PlatformView

習慣上會在lib資料夾下再加一個dart資料夾,放置對各平臺進行抽象的程式碼,並且最後對外export的時候,只export這個資料夾下的檔案。

lib資料夾結構概覽: . ├── 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

原生端結構

原生端生成的檔案分成兩種。

第一種是PlatformViewFactory類,負責PlatformView的建立,Fluttify會掃描到SDK內所有的View類併為其生成PlatformViewFactory類。第二種是主Plugin類,負責所有的MethodChannel的呼叫處理。

示例的android端的資料夾結構,ios端類似:

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

語言元素的對映

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

package com.test;
class A {}
複製程式碼

轉換為dart:

class com_test_A {}
複製程式碼

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

@interface TestClassA
@end
複製程式碼

轉換為:

class TestClassA {}
複製程式碼

介面

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

萬幸的是dart支援mixinmixin正好能夠處理objc的protocol特性。

示例 java:

package com.test;

interface InterfaceA {}
class ClassA implements InterfaceA {}
複製程式碼

轉換為dart:

class com_test_ClassA extends java_lang_Object with com_test_Interface {}
複製程式碼

objc:

@protocol TestInterfaceA
@end

@interface TestClassA
@end
複製程式碼

轉換為dart:

class TestClassA extends NSObject with TestInterfaceA {}
複製程式碼

方法

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

package com.test;
class TestClassA {
  public String testMethod(int arg) { /* 方法內容 */ }
}
複製程式碼

轉換為dart:

class com_test_TestClassA {
  String testMethod(int arg) { /* 呼叫原生程式碼 */ }
}
複製程式碼

objc:

@interface TestClassA
- (NSString*) testMethod: (NSInteger) arg;
@end
複製程式碼

轉換為dart:

class TestClassA {
  String testMethod(int arg) { /* 呼叫原生程式碼 */}
}
複製程式碼

函式

java沒有頂層函式,所以沒有需要處理的。

objc的函式實際上就是c函式,而dart也支援頂層函式,且與objc的函式語義上沒有太大的出入。

常量

目前支援轉換java的類常量到dart的類常量。

回撥

回撥分為lambda和delegate,不過在Fluttify的生成程式碼中的角色差不多。

回撥的實現主要通過雙向的MethodChannel呼叫來實現,比如說java端有一個方法:

void setCallback(Callback callback) { /* 程式碼 */ }
複製程式碼

生成的dart程式碼會是這樣的:

Future<void> setCallback(Callback callback) async {
  await MethodChannel('some channel').invokeMethod('some method');
  
  // 這裡會接收到native端的呼叫
  MethodChannel('some channel callback').setMethodHandler((methodResult) {
    // 處理原生的回撥
    callback.onXXX();
  });
}
複製程式碼

記憶體管理

dart端

Dart端的所有SDK類都會間接繼承foundation_fluttify中定義的Ref類,這個類代表是一個引用類,內部含有一個refId欄位,儲存的是原生端對應物件的id。

目前這個id的實現使用的是物件的hashCode。android端所有的物件都會有hashCode()方法,而ios端只有繼承NSObject的類才有hash欄位,如果碰到有處理結構體的需要,則用NSValue包裝結構體後再呼叫其hash欄位。

當呼叫SDK類的方法時,會把refId傳遞給原生,然後原生從全域性HEAP中獲取到目標物件,然後再在目標物件上進行呼叫。

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

原生端

foundation_fluttify的原生端提供了一個HEAP全域性集合,用來存放外掛呼叫過程中產生的原生物件。當dart端開始一個方法呼叫時,原生端便會先從HEAP中獲取到目標物件,再呼叫對應方法。

如果需要把釋放一個物件需要把它從HEAP中刪除,不然HEAP會一直強引用物件導致一直佔用記憶體。從HEAP中刪除後,後續的記憶體管理就交給系統來處理了。

結語

本文對Fluttify輸出的外掛工程的結構作了大致的介紹。這些其實也包含了很多我在實現Fluttify過程中遇到的困難,包括java/objc/dart這些語言在語法上的統一,如何實現回撥等等,還有很多很多細節的問題,更有甚者還要給SDK作者的一些騷操作騷寫法擦屁股。

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

相關文章