原生(iOS)與Flutter混合開發步驟

建古發表於2021-08-20

一、前言:目前混合開發屬於主流,因為多數都在原來的專案上整合Flutter模組,除非新的專案用純Flutter,那是挺爽的。混合開發多數會有點坑吧。現在記錄下混合開發的坑

二、整合(以iOS為例),使用Pods方式 1、首先建立一個Flutter模組(非Flutter專案,是模組) 在這裡插入圖片描述 2、建立後之後,把原生專案和Flutter模組放在一個資料夾下,同一層級。 在這裡插入圖片描述 3、原生專案開啟Podfile,加入Flutter,如下

// my_flutter 是建立Flutter的模組名稱
flutter_application_path = '../my_flutter'
load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')

platform :ios, '9.0' 
target 'NativeIOS' do
  use_frameworks!
  /// 這邊引入
  install_all_flutter_pods(flutter_application_path)
  /// 其他
  ...
end

複製程式碼

4、在原生目錄下,pod install,在pod install之前,先在Flutter模組下執行 flutter pub get 4.1、 Flutter模組下 執行 flutter pub get 4.2、 在原生專案下 執行 pod install 如果以上不報錯,混合開發模式到這裡就整合完了。,可以好好的玩耍了。不過好像想的有點多...

5、Flutter模組那邊怎麼寫還是怎麼寫,Flutter模組也是可以跑起來的。

6、在原生專案中註冊Flutter引擎, 就是flutterEngine。 在啟動的時候就註冊,這樣後續就不會出現卡頓的樣式,有點兒難受。

/// 在AppDelegate入口初始化即可
#pragma mark - initFlutterEngine
- (void)initFlutterEngine {
    self.flutterEngine = [[FlutterEngine alloc] initWithName:@"WMFlutterEngine"];
    [self.flutterEngine run];
    [GeneratedPluginRegistrant registerWithRegistry:self.flutterEngine];
}

/// 修改如下改成 FlutterEngine
@interface AppDelegate : FlutterEngine 
複製程式碼

7、在原先專案需要跳轉的地方寫業務即可了。

/// 某個原生控制器
// testPushFlutterPage 跳轉的地方 ,其中self.flutterVC 是我單獨的一個類 見8 
/// 跳轉到Flutter頁面。
- (void)testPushFlutterPage {
    self.flutterVC.fd_prefersNavigationBarHidden = YES;
    [self showViewController:self.flutterVC sender:nil];
}

- (void)initMethodChannel {
    self.flutterVC = [[WMFlutterViewController alloc] init];
    [self.flutterVC initMethodChannel];
}

- (void)viewDidLoad {
    [super viewDidLoad];
    [self initMethodChannel];
}

複製程式碼

8、flutterVC類

#pragma mark - Initial Methods // 初始化的方法
- (instancetype)init
{
    self = [super init];
    if (self) {
       self = [[WMFlutterViewController alloc] initWithEngine:[self getFlutterEngine] nibName:nil bundle:nil];
    }
    return self;
}

//
- (void)initMethodChannel {
    FlutterMethodChannel *methodChannel = [FlutterMethodChannel methodChannelWithName:@"PatientSearchChannel" binaryMessenger:self.binaryMessenger];
    self.methodChannel = methodChannel;
    //通過block回撥監聽通道中來自flutter的訊息體 這裡做一個dismiss方法,由於iOS中將flutter頁面push出來,次數實現dismiss方法,給flutter傳送dismss訊息,就知道是讓iOS將當前頁面關閉的動作,iOS收到後,執行關閉操作
    __weak typeof(self) weakself = self;
    [methodChannel setMethodCallHandler:^(FlutterMethodCall * _Nonnull call, FlutterResult  _Nonnull result) {
        __strong typeof(weakself) strongself = weakself;
        //dissmiss當前頁面
        if([call.method isEqualToString:@"dismiss"]){
//            [strongself.flutterVC dismissViewControllerAnimated:YES completion:nil];
            [strongself.navigationController popViewControllerAnimated:YES];
        }
        if (result) {
            result(@"回到的地方");
        }
        
        //dissmiss當前頁面
        if([call.method isEqualToString:@"pushNative"]){
            WMLog(@"pushNative 進來了 資料是 %@", call.arguments);
            WMNoticeSetViewController *VC = [[WMNoticeSetViewController alloc] init];
            WMLog(@"%@", self .navigationController);
            [self.navigationController pushViewController:VC animated:YES];
        }
        
        //dissmiss當前頁面
        if([call.method isEqualToString:@"pushNativeTwo"]){
            WMLog(@"pushNativeTwo 進來了 資料是 %@", call.arguments);
            NSDictionary *dataDic = call.arguments;
            WMTestViewController *VC = [[WMTestViewController alloc] init];
            VC.name = dataDic[@"name"];
            WMLog(@"%@", self.navigationController);
            [self.navigationController pushViewController:VC animated:YES];
        }
    }];
}

