構建屬於本身的Flutter混合開發框架

所謂混合開發,指的是 App 的總體架構以原生技術棧爲基礎,將 Flutter 運行環境嵌入到原生 App 工程中,而後由原生開發人員爲 Flutter 運行提供宿主容器及基礎能力支撐,而 Flutter 開發人員則負責應用層業務及 App 內大部分渲染工做。前端

在這種開發模式下,好處十分明顯。對於工程師而言,跨平臺的 Flutter 框架減小了對底層環境的依賴,使用完整的技術棧和工具鏈隔離了各個終端系統的差別,不管是 Android、iOS 甚至是前端工程師,均可以使用統一而標準化的能力進行業務開發,從而擴充了技能棧。而對於企業而言,這種方式不只具有了原生 App 良好的用戶體驗,以及豐富的底層能力,還同時擁有了跨平臺技術開發低成本和多端體驗一致性的優點,直接節省研發資源。android

那麼,在原生工程中引入 Flutter 混合開發能力,咱們應該如何設計工程架構,原生開發與 Flutter 開發的工做模式又是怎樣的呢?ios

混合開發架構

與純 Flutter 工程可以以自治的方式去分拆軟件功能、管理工程依賴不一樣,Flutter 混合工程的功能分治須要原生工程與 Flutter 工程一塊兒配合完成,即:在 Flutter 模塊的視角看來,一部分與渲染相關的基礎能力徹底由 Flutter 代碼實現,而另外一部分涉及操做系統底層、業務通用能力部分,以及總體應用架構支撐,則須要藉助於原生工程給予支持。git

咱們能夠經過四象限分析法,把純 Flutter 應用按照業務和 UI 分解成 4 類。一樣,混合工程的功能單元也能夠按照這個分治邏輯分爲 4 個維度,即不具有業務屬性的原生基礎功能、不具有業務屬性的原生 UI 控件、不具有 UI 屬性的原生基礎業務功能和帶 UI 屬性的獨立業務模塊,以下圖所示。程序員

在這裏插入圖片描述
從圖中能夠看到,對於前 3 個維度(即原生 UI 控件、原生基礎功能、原生基礎業務功能)的定義,純 Flutter 工程與混合工程並沒有區別,只不過實現的方式由 Flutter 變成了原生;對於第四個維度(即獨立業務模塊)的功能歸屬,考慮到業務模塊的最小單元是頁面,而 Flutter 的最終呈現形式也是獨立的頁面,所以咱們把 Flutter 模塊也歸爲此類,咱們的工程能夠像依賴原生業務模塊同樣直接依賴它,爲用戶提供獨立的業務功能。當咱們把這些組件及其依賴按照從上到下的方式進行劃分,而後再總體看,就是一個完整的混合開發架構了,整個架構下圖所示。

在這裏插入圖片描述

能夠看到,原生工程和 Flutter 工程的邊界定義清晰,雙方均可以保持原有的分層管理依賴的開發模式不變。須要注意的是,做爲一個內嵌在原生工程的插件,Flutter 模塊的運行環境是由原生工程提供支持的,這也就意味着在渲染交互能力以外的部分基礎功能(好比網絡、存儲),以及和原生業務共享的業務通用能力(好比支付、帳號)須要原生工程配合完成,即原生工程以分層的形式提供上層調用接口,Flutter 模塊以插件的形式直接訪問原生代碼宿主對應功能實現。github

所以,不只不一樣歸屬定義的原生組件以前存在着分層依賴的關係,Flutter 模塊與原生組件以前也隱含着分層依賴的關係。好比,Flutter 模塊中處於基礎業務模塊的帳號插件,依賴位於原生基礎業務模塊中的帳號功能;Flutter 模塊中處於基礎業務模塊的網絡插件,依賴位於原生基礎功能的網絡引擎庫。編程

在混合工程架構中,像原生工程依賴 Flutter 模塊、Flutter 模塊又依賴原生工程這樣跨技術棧的依賴管理行爲,其實是經過將雙方抽象爲彼此對應技術棧的依賴,從而實現分層管理的:即將原生對 Flutter 的依賴抽象爲依賴 Flutter 模塊所封裝的原生組件,而 Flutter 對原生的依賴則抽象爲依賴插件所封裝的原生行爲。json

