從零開始向原生專案整合Flutter以及通訊

iAllenC發表於2021-07-05

Flutter最近比較火,很多大廠小廠都在搞,網上的教程也很多,但是很多教程其實是不全面的,也有很多其實都是互相copy的。尤其是關於原生整合Flutter並且實現相互通訊這一塊。筆者今天就來從零開始,與各位讀者,一起實現一個原生專案整合Flutter,並實現相互通訊。 #0 安裝Flutter環境 這個很簡單,教程很多,跟著官方文件來就行了,筆者就不多說了,浪費大家時間。 #1 新建Flutter Module 這裡筆者建議使用Android Studio。開啟AS,File->New->New Flutter Project,如圖: image.png 接下來,選擇Flutter Module,點選Next image.png 輸入專案名稱以及專案位置等資訊,然後點選Finish: image.png OK,很簡單,一個Flutter Module就建好了。 #2 整合進現有專案

iOS

在podfile裡頂層新增

flutter_application_path = '../path/to/your/flutter/module'
load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')
複製程式碼

然後在如下位置新增

target `Your target` do 
  ...
  install_all_flutter_pods(flutter_application_path)
end
複製程式碼

接下來進入到專案目錄中,pod install一下,就可以了。 這裡可以參照官方文件(選項 A)

注意:如果build報錯了,先開啟flutter專案,在對應iOS機型下面跑一遍專案,然後回到iOS,重新build就可以了

Android

開啟現有專案,點選File->New->New Module: image.png 選擇Import Flutter Module,在輸入框中選擇剛才新建的flutter module路徑: image.png 點選finish,就整合好了。 接下來,需要在manifest裡面配置一下:

        <activity
            android:name="io.flutter.embedding.android.FlutterActivity"
            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
            android:hardwareAccelerated="true"
            android:theme="@style/Theme.Flutter_native_android.NoActionBar"
            android:windowSoftInputMode="adjustResize" />
複製程式碼

#3 顯示Flutter介面

iOS

在主頁面,或者需要的地方,引入FlutterPluginRegistrant, 並新增如下程式碼:

    lazy var flutterEngine: FlutterEngine = {
        let flutterEngine = FlutterEngine(name: "Your Engin Name")
        flutterEngine.run()
        GeneratedPluginRegistrant.register(with: flutterEngine)
        if let registerer = flutterEngine.registrar(forPlugin: "FlutterNativePlugin") {
            FlutterNativePlugin.register(with: registerer)
        }
        return flutterEngine
    }()
複製程式碼

其中"FlutterNativePlugin"實際是後面會建立的用來進行通訊的類名,暫時先放著。 接下來建立FlutterViewController:

        let flutterVC = FlutterViewController(engine: flutterEngine, nibName: nil, bundle: nil)
        let splashScreenView = UIView()
        splashScreenView.backgroundColor = .white
        flutterVC.splashScreenView = splashScreenView
複製程式碼

這裡可以通過一個屬性來持有,也可以在需要的時候再建立,根據需求來定。至於splashScreenView實際是設定載入時的過渡頁面,預設是專案的launchscreen,也可以根據具體的需求來修改。 在需要進入Flutter頁面的地方,呼叫push或者present:

        navigationController?.pushViewController(flutterVC, animated: true)
複製程式碼

接下來,就可以進入Flutter頁面了。

Android

Android 也很簡單,在需要的地方(比如MainActivity的onCreate裡)建立Flutter相關例項物件

        flutterEngine = new FlutterEngine(this);
        flutterEngine.getDartExecutor().executeDartEntrypoint(
                DartExecutor.DartEntrypoint.createDefault()
        );
        FlutterEngineCache
                .getInstance()
                .put("my_engine_id", flutterEngine);
複製程式碼

在按鈕的點選事件中跳轉:

startActivity(
        FlutterActivity.withCachedEngine("my_engine_id").build(activity)
);
複製程式碼

build的引數傳入當前activity就可以了。 這裡採取的也是官方建議的使用快取的 FlutterEngine進行載入的方案

#4 相互通訊

##flutter 我們先實現flutter部分,這樣,後面iOS和Android寫好後就可以直接看效果了。 新建一個類,比如叫NativeCaller:

import 'package:flutter/services.dart';
import 'package:flutter_module/Networking/token_manager.dart';
class NativeCaller {
  static const MethodChannel _channel = const MethodChannel("your_channel_name");

  static init() {
    _channel.setMethodCallHandler((call) => handleMethodCall(call));
  }

  static Future<String> greeting(String content) async {
    return await _channel.invokeMethod("greeting", content);
  }

  static Future route(String url) async {
    return await _channel.invokeMethod("route", url);
  }

  static Future goBack() async {
    return await _channel.invokeMethod("goBack");
  }

  static Future handleMethodCall(MethodCall call) async {
    print("Handle method call:${call.method} with arguments:${call.arguments}");
    return Future.value(true);
  }
}

複製程式碼

