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