Flutter 混合開發流程

在常規的軟件開發流程中,工程師的職責涉及從需求到上線的整個生命週期,包含需求階段 -> 方案階段 -> 開發階段 -> 發佈階段 -> 線上運維階段,這其實就是一種抽象的工做流程。緩存

其中,和工程化關聯最爲緊密的是開發階段和發佈階段。咱們能夠將工做流中和工程開發相關的部分抽離定義爲開發工做流,根據生命週期中關鍵節點和高頻節點,能夠將整個工做流劃分爲以下七個階段,即初始化 -> 開發 / 調試 -> 構建 -> 測試 -> 發佈 -> 集成 -> 原生工具鏈。下圖演示了Flutter和原生開發的工做流。安全

在這裏插入圖片描述
其中,前 6 個階段是 Flutter 的標準工做流,最後一個階段是原生開發的標準工做流。能夠看到,在混合開發工做模式中,Flutter 的開發模式與原生開發模式之間有着清晰的分工邊界:Flutter 模塊是原生工程的上游,其最終產物是原生工程的依賴對象。從原生工程視角看,其開發模式與普通原生應用並沒有區別。

對於 Flutter 標準工做流的 6 個階段而言,每一個階段都會涉及業務或產品特性提出的特異性要求,技術方案的選型,各階段工做成本可用性、可靠性的衡量,以及監控相關基礎服務的接入和配置等。每件事兒都是一個固定的步驟,而當開發規模隨着文檔、代碼、需求增長時,咱們會發現重複的步驟愈來愈多。此時,若是咱們把這些步驟像抽象代碼同樣,抽象出一些相同操做,就能夠大大提高開發效率。

優秀的程序員會發掘工做中的問題,從中探索提升生產力的辦法,而轉變思惟模式就是一個不錯的起點。以持續交付的指導思想來看待這些問題,咱們但願總體方案可以以可重複、可配置化的形式,來保障整個工做流的開發體驗、效率、穩定性和可靠性,而這些都離不開 Flutter 對命令行工具支持。

好比,對於測試階段的 Dart 代碼分析,咱們可使用 flutter analyze 命令對代碼中可能存在的語法或語義問題進行檢查;又好比,在發佈期的 package 發佈環節,咱們可使用 flutter packages pub publish --dry-run 命令對待發布的包進行發佈前檢查,確認無誤後使用去掉 dry-run 參數的 publish 命令將包提交至 Pub 站點。

這些基本命令對各個開發節點的輸入、輸出以及執行過程進行了抽象,熟練掌握它們及對應的擴展參數用法,咱們不只能夠在本地開發時打造一個易用便捷的工程開發環境,還能夠將這些命令部署到雲端,實現工程構建及部署的自動化。在Flutter 標準工做流中,經常使用的命令以下所示。

在這裏插入圖片描述

混合開發的基本設計原則

在混合開發中,咱們須要重點關注的是項目的基本設計原則,即肯定分工邊界。下面從工程架構維度和工做模式維度來進行拆分。

在工程架構維度,因爲 Flutter 模塊做爲原生工程的一個業務依賴,其運行環境是由原生工程提供的,所以咱們須要將它們各自抽象爲對應技術棧的依賴管理方式,以分層依賴的方式肯定兩者的邊界。

而在工做模式維度,考慮到 Flutter 模塊開發是原生開發的上游,所以咱們只須要從其構建產物的過程入手,抽象出開發過程當中的關鍵節點和高頻節點,以命令行的形式進行統一管理。構建產物是 Flutter 模塊的輸出,同時也是原生工程的輸入,一旦產物完成構建,咱們就能夠接入原生開發的工做流了。

在 Flutter 混合框架中,Flutter 模塊與原生工程是相互依存、互利雙贏的關係。

  • Flutter 跨平臺開發效率高,渲染性能和多端體驗一致性好,所以在分工上主要專一於實現應用層的獨立業務(頁面)的渲染閉環;
  • 原生開發穩定性高,精細化控制力強,底層基礎能力豐富,所以在分工上主要專一於提供總體應用架構,爲 Flutter 模塊提供穩定的運行環境及對應的基礎能力支持。