這個類持有了一個MethodChannel型別的靜態變數,注意MethodChannel初始化方法的引數要和後面原生的保持一致。 其中的greeting,route,goBack三個方法用於呼叫原生對應方法,返回的future則會通過回撥的方式給到原生。 handleMethodCall用於接收原生的呼叫,需要注意這邊:

  static init() {
    _channel.setMethodCallHandler((call) => handleMethodCall(call));
  }
複製程式碼

需要呼叫這個方法之後才能開始接收原生的呼叫,建議寫在main裡面,如:

void main() {
  WidgetsFlutterBinding.ensureInitialized();
  NativeCaller.init();
  runApp(MyApp());
}
複製程式碼

iOS

新建一個類,實現FlutterPlugin協議:

class FlutterNativePlugin: NSObject {
        
}

extension FlutterNativePlugin: FlutterPlugin {
    
    static var registrar: FlutterPluginRegistrar?
    static var methodChannel: FlutterMethodChannel?
    
    static func register(with registrar: FlutterPluginRegistrar) {
        self.registrar = registrar
        methodChannel = FlutterMethodChannel(name: "your_channel_name", binaryMessenger: registrar.messenger());
        let plugin = FlutterNativePlugin()
        registrar.addMethodCallDelegate(plugin, channel: methodChannel!)
    }
    
    func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
        log.info("Flutter call:\(call.method), params:\(String(describing: call.arguments))");
    }

    static func sendMethod(_ method: SendMethod, with params: Any?, completion: FlutterResult? = nil) {
        log.info("Sending method:\(method), with params:\(String(describing: params))")
        guard let methodChannel = methodChannel else { return }
        methodChannel.invokeMethod(method.rawValue, arguments: params, result: completion)
    }

}
複製程式碼

其中,handle方法用於接收flutter端的呼叫,並通過result回撥結果給flutter; sendMethod方法是筆者自定義的方法,用於呼叫flutter的方法。

接著,在建立FltuterEngine的地方,把這個FlutterIosPlugin註冊進去,這樣它才能開始接收呼叫。

        if let registerer = flutterEngine.registrar(forPlugin: "FlutterNativePlugin") {
            FlutterNativePlugin.register(with: registerer)
        }

複製程式碼

當然,這兩行我們在一開始已經寫過了,這裡只是為了強調一下,必須有這兩行,FlutterNativePlugin才能開始工作。

現在,在適當的地方呼叫FlutterNativePlugin.sendMethod(_ method: SendMethod, with params: Any?, completion: FlutterResult? = nil) 方法,flutter就可以接收到了,同樣,在flutter適當的地方呼叫NativeCaller的對應方法,也可以成功呼叫到iOS端來了。

Android

Android需要新建兩個類:

public class FlutterAndroidPlugin implements FlutterPlugin {

    private static final String CHANNEL_NAME = "flutter.figure";
    private MethodChannel channel;
    private FlutterAndroidCallHandler handler;
    @Override
    public void onAttachedToEngine(@NonNull @NotNull FlutterPluginBinding binding) {
        setupChannel(binding.getBinaryMessenger(), binding.getApplicationContext());
    }

    @Override
    public void onDetachedFromEngine(@NonNull @NotNull FlutterPluginBinding binding) {
        teardownChannel();
    }

    private void setupChannel(BinaryMessenger messenger, Context context) {
        channel = new MethodChannel(messenger, CHANNEL_NAME);
        handler = new FlutterAndroidCallHandler();
        channel.setMethodCallHandler(handler);
    }

    private void teardownChannel() {
        handler = null;
        channel.setMethodCallHandler(null);
        channel = null;
    }

    public void callMethod(@NonNull  String method, @Nullable Object params) {
        channel.invokeMethod(method, params, new MethodChannel.Result() {
            @Override
            public void success(@Nullable @org.jetbrains.annotations.Nullable Object result) {
                Log.d("Plugin", "Success:" + result);
            }

            @Override
            public void error(String errorCode, @Nullable @org.jetbrains.annotations.Nullable String errorMessage, @Nullable @org.jetbrains.annotations.Nullable Object errorDetails) {
                Log.e("Plugin", "Error:" + errorMessage);
            }

            @Override
            public void notImplemented() {
                Log.e("Plugin", "Not implemented!");
            }
        });
    }

}

public class FlutterAndroidCallHandler implements MethodChannel.MethodCallHandler {

    @Override
    public void onMethodCall(@NonNull @NotNull MethodCall call, @NonNull @NotNull MethodChannel.Result result) {
        Log.d("Plugin", "Method:" + call.method + "Params:" + call.arguments);
    }


}
複製程式碼

然後,在建立FlutterEngin的地方,比如上面提到的MainActivity的onCreate中,將 這個plugin註冊起來:

        FlutterAndroidPlugin plugin = new FlutterAndroidPlugin();
        flutterEngine.getPlugins().add(plugin);
複製程式碼

之後,在適當的地方呼叫

plugin.callMethod("greeting", "Hello Flutter");
複製程式碼

在flutter端就可以收到了,同時,flutter端的呼叫也會在FlutterAndroidCallHandler.onMethodCall中接收到。

Demo 在這裡:Demo

相關文章