混合编程的例子
在Flutter
中,通过MethodChannel
与原生通讯的。当创建一个插件项目时,生成的项目会自带MethodChannel
的例子。
可以用Android Stuido
创建一个插件项目,或者使用命令行创建:
1
flutter create --template=plugin -i swift -a kotlin hybrid_plugin
当项目创建完,可以看到生成的关键代码:
lib/hybrid_plugin.dart
1
2
3
4
5
6
7
8
9
10
11
12
13
import 'dart:async';
import 'package:flutter/services.dart';
class HybridPlugin {
static const MethodChannel _channel =
const MethodChannel('hybrid_plugin');
static Future<String> get platformVersion async {
final String version = await _channel.invokeMethod('getPlatformVersion');
return version;
}
}
android/src/../hybrid_plugin/HybridPlugin.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.example.hybrid_plugin
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result
import io.flutter.plugin.common.PluginRegistry.Registrar
class HybridPlugin: MethodCallHandler {
companion object {
@JvmStatic
fun registerWith(registrar: Registrar) {
val channel = MethodChannel(registrar.messenger(), "hybrid_plugin")
channel.setMethodCallHandler(HybridPlugin())
}
}
override fun onMethodCall(call: MethodCall, result: Result) {
if (call.method == "getPlatformVersion") {
result.success("Android ${android.os.Build.VERSION.RELEASE}")
} else {
result.notImplemented()
}
}
}
ios/Classes/SwiftHybridPlugin.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import Flutter
import UIKit
public class SwiftHybridPlugin: NSObject, FlutterPlugin {
public static func register(with registrar: FlutterPluginRegistrar) {
let channel = FlutterMethodChannel(name: "hybrid_plugin", binaryMessenger: registrar.messenger())
let instance = SwiftHybridPlugin()
registrar.addMethodCallDelegate(instance, channel: channel)
}
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
result("iOS " + UIDevice.current.systemVersion)
}
}
以上就是一个完整的Flutter
与android native
、iOS native
通讯的例子。下面来详细分析一下。
Flutter中创建MethodChannel
在Flutter
项目中,创建MethodChannel
实例。并且要约定一个Channel名字,因为这个名字在native
端需要用到。
创建一个MethodChannel
实例:
1
static const MethodChannel _channel = const MethodChannel('hybrid_plugin');
然后,通过MethodChannel.invokeMethod<T>
来调起原生中的方法。
invokeMethod
方法接收两个入参:
String method
. 方法名(并非原生中的方法名,只相当于是一个Key)。原生端会根据不同的名字去调用不同的逻辑。dynamic arguments
. 可选,入参。
返回一个Future<T>
, 是原生端返回的内容T
.
1
final String version = await _channel.invokeMethod('getPlatformVersion');
到此,Flutter
端的工作就结束了。下面是原生端的代码实现。
原生端对接MethodChannel
以Android
端为例,需要实现MethodCallHandler
接口。在上面的例子中,是创建了HybridPlugin
类。
MethodCallHandler
接口只有一个方法:
1
2
3
public interface MethodCallHandler {
void onMethodCall(MethodCall var1, MethodChannel.Result var2);
}
onMethodCall
的第一个参数MethodCall
, 有两个属性:
String method
Object arguments
这跟在Flutter
中调用inokeMethod
方法的入参完全一致。在Flutter
端调用inokeMethod
之后,原生端就会回调onMethodCall
方法,并通过MethodCall
将入参传递过来。
第二个参数MethodChannel.Result
, 明显就是返回结果用的。它有三个方法:
它的定义如下:
1
2
3
4
5
6
7
public interface Result {
void success(@Nullable Object var1);
void error(String var1, @Nullable String var2, @Nullable Object var3);
void notImplemented();
}
suceess
方法在成功时调用。接收一个参数,并会将值返回到Flutter
中,即inovkeMethod
的返回值。
error
方法在失败时调用。接收三个参数,第一个参数是错误码;第二个参数是错误信息;第三个参数是错误详情。
notImplemented
方法表示没有匹配到方法名。
从源码中能够看到一些本质, 下面是一部分源码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
private final class IncomingMethodCallHandler implements BinaryMessageHandler {
//...
public void onMessage(ByteBuffer message, final BinaryReply reply) {
//...
try {
this.handler.onMethodCall(call, new MethodChannel.Result() {
public void success(Object result) {
reply.reply(MethodChannel.this.codec.encodeSuccessEnvelope(result));
}
public void error(String errorCode, String errorMessage, Object errorDetails) {
reply.reply(MethodChannel.this.codec.encodeErrorEnvelope(errorCode, errorMessage, errorDetails));
}
public void notImplemented() {
reply.reply((ByteBuffer)null);
}
});
} catch (RuntimeException var5) {
//...
}
}
}
其中,BinaryReply.reply
方法只接收一个ByteBuffer
, BinaryReply
定义如下:
1
2
3
public interface BinaryReply {
void reply(ByteBuffer var1);
}
也就是说,不管是success
、error
、notImplemented
, 都是返回的ByteBuffer
。那ByteBuffer
的内容是如何区分成功、失败数据的呢?
StandardMethodCodec
是默认的codec
,看下它的实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public final class StandardMethodCodec implements MethodCodec {
//...
public ByteBuffer encodeSuccessEnvelope(Object result) {
ExposedByteArrayOutputStream stream = new ExposedByteArrayOutputStream();
stream.write(0);
this.messageCodec.writeValue(stream, result);
ByteBuffer buffer = ByteBuffer.allocateDirect(stream.size());
buffer.put(stream.buffer(), 0, stream.size());
return buffer;
}
public ByteBuffer encodeErrorEnvelope(String errorCode, String errorMessage, Object errorDetails) {
ExposedByteArrayOutputStream stream = new ExposedByteArrayOutputStream();
stream.write(1);
this.messageCodec.writeValue(stream, errorCode);
this.messageCodec.writeValue(stream, errorMessage);
this.messageCodec.writeValue(stream, errorDetails);
ByteBuffer buffer = ByteBuffer.allocateDirect(stream.size());
buffer.put(stream.buffer(), 0, stream.size());
return buffer;
}
//...
}
如果是成功,先写入0
,再写入返回的内容;如果是失败,先写入1
,再分别写入错误信息。
源码分析部分到此结束,接着回到上面的分析中。
细心的同学可能注意到,在android/HybridPlugin
中,还有一个静态方法:
1
2
3
4
5
6
7
companion object {
@JvmStatic
fun registerWith(registrar: Registrar) {
val channel = MethodChannel(registrar.messenger(), "hybrid_plugin")
channel.setMethodCallHandler(HybridPlugin())
}
}
它有两个作用:
将原生端与
Flutter
端连接起来。在上面
Flutter
端创建Channel时,有约定一个Channel名hybrid_plugin
。这里就是通过这个Channel名来匹配对应的Channel。框架会自动查找项目中的
registerWith(Registrar)
方法,并通过代码生成的方式生成GeneratedPluginRegistrant
类。在该类中,会自动调用HybridPlugin.register
方法注册。
对于第二个作用,可以看下demo
中代码。我们在创建这个插件时,在根目录下会自动生成一个example
项目,并且已经引入了这个插件。
example/pubspec.yaml:
1
2
3
4
5
6
7
8
9
10
11
12
name: hybrid_plugin_example
description: Demonstrates how to use the hybrid_plugin plugin.
#...
dev_dependencies:
flutter_test:
sdk: flutter
hybrid_plugin:
path: ../
#...
在example
项目中的GeneratedPluginRegistrant
类:
1
2
3
4
5
6
7
8
9
10
public final class GeneratedPluginRegistrant {
public static void registerWith(PluginRegistry registry) {
if (alreadyRegisteredWith(registry)) {
return;
}
HybridPlugin.registerWith(registry.registrarFor("com.example.hybrid_plugin.HybridPlugin"));
}
//...
}
看到了吗?它已经调用HybridPlugin.registerWith
去注册了。
而在example
项目的MainActivity
中,会调用GeneratedPluginRegistrant.registerWith
:
1
2
3
4
5
6
class MainActivity: FlutterActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
GeneratedPluginRegistrant.registerWith(this)
}
}
这样,我们写的插件就会自动注册完成。
总结
开发一个混编插件,可以总结成下面几步:
- 约定协议。包括Channel名,方法名,方法入参与出参。
- 在
Flutter
端创建MethodChanel
。根据业务逻辑,通过MethodChannel.invokeMethod
实现调原生。 - 在原生端(以
Android
为例),实现MethodCallHandler
接口。根据不同的方法名,调用不同的逻辑。 - 创建
registerWith
静态方法,通过Channel名将MethodCallHandler
实现类绑定到MethodChannel
中。
当然,如果只是项目中需要混编,而不想实现一个插件的话,可以直接在MainActivty
中直接进行MethodChannel
绑定,无需创建静态方法registerWith
。