那麼,在原生工程中爲 Flutter 模塊提供基礎能力支撐的過程當中,面對跨技術棧的依賴管理,咱們該遵循何種原則呢?對於 Flutter 模塊及其依賴的原生插件們,咱們又該如何以標準的原生工程依賴形式進行組件封裝呢?下面重點看一下原生工程是如何進行插件管理的。

能夠看到,在原生 App 工程中引入 Flutter 運行環境,由原生開發主作應用架構和基礎能力賦能、Flutter 開發主作應用層業務的混合開發協做方式,可以綜合原生 App 與 Flutter 框架雙方的特色和優點,不只能夠直接節省研發資源,也符合目前行業人才能力模型的發展趨勢。

原生插件管理

在Flutter 應用中,Dart 代碼提供原生能力支持主要有兩種方式,即在原生工程中的 Flutter 應用入口註冊原生代碼宿主回調的輕量級方案,以及使用插件工程進行獨立拆分封裝的工程化解耦方案。

不過,不管使用哪一種方式,Flutter 應用工程提供的標準解決方案,都可以在集成構建時自動管理原生代碼宿主及其相應的原生依賴,而後只須要在應用層使用 pubspec.yaml 文件去管理 Dart 的依賴便可。

但對於混合工程而言,依賴關係的管理則會複雜一些。這是由於與 Flutter 應用工程有着對原生組件簡單清晰的單向依賴關係不一樣,混合工程對原生組件的依賴關係是多向的,即Flutter 模塊工程會依賴原生組件,而原生工程的組件之間也會互相依賴。

若是繼續使用Flutter 的工具鏈管理原生組件的依賴關係,那麼整個工程就會陷入不穩定的狀態之中。所以,對於混合工程的原生依賴,Flutter 模塊並不須要介入,徹底交由原生工程進行統一管理纔是正確的作法。而 Flutter 模塊工程對原生工程的依賴,體如今依賴原生代碼宿主提供的底層基礎能力的原生插件上。

下面咱們就以網絡通訊這一基礎能力爲例,展開說明原生工程與 Flutter 模塊工程之間應該如何管理依賴關係。

網絡插件依賴管理實踐

衆所周知,在 Flutter開發中,咱們可使用 HttpClient、http 與 dio 這三種通訊方式來實現與服務端的數據交換。不過,在混合工程中,考慮到原生組件也須要使用網絡通訊能力,因此一般是由原生工程來提供網絡通訊功能,而後封裝後提供給Flutter使用。這樣,不只能夠在工程架構層面實現更合理的功能分治,還能夠統一整個 App 內數據交換的行爲。好比,在網絡引擎中爲接口請求增長通用參數,或者是集中攔截錯誤等。

在原生網絡通訊方面,目前市面上有不少優秀的第三方開源 SDK,好比 iOS 的 AFNetworking 和 Alamofire、Android 的 OkHttp 和 Retrofit 等。考慮到 AFNetworking 和 OkHttp 在各自平臺的社區活躍度相對最高,所以下面就以它倆爲例演示混合工程的原生插件管理方法。

網絡插件封裝

要想搞清楚如何管理原生插件,咱們須要先使用方法通道來創建 Dart 層與原生代碼宿主之間的聯繫。

1,Dart代碼封裝

對於插件工程的 Dart 層代碼而言,因爲它僅僅是原生工程的代碼宿主代理,因此這一層的接口設計比較簡單,只須要提供一個能夠接收請求 URL 和參數,並返回接口響應數據的方法便可 ,以下所示。

class FlutterPluginNetwork {
  ...
  static Future<String> doRequest(url,params)  async {
    //使用方法通道調用原生接口doRequest,傳入URL和param兩個參數
    final String result = await _channel.invokeMethod('doRequest', {
      "url": url,
      "param": params,
    });
    return result;
  }
}
複製代碼

關於Flutter如何與原生進行交互,能夠查看我以前的文章:混合開發簡介

完成Dart 層接口封裝後,接下來再看一下 Android 和 iOS 代碼宿主是如何響應 Dart 層的接口調用的。

2,原生端封裝

前面說過,原生代碼的基礎通訊能力是基於 AFNetworking(iOS)和 OkHttp(Android)作的封裝,因此爲了在原生代碼中使用它們,咱們首先須要分別在 flutter_plugin_network.podspec 和 build.gradle 文件中添加插件的依賴。對於iOS工程來講,在 flutter_plugin_network.podspec 文件中,聲明工程對 AFNetworking 的依賴。

