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