/// 獲取引擎
- (FlutterEngine *)getFlutterEngine {
    FlutterEngine *flutterEngine = ((AppDelegate *)UIApplication.sharedApplication.delegate).flutterEngine;
    return flutterEngine;
}

複製程式碼

以後原生部分就寫完了。

9、Flutter部分,我寫了一個工具類,來和原生互動的類

class NativeChannelUtils {
  static const MethodChannel _patientSearchChannel =
      MethodChannel('PatientSearchChannel');

  /// 患者搜尋關閉
  static patientSearchDismiss({WMValueCallBack valueCallBack}) {
    _patientSearchChannel.invokeMethod("dismiss").then((value) {
      Utils.logs('回來的資料 = ${value}');
      if (valueCallBack != null) {
        valueCallBack(value);
      }
    });
  }

  /// 患者搜尋到原生介面
  static patientSearchPushToNative({WMValueCallBack valueCallBack}) {
    _patientSearchChannel.invokeMethod(
        "pushNative", {"data": "我是Flutter資料", 'code': '1000'}).then((value) {
      Utils.logs('回來的資料 = ${value}');
      if (valueCallBack != null) {
        valueCallBack(value);
      }
    });
  }

  /// 患者搜尋到原生介面
  static patientSearchPushToNativeTwo(
      {Map arguments, WMValueCallBack valueCallBack}) {
    _patientSearchChannel
        .invokeMethod("pushNativeTwo", arguments)
        .then((value) {
      Utils.logs('回來的資料 = ${value}');
      if (valueCallBack != null) {
        valueCallBack(value);
      }
    });
  }
}
複製程式碼

在Flutter需要的地方呼叫。

NativeChannelUtils.patientSearchDismiss(valueCallBack: (value) {
            Utils.logs("哈哈哈哈");
          });
複製程式碼

` 注意點:原生和Flutter互動有三種方式,目前使用的MethodChannel方式

MethodChannel:Flutter 與 Native 端相互呼叫,呼叫後可以返回結果,可以 Native 端主動呼叫,也可以Flutter主動呼叫,屬於雙向通訊。此方式為最常用的方式, Native 端呼叫需要在主執行緒中執行。
BasicMessageChannel:用於使用指定的編解碼器對訊息進行編碼和解碼,屬於雙向通訊,可以 Native 端主動呼叫,也可以Flutter主動呼叫。
EventChannel:用於資料流(event streams)的通訊, Native 端主動傳送資料給
複製程式碼

這個是Flutter原生方式混合開發。由於是在專案中整合的,後期看下 提出demo.

這邊我用閒魚的flutter_boost 混合開發方案 寫了一個demo。 demo地址:github.com/yj229201093…

但總覺得和自己想要的差一點點東西。所以目前採用原生方式混合開發。

注意:如果使用原生方式,儘量不要開多個引擎,不然開銷很大,就是:下面就開啟了兩個引擎。Flutter引擎是相互獨立的。互不干擾的。 原生A->FlutterB, FlutterB->FlutterC, FlutterC->原生D, 原生D->FlutterE

建議是一個閉環開發,一個專案開啟一個Flutter引擎。就是原生進入Flutter模組,然後Flutter模組結束後就回到原生,這樣算一個閉環。

如果反覆跳轉可以使用閒魚的flutter_boost方案。

後續可能採用flutter_boost方案。目前導航欄方便還有點沒有理清。

相關文章