Pod::Spec.new do |s|
  ...
  s.dependency 'AFNetworking'
end
複製代碼

對於Android原生工程來講, 在 build.gradle 文件中添加對 OkHttp 的依賴,以下所示。

dependencies {
    implementation "com.squareup.okhttp3:okhttp:4.2.0"
}
複製代碼

而後,咱們須要在原生接口 FlutterPluginNetworkPlugin 類中,完成例行的初始化插件實例、綁定方法通道工做。最後,咱們還須要在方法通道中取出對應的 URL 和 請求 參數,爲 doRequest 方法分別提供 AFNetworking 和 OkHttp 的實現版本。

對於 iOS 的調用而言,因爲 AFNetworking 的網絡調用對象是 AFHTTPSessionManager 類,因此咱們須要對這個類進行實例化,並定義其接口返回的序列化方式(本例中爲字符串),而後剩下的工做就是用它去發起網絡請求,使用方法通道通知 Dart 層執行結果。

@implementation FlutterPluginNetworkPlugin
...
//方法通道回調
- (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
    //響應doRequest方法調用
    if ([@"doRequest" isEqualToString:call.method]) {
        //取出query參數和URL
        NSDictionary *arguments = call.arguments[@"param"];
        NSString *url = call.arguments[@"url"];
        [self doRequest:url withParams:arguments andResult:result];
    } else {
        //其餘方法未實現
        result(FlutterMethodNotImplemented);
    }
}
//處理網絡調用
- (void)doRequest:(NSString *)url withParams:(NSDictionary *)params andResult:(FlutterResult)result {
    //初始化網絡調用實例
    AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
    //定義數據序列化方式爲字符串
    manager.responseSerializer = [AFHTTPResponseSerializer serializer];
    NSMutableDictionary *newParams = [params mutableCopy];
    //增長自定義參數
    newParams[@"ppp"] = @"yyyy";
    //發起網絡調用
    [manager GET:url parameters:params progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
        //取出響應數據,響應Dart調用
        NSString *string = [[NSString alloc] initWithData:responseObject encoding:NSUTF8StringEncoding];
        result(string);
    } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
        //通知Dart調用失敗
        result([FlutterError errorWithCode:@"Error" message:error.localizedDescription details:nil]);
    }];
}
@end
複製代碼

Android 的調用相似,OkHttp的網絡調用對象是 OkHttpClient 類,因此咱們一樣須要對這個類進行實例化。OkHttp的默認序列化方式就是字符串,因此咱們什麼都不用作,只須要 URL 參數加工成 OkHttp 指望的格式,而後就是用它去發起網絡請求,使用方法通道通知 Dart 層執行結果便可。

public class FlutterPluginNetworkPlugin implements MethodCallHandler {
  ...
  @Override
  //方法通道回調
  public void onMethodCall(MethodCall call, Result result) {
    //響應doRequest方法調用
    if (call.method.equals("doRequest")) {
      //取出query參數和URL
      HashMap param = call.argument("param");
      String url = call.argument("url");
      doRequest(url,param,result);
    } else {
      //其餘方法未實現
      result.notImplemented();
    }
  }
  //處理網絡調用
  void doRequest(String url, HashMap<String, String> param, final Result result) {
    //初始化網絡調用實例
    OkHttpClient client = new OkHttpClient();
    //加工URL及query參數
    HttpUrl.Builder urlBuilder = HttpUrl.parse(url).newBuilder();
    for (String key : param.keySet()) {
      String value = param.get(key);
      urlBuilder.addQueryParameter(key,value);
    }
    //加入自定義通用參數
    urlBuilder.addQueryParameter("ppp", "yyyy");
    String requestUrl = urlBuilder.build().toString();

    //發起網絡調用
    final Request request = new Request.Builder().url(requestUrl).build();
    client.newCall(request).enqueue(new Callback() {
      @Override
      public void onFailure(Call call, final IOException e) {
        //切換至主線程,通知Dart調用失敗
        registrar.activity().runOnUiThread(new Runnable() {
          @Override
          public void run() {
            result.error("Error", e.toString(), null);
          }
        });
      }
      
      @Override
      public void onResponse(Call call, final Response response) throws IOException {
        //取出響應數據
        final String content = response.body().string();
        //切換至主線程,響應Dart調用
        registrar.activity().runOnUiThread(new Runnable() {
            @Override
            public void run() {
              result.success(content);
            }
        });
      }
    });
  }
}
複製代碼

須要注意的是,因爲方法通道是非線程安全的,因此原生代碼與 Flutter 之間全部的接口調用必須發生在主線程。而 OktHtp 在處理網絡請求時,因爲涉及非主線程切換,因此須要調用 runOnUiThread 方法以確保回調過程是在 UI 線程中執行的,不然應用可能會出現奇怪的 Bug,甚至是 Crash。

有些同窗可能會有疑問,爲何 doRequest 的 Android 實現須要手動切回 UI 線程,而 iOS 實現則不須要呢?這實際上是由於 doRequest 的 iOS 實現背後依賴的 AFNetworking,已經在數據回調接口時爲咱們主動切換了 UI 線程,因此咱們天然不須要重複再作一次了。

在完成了原生接口封裝以後,Flutter 工程所需的網絡通訊功能的接口實現,就所有搞定了。

Flutter 模塊工程依賴管理

經過上面這些步驟,咱們以插件的形式提供了原生網絡功能的封裝。接下來,咱們就須要在 Flutter 模塊工程中使用這個插件,並提供對應的構建產物封裝,提供給原生工程使用了。

  • 第一,如何使用 FlutterPluginNetworkPlugin 插件,也就是模塊工程功能如何實現;
  • 第二,模塊工程的 iOS 構建產物應該如何封裝,也就是原生 iOS 工程如何管理 Flutter 模塊工程的依賴;
  • 第三,模塊工程的 Android 構建產物應該如何封裝,也就是原生 Android 工程如何管理 Flutter 模塊工程的依賴。

1,模塊工程功能實現

爲了使用 FlutterPluginNetworkPlugin 插件實現與服務端的數據交換能力,咱們首先須要在 pubspec.yaml 文件中,將工程對它的依賴顯示地聲明出來,以下所示。

flutter_plugin_network:
    git:
      url: https://github.com/cyndibaby905/flutter_plugin_network.git
複製代碼

而後,咱們還得在 main.dart 文件中爲它提供一個觸發入口。在下面的示例代碼中,咱們在界面上顯示一個 RaisedButton 按鈕,在其點擊回調函數時使用 FlutterPluginNetwork 插件發起了一次網絡接口調用,並把網絡返回的數據打印到了控制檯上,代碼以下。

RaisedButton(
  child: Text("doRequest"),
  onPressed:()=>FlutterPluginNetwork.doRequest("https://jsonplaceholder.typicode.com/posts", {'userId':'2'}).then((s)=>print('Result:$s')),
)
複製代碼

運行這段代碼,點擊 doRequest 按鈕時會觀察控制檯輸出,證實 Flutter 模塊的功能表現是徹底符合預期的。

在這裏插入圖片描述

構建產物封裝

咱們都知道,模塊工程的 Android 構建產物是 aar,iOS 構建產物是 Framework。Flutter插件依賴的模塊工程構建產物的兩種封裝方案,即手動封裝方案與自動化封裝方案。這兩種封裝方案,最終都會輸出一樣的組織形式(Android 是 aar,iOS 則是帶 podspec 的 Framework 封裝組件)。

若是咱們的模塊工程存在插件依賴,又該如何進行封裝,它的封裝過程是否有區別呢?簡單的說,對於模塊工程自己而言,這個過程沒有區別;但對於模塊工程的插件依賴來講,咱們須要主動告訴原生工程,哪些依賴是須要它去管理的。

因爲 Flutter 模塊工程把全部原生的依賴都交給了原生工程去管理,所以其構建產物並不會攜帶任何原生插件的封裝實現,因此咱們須要遍歷模塊工程所使用的原生依賴組件們,爲它們逐一輩子成插件代碼對應的原生組件封裝。

在純Flutter 工程中,管理第三方依賴庫使用的是.packages 文件存儲,它使用的是依賴包名與系統緩存中的包文件路徑。相似的,插件依賴也可使用相似的文件進行統一管理,即.flutter-plugins。咱們能夠經過這個文件,找到對應的插件名字(本例中即爲 flutter_plugin_network)及緩存路徑,以下所示。

flutter_plugin_network=/Users/hangchen/Documents/flutter/.pub-cache/git/flutter_plugin_network-9b4472aa46cf20c318b088573a30bc32c6961777/
複製代碼

同時,插件緩存自己也能夠被視爲一個 Flutter 模塊工程,因此咱們能夠採用與模塊工程相似的辦法,爲它生成對應的原生組件封裝。

iOS 構建產物封裝

對於 iOS 而言,這個過程相對簡單些,因此咱們先來看看模塊工程的 iOS 構建產物封裝過程。

首先,在插件工程的 iOS 目錄下,模塊工程提供了帶 podspec 文件的源碼組件,podspec 文件提供了組件的聲明(及其依賴),所以咱們能夠把這個目錄下的文件拷貝出來,連同 Flutter 模塊組件一塊兒放到原生工程中的專用目錄,並寫到 Podfile 文件中。

#Podfile
target 'iOSDemo' do
  pod 'Flutter', :path => 'Flutter'
  pod 'flutter_plugin_network', :path => 'flutter_plugin_network'
end
複製代碼

原生工程會識別出組件自己及其依賴,並按照聲明的依賴關係依次遍歷,自動安裝。而後,咱們就能夠像使用不帶插件依賴的模塊工程同樣,把它引入到原生工程中,爲其設置入口,並在 FlutterViewController 中展現 Flutter 模塊的頁面了。

不過須要注意的是,因爲 FlutterViewController 並不感知這個過程,所以不會主動初始化項目中的插件,因此咱們還須要在入口處手動將工程裏全部的插件依次聲明出來,以下所示。

//AppDelegate.m:
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
    //初始化Flutter入口
    FlutterViewController *vc = [[FlutterViewController alloc]init];
    //初始化插件
    [FlutterPluginNetworkPlugin registerWithRegistrar:[vc registrarForPlugin:@"FlutterPluginNetworkPlugin"]];
    //設置路由標識符
    [vc setInitialRoute:@"defaultRoute"]; 
    self.window.rootViewController = vc;
    [self.window makeKeyAndVisible];
    return YES;
}
複製代碼

而後,使用Xcode 運行這段代碼,點擊 doRequest 按鈕,若是能夠看到接口返回的數據信息可以被正常打印,證實咱們已經能夠在原生 iOS 工程中順利的使用 Flutter 模塊了。

在這裏插入圖片描述

Android 構建產物封裝

與 iOS 的插件工程組件在 ios 目錄相似,Android 的插件工程組件在 android 目錄下。對於 iOS 的插件工程,咱們能夠直接將源碼組件提供給原生工程,但對於 Andriod 的插件工程來講,咱們只能將 aar 組件提供給原生工程,因此咱們不只須要像 iOS 操做步驟那樣進入插件的組件目錄,還須要藉助構建命令,爲插件工程生成 aar。使用下面的命令便可生成插件工程的aar包。

cd android
./gradlew flutter_plugin_network:assRel
複製代碼

命令執行完成以後,aar 就生成好了,aar 包位於 android/build/outputs/aar 目錄下,咱們打開插件緩存對應的路徑,提取出對應的 aar便可。咱們把生成的插件 aar,連同 Flutter 模塊 的aar 一塊兒放到原生工程的 libs 目錄下,最後在 build.gradle 文件裏引入插件工程,以下所示。

//build.gradle
dependencies {
    ...
    implementation(name: 'flutter-debug', ext: 'aar')
    implementation(name: 'flutter_plugin_network-debug', ext: 'aar')
    implementation "com.squareup.okhttp3:okhttp:4.2.0"
    ...
}
複製代碼

而後,咱們就能夠在原生工程中爲其設置入口,在 FlutterView 中展現 Flutter 頁面,接下來就可使用 Flutter 模塊帶來的高效開發和高性能渲染能力了。

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        View FlutterView = Flutter.createView(this, getLifecycle(), "defaultRoute"); 
        setContentView(FlutterView);
    }
}
複製代碼

須要注意的是,與 iOS 插件工程的 podspec 可以攜帶組件依賴不一樣,Android 插件工程的封裝產物 aar 自己不攜帶任何配置信息。因此,若是插件工程自己存在原生依賴(如 flutter_plugin_network 依賴 OkHttp ),咱們是沒法經過 aar 去告訴原生工程其所需的原生依賴的。對於這種狀況,咱們只須要在原生工程中的 build.gradle 文件裏手動地將插件工程的依賴的插件(即 OkHttp)顯示地聲明出來便可,以下所示。

//build.gradle
dependencies {
    ...
    implementation(name: 'flutter-debug', ext: 'aar')
    implementation(name: 'flutter_plugin_network-debug', ext: 'aar')
    implementation "com.squareup.okhttp3:okhttp:4.2.0"
    ...
}
複製代碼

至此,混合模塊工程及其插件依賴封裝成原生組件的所有工做就完成了,接下來原生工程能夠像使用一個普通的原生組件同樣去使用 Flutter 模塊組件的功能了。在 Android Studio 中運行這段代碼,並點擊 doRequest 按鈕,能夠看到,咱們能夠在原生 Android 工程中正常使用 Flutter 封裝的頁面組件了。

在這裏插入圖片描述
固然,考慮到手動封裝模塊工程及其構建產物的過程,繁瑣且容易出錯,咱們能夠把這些步驟抽象成命令行腳本,並把它部署到 Travis 上。這樣在 Travis 檢測到代碼變動以後,就會自動將 Flutter 模塊的構建產物封裝成原生工程指望的組件格式了。

總結

衆所周知,Flutter 模塊工程的原生組件封裝形式是 aar(Android)和 Framework(Pod)。與純 Flutter 應用工程可以自動管理插件的原生依賴不一樣,混合工程的這部分工做在模塊工程中是徹底交給原生工程去管理的。所以,咱們須要查找記錄了插件名稱及緩存路徑映射關係的.flutter-plugins 文件,提取出每一個插件所對應的原生組件封裝,集成到原生工程中。

相比iOS插件管理來講,Android的插件管理比較繁瑣。對於有着插件依賴的 Android 組件封裝來講,因爲 aar 自己並不攜帶任何配置信息,所以其操做以手工爲主:咱們不只要執行構建命令依次生成插件對應的 aar,還須要將插件自身的原生依賴拷貝至原生工程。

爲了解決這一問題,業界出現了一種名爲fat-aar的打包手段,它可以將模塊工程自己,及其相關的插件依賴統一打包成一個大的 aar,從而省去了依賴遍歷和依賴聲明的過程,實現了更好的功能自治性。但這種解決方案存在一些較爲明顯的不足,如下是使用中存在的一些問題:

  • 依賴衝突問題:若是原生工程與插件工程都引用了一樣的原生依賴組件(OkHttp),則原生工程的組件引用其依賴時會產生合併衝突,所以在發佈時必須手動去掉原生工程的組件依賴。
  • 嵌套依賴問題:fat-aar 只會處理 embedded 關鍵字指向的這層一級依賴,而不會處理再下一層的依賴。所以,對於依賴關係複雜的插件支持,咱們仍須要手動處理依賴問題。
  • Gradle 版本限制問題:fat-aar 方案對 Gradle 插件版本有限制,且實現方式並非官方設計考慮的點,加之 Gradle API 變動較快,因此存在後續難以維護的問題。
  • 不更新。fat-aar 項目已經再也不維護了,最近一次更新仍是 2 年前,對Android的新版本存在較大的風險。

所以,fat-aar 並非管理插件工程依賴的好的解決方案,因此最好仍是得老老實實地去遍歷插件依賴,以持續交付的方式自動化生成 aar。

參考資料

1,Flutter 應用程序調試
2,Flutter For Web入門實戰
3,Flutter開發之路由與導航
4,Flutter 必備開源項目
5,Flutter混合開發
6,Flutter的Hot Reload是如何作到的
7,《Flutter in action》開源
8,Flutter開發之JSON解析
9,Flutter開發之基礎Widgets
10,Flutter開發之導航與路由管理
11,Flutter開發之網絡請求
12,Flutter基礎知識
13,Flutter開發之Dart語言基礎
14,Flutter入門與環境搭建
15,移動跨平臺方案對比:WEEX、React Native、Flutter和PWA
16,Flutter開發之異步編程

相關文章
相關標籤/搜索