Flutter package:flutter插件
####创建插件
官方方案
flutter create --org [包前缀]--template=plugin -a kotlin [plugin_name(同时也会作为包的后缀)]
参数说明:
-
org 包前缀
-
template: 插件类型,目前支持创建三种类型的flutter工程(参考flutter create -h)
- [app] 默认 生成一个flutter app 工程
- [package] 生成一个可共享的纯 dart 库。
- [plugin] 生成一个桥接了原生方法的 dart 库。 也就是插件。
-
a 表明andoid插件语言类型:默认 java 可以改为kotlin
-
i 表明IOS插件语言类型:默认ObjectC 可修改为swift
-
最后一个参数: 插件名称。也会作为报名的最后一部分。建议使用下划线形式。这个名称会在以下文件内出现:
pubspec.yaml 作为插件名称。外部引用此插件也是用这个名字。 pubspec.yam 中的 flutter 部分,主要用来自动生成相关代码。 plugin: androidPackage: com.sd.test_plugin pluginClass: TestPlugin lib下根目录中的同名文件: 对外暴露的插件文件,可以写逻辑,也可以作为统一入口。export希望导出的功能 method名称 static const MethodChannel _channel = const MethodChannel('test_plugin'); test下的测试文件。 example下的pubspec.yaml文件。 android文件夹下:setting.gradle,build.gradle. AndroidManifest中包名。 src中的包结构。 插件桥接原生的类(java,kotlin)文件。此文件会自动将 “_” 变为驼峰形式命名 插件类中的channel 名称,用来和dart端通信 val channel = MethodChannel(registrar.messenger(), "test_plugin")
因此建议生成文件时采用下划线分隔,命名。如果已经生成了插件,想修改名字,记得仔细排查上述文件。注意:methodChannel中的名称必须保证唯一性。最好增加一个包前缀,防止和其他三方插件冲突。此处可以单独修改,不影响插件本身的名称。
官方给出的开发Package方法,适用于生成一个较为通用的插件。即和具体业务关系不是很大。逻辑相对独立。比如获取包名,http。打点等。如果和APP业务耦合较多,个人建议采用动态注册插件的方法。
动态注册插件
从上面命令生成的模版工程可以看出。插件的本质就是原生端(Android,IOS),通过Method和dart端进行通信。将信息(数据)传递到Dart端。因此只要确定好信息通道(methodChannel的名称),完全可以直接在代码中编写相应逻辑。
- 原生端(android)
// 在承载相应 Flutter 内容的 Activity 中 注册MethodChannel。
class MainActivity() : FlutterActivity() {
private val CHANNEL = "com.sd.example.customPlugin"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
GeneratedPluginRegistrant.registerWith(this)
// new Method时,会进行channel的绑定。
MethodChannel(flutterView, CHANNEL).setMethodCallHandler { call, result ->
switch (methodCall.method) {
case "getInformation":
// getInformation
result.success("information");
break;
default:
result.notImplemented();
}
}
}
}
- dart 端
class _MyHomePageState extends State<MyHomePage> {
static const platform = const MethodChannel('com.sd.example.customPlugin');
platform.invokeMethod("getInformation").then((value) {
// 获取到 “information” 字符串
print(value);
});
}
优化内容
如果仔细观察系统生成插件的代码可以发现如下内容
public static void registerWith(PluginRegistry registry) {
if (alreadyRegisteredWith(registry)) {
return;
}
// 注册各个插件
}
private static boolean alreadyRegisteredWith(PluginRegistry registry) {
final String key = GeneratedPluginRegistrant.class.getCanonicalName();
if (registry.hasPlugin(key)) {
return true;
}
registry.registrarFor(key);
return false;
}
在注册插件之前,判断插件是否已经注册过。避免重复注册,浪费资源。(重复注册, 会在Map中覆盖之前注册过的channel,可以参考DartMessenger中setMessageHandler方法的实现)建议参照官方实现方法对动态注册过程进行优化。
public static void registerCustomPlugin(PluginRegistry registry) {
// 声明一个Key用来判断是否在此registry中注册过此插件。没必要和methodChannel中的名字相同,每一个注册过程有一个名字即可。
String key = JtSdk.application.getPackageName() + ".customPlugin";
if (!registry.hasPlugin(key)) {
new MethodChannel(registry.registrarFor(key).messenger(),
Application.getPackageName() + "/nativePlugin").setMethodCallHandler(
// 注意:此方法调用在单独线程中,当心线程同步问题
(methodCall, result) -> {
switch (methodCall.method) {
case "getInformation":
// getInformation
result.success("information");
break;
default:
result.notImplemented();
break;
}
});
}
}
#####出场角色介绍
- PluginRegistry: 插件注册表,主要实现类 FlutterPluginRegistry。注册插件的入口,内部通过Map维护插件列表。他不直接管理每个插件。充当一个类似门户的角色,持有activity,flutterView,flutterNative等关键对象。
- MethodChannel: 负责 1.绑定BinaryMessenger和具体channel名称,暴露原生方法给Dart。2. 和dart端进行通信,调用Dart端方法。
构造方法
MethodChannel(BinaryMessenger messenger, String name, MethodCodec codec)
-
BinaryMessenger: 原生Native通信角色。主要实现类就是FlutterNativeView。上面的构造方法中,可以看出每个MethodChannel适合一个Messenger绑定的。如果新启动了一个FlutterActivity,默认情况下,是需要重新注册所有插件的(Boost因为复用了FlutterNativeView,因此不需要重新注册)
-
MethodCodec: 消息编解码器。负责编解码MethodCall,和返回结果。使数据可以通过Native(c++)层在原生,Dart端进行传递。实现类有两种:
- StandardMethodCodec :直接以二进制形式传递数据传递,要求数据的写入,读取顺序必须一致,类似Parcelable;
- JSONMethodCodec:数据先封装为Json格式,然后再变为二进制。返过来也是,现将bytebuffer变为String,然后转为Json。
各平台类型对应关系
| Dart | Android | iOS |
|---|---|---|
| null | null | nil (NSNull when nested) |
| bool | java.lang.Boolean | NSNumber numberWithBool: |
| int | java.lang.Integer | NSNumber numberWithInt: |
| int, if 32 bits not enough | java.lang.Long | NSNumber numberWithLong: |
| int, if 64 bits not enough | java.math.BigInteger | FlutterStandardBigInteger |
| double | java.lang.Double | NSNumber numberWithDouble: |
| String | java.lang.String | NSString |
| Uint8List | byte[] | FlutterStandardTypedData typedDataWithBytes: |
| Int32List | int[] | FlutterStandardTypedData typedDataWithInt32: |
| Int64List | long[] | FlutterStandardTypedData typedDataWithInt64: |
| Float64List | double[] | FlutterStandardTypedData typedDataWithFloat64: |
| List | java.util.ArrayList | NSArray |
| Map | java.util.HashMap | NSDictionary |
- MethodCallHandler: 负责处理dart 端发送过来的请求,接受两个参数:MethodCall,Result
- MethodCall:请求的封装类:
- MethodCall.method: 是dart端希望调用的方法名称;
- MethodCall.arguments: 是dart端传递过来的参数,可能是上述对照表中的任意类型。
- Result: 负责回调给dart端结果,结果分为三种:
- success(result) : result会自动转换为上述表中类型,传递给dart端。
- error(code,messege,details): dart端会抛出一个 PlatformException 的异常。参数为异常中的code等值
- notImplemented():dart端抛出 MissingPluginException
开发使用插件
创建完成插件之后,就需要在两端编写相应的逻辑代码。一个简单的插件,Dart端几乎不需要编写代码,只负责调用即可。唯一需要注意的是Flutter和原生端运行在不同的线程中。因此所有跨端的调用都是异步执行的。
dart端
class _MyHomePageState extends State<MyHomePage> {
static const platform = const MethodChannel('com.sd.example.customPlugin');
// invodeMethod返回的是一个Future<T> 类型
platform.invokeMethod("getInformation").then((value) {
// 获取到 “information” 字符串
print(value);
});
}
错误写法
getInformation() async {
return await platform.invokeMethod("getInformation");
}
test() {
Strng information = getInformation();
print(information);
}
原生端
原生端主要实现的方法,就在 onMethodCall 中,针对不同的method,执行不同逻辑,并通过result,返回结果。
@Override
public void onMethodCall(MethodCall methodCall, Result result) {
switch (methodCall.method) {
case "method1":
var param1 = methodCall.argument("param1"),
var param2 = methodCall.argument("param2");
result.success(param1 + param2);
break;
case "method2":
try{
// do business
result.success(res);
} catch (e) {
result.error("101", "系统错误", "错误原因");
}
break;
default:
result.notImplemented();
break;
}
需要注意以下几点
- onMethod方法调用在UI线程,要注意不要执行耗时操作。
- result中的相应方法一定要调用。否则Dart端会有一个待完成的Future对象,一直在挂起等待。虽然不会阻塞dart线程,但是会有其他不可预期的异常。
- result下的方法。需要在主线程调用。 flutter engine中的组件都是非线程安全的。为避免出现未知错误,和engine通信的相关方法。都要在主线程调用执行。
####使用方法:
和使用第三方库相同,在pubspec.yaml 文件中添加相关依赖。
dependencies:
#通过git形式进行依赖
jt_base_http:
git:
url: ssh://gerrit.rong360.com:29418/flutter_plugins
path: jt_base_http
# 通过版本号依赖
json_annotation: ^2.4.0
# 通过相对路径依赖
jt_base_log:
path: ../
然后创建methodChannel,调用相应方法即可。
class _MyHomePageState extends State<MyHomePage> {
static const platform = const MethodChannel('com.sd.example.customPlugin');
platform.invokeMethod("getInformation").then((value) {
// 获取到 “information” 字符串
print(value);
});
}
原理

dart -> 原生
// dart本身是单线程模型,对与原生的调用必须通过异步方式执行。
platform.invokeMethod("xxx") 方法本身也是返回一个Future对象。
const platform = const MethodChannel('channel_name');
await platform.invokeMethod("xxx");
-> invokeMethod源码
platform_channel.dart
@optionalTypeArgs
Future<T> invokeMethod<T>(String method, [ dynamic arguments ]) async {
assert(method != null);
// 关键代码,通过 BinaryMessenger 以ByteData形式发送 message
final ByteData result = await binaryMessenger.send(
// channel_name
name,
// ByteData 包含方法名称和相关参数。无论使用Json还是Standard codec。都会编码为二进制
codec.encodeMethodCall(MethodCall(method, arguments)),
);
if (result == null) {
throw MissingPluginException('No implementation found for method $method on channel $name');
}
final T typedResult = codec.decodeEnvelope(result);
return typedResult;
}
->
binary_messenger.dart
@override
Future<ByteData> send(String channel, ByteData message) {
final MessageHandler handler = _mockHandlers[channel];
if (handler != null)
return handler(message);
// 真正发送message的方法
return _sendPlatformMessage(channel, message);
}
Future<ByteData> _sendPlatformMessage(String channel, ByteData message) {
// 注意:Completer 其内部包含一个future对象,供dart isolate执行。至于这个future对象何时结束,又completer 自己控制
final Completer<ByteData> completer = Completer<ByteData>();
// 发送message方法到JNI层,同时封装一个回调 PlatformMessageResponseCallback 用来接受原生端的respone,并结束这个 future
ui.window.sendPlatformMessage(channel, message, (ByteData reply) {
try {
completer.complete(reply);
} catch (exception, stack) {
FlutterError.reportError(FlutterErrorDetails(
exception: exception,
stack: stack,
library: 'services library',
context: ErrorDescription('during a platform message response callback'),
));
}
});
return completer.future;
}
->
window.dart
// callback定义
typedef PlatformMessageResponseCallback = void Function(ByteData data);
// JNI方法。
void sendPlatformMessage(String name,
ByteData data,
PlatformMessageResponseCallback callback) {
final String error =
_sendPlatformMessage(name, _zonedPlatformMessageResponseCallback(callback), data);
if (error != null)
throw Exception(error);
}
String _sendPlatformMessage(String name,
PlatformMessageResponseCallback callback,
ByteData data) native 'Window_sendPlatformMessage';
到这里,我们可以知道,invokeMethod方法,其实就是包装一些请求参数(methodname,params),通过codec加密。并返回一个 Future 放在dart event queue中执行,至于这个Future何时结束,完全由native端何时调用complete决定。
继续JNI层,看下我们的请求到底去了哪里。切入点是'Window_sendPlatformMessage'
lib/ui/window/window.cc
{"Window_sendPlatformMessage", _SendPlatformMessage, 4, true}
->
engine/lib/ui/window/window.cc
Dart_Handle SendPlatformMessage(Dart_Handle window,
const std::string& name,
Dart_Handle callback,
Dart_Handle data_handle) {
UIDartState* dart_state = UIDartState::Current();
if (!dart_state->window()) {
return tonic::ToDart(
"Platform messages can only be sent from the main isolate");
}
fml::RefPtr<PlatformMessageResponse> response;
if (!Dart_IsNull(callback)) {
response = fml::MakeRefCounted<PlatformMessageResponseDart>(
tonic::DartPersistentValue(dart_state, callback),
dart_state->GetTaskRunners().GetUITaskRunner());
}
if (Dart_IsNull(data_handle)) {
dart_state->window()->client()->HandlePlatformMessage(
fml::MakeRefCounted<PlatformMessage>(name, response));
} else {
tonic::DartByteData data(data_handle);
const uint8_t* buffer = static_cast<const uint8_t*>(data.data());
// 通知具体 client 处理message
dart_state->window()->client()->HandlePlatformMessage(
fml::MakeRefCounted<PlatformMessage>(
name, std::vector<uint8_t>(buffer, buffer + data.length_in_bytes()),
response));
}
省略其他过程。平台判断等
->
platform_view_android.cc
// |PlatformView|
void PlatformViewAndroid::HandlePlatformMessage(
fml::RefPtr<flutter::PlatformMessage> message) {
JNIEnv* env = fml::jni::AttachCurrentThread();
fml::jni::ScopedJavaLocalRef<jobject> view = java_object_.get(env);
if (view.is_null())
return;
int response_id = 0;
if (auto response = message->response()) {
// 缓存一下respone。等待客户端处理完成在通知回Dart
response_id = next_response_id_++;
pending_responses_[response_id] = response;
}
auto java_channel = fml::jni::StringToJavaString(env, message->channel());
if (message->hasData()) {
fml::jni::ScopedJavaLocalRef<jbyteArray> message_array(
env, env->NewByteArray(message->data().size()));
env->SetByteArrayRegion(
message_array.obj(), 0, message->data().size(),
reinterpret_cast<const jbyte*>(message->data().data()));
message = nullptr;
// This call can re-enter in InvokePlatformMessageXxxResponseCallback.
// 调用原生方法处理 message
FlutterViewHandlePlatformMessage(env, view.obj(), java_channel.obj(),
message_array.obj(), response_id);
} else {
message = nullptr;
// This call can re-enter in InvokePlatformMessageXxxResponseCallback.
// 调用原生方法处理 message
FlutterViewHandlePlatformMessage(env, view.obj(), java_channel.obj(),
nullptr, response_id);
}
}
platform_view_android_jni.cc
// 原生端方法名为 io/flutter/embedding/engine/FlutterJNI.handlePlatformMessage()
g_flutter_jni_class = new fml::jni::ScopedJavaGlobalRef<jclass>(
env, env->FindClass("io/flutter/embedding/engine/FlutterJNI"));
g_handle_platform_message_method =
env->GetMethodID(g_flutter_jni_class->obj(), "handlePlatformMessage",
"(Ljava/lang/String;[BI)V");
void FlutterViewHandlePlatformMessage(JNIEnv* env,
jobject obj,
jstring channel,
jobject message,
jint responseId) {
env->CallVoidMethod(obj, g_handle_platform_message_method, channel, message,
responseId);
FML_CHECK(CheckException(env));
}
消息发送到Java端后,自然需要Java端处理方法,并将结果返回给dart
先看 java端注册Messanger方法
简单版本,不需要flutter create
// messenger一般就是 FlutterView
new MethodChannel(messenger, "channel_name").setMethodCallHandler(
new MethodChannel.MethodCallHandler(){
onMethodCal((@NonNull MethodCall call, @NonNull MethodChannel.Result result){
// 根据call中的method(方法名)arguments(参数)执行操作。
// 调用result中的api: success,error,notImplemented。发送结果到dart
}
})
setMethodCallHandler时 实际调用了
@UiThread
public void setMethodCallHandler(@Nullable MethodChannel.MethodCallHandler handler) {
this.messenger.setMessageHandler(this.name, handler == null ? null :
new MethodChannel.IncomingMethodCallHandler(handler)); // 对handler的封装,内部管理何时调用onMethodCall。并传递Result。
}
messenger就是上面提到的FlutterView。其内部又直接调用了 FlutterNativeView 的 setMessageHandler 方法
FlutterNativeView.java
@UiThread
public void setMessageHandler(String channel, BinaryMessageHandler handler) {
this.mNativeView.setMessageHandler(channel, handler);
}
// 注:关于 FlutterNativeView 他就是FlutterView(原生View)和native Fluter 引擎通信的关键对象。官方所给的混合方案中。生成的 Flutter 类 在 createView 的时候都会创建一个新的FlutterNativeView,对于内存和性能都有所损耗。FlutterBoost方案中,在创建新页面的时候复用了FutterView和FlutterNativeView。因此可以提高部分性能。
之后又陆续调用了 DartExecutor, DartMessenger的setMessageHandler 方法。最终 handler 以Map的形式,保存在DartMessenger中的messageHandlers map中,key是之前提到的 "channel_name" .
仔细阅读DartMessenger 类,可以发现一个 handleMessageFromDart 方法,这个方法,就是Native端(FlutterViewHandlePlatformMessage)最终调用的java方法
public void handleMessageFromDart(@NonNull String channel, @Nullable byte[] message, int replyId) {
Log.v("DartMessenger", "Received message from Dart over channel '" + channel + "'");
BinaryMessageHandler handler = (BinaryMessageHandler)this.messageHandlers.get(channel);
if (handler != null) {
try {
Log.v("DartMessenger", "Deferring to registered handler to process message.");
ByteBuffer buffer = message == null ? null : ByteBuffer.wrap(message);
// 关键函数,将dart端发送过来的 message 传递给最终的 Handler
handler.onMessage(buffer, new DartMessenger.Reply(this.flutterJNI, replyId));
} catch (Exception var6) {
Log.e("DartMessenger", "Uncaught exception in binary message listener", var6);
this.flutterJNI.invokePlatformMessageEmptyResponseCallback(replyId);
}
} else {
Log.v("DartMessenger", "No registered handler for message. Responding to Dart with empty reply message.");
this.flutterJNI.invokePlatformMessageEmptyResponseCallback(replyId);
}
}
后续也就是一直调用到我们之前设置的 onMethodCall 方法了。
还有一个疑问,就是之前dart端发送消息的时候,执行了一个 Futture 这个 Futture 需要持有它的 Completer对象调用complete方法,才能停止。这个方法什么时候调用呢?
之前提过 setMethodCall里,对与Handler有一个封装类 MethodChannel.IncomingMethodCallHandler(handler) 分析一下他的 onMesage 方法 就有答案了
@UiThread
public void onMessage(ByteBuffer message, final BinaryReply reply) {
// 解码MethodCall 注意要使用对应的Codec
MethodCall call = MethodChannel.this.codec.decodeMethodCall(message);
try {
this.handler.onMethodCall(call, new MethodChannel.Result() {
// 加密结果,并返回给Dard
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) {
Log.e("MethodChannel#" + MethodChannel.this.name, "Failed to handle method call", var5);
reply.reply(MethodChannel.this.codec.encodeErrorEnvelope("error", var5.getMessage(), (Object)null));
}
}
可以看到,只要我们调用了result的任意方法(success,error,notImplemented),都会通过这个reply 方法发送一个消息出去,通知最开始提到的Completer对象执行 complete。
简单看一下调用链。
DartMessenger.Reply 对象。
public void reply(@Nullable ByteBuffer reply) {
if (this.done.getAndSet(true)) {
throw new IllegalStateException("Reply already submitted");
} else {
if (reply == null) {
this.flutterJNI.invokePlatformMessageEmptyResponseCallback(this.replyId);
} else {
this.flutterJNI.invokePlatformMessageResponseCallback(this.replyId, reply, reply.position());
}
}
}
又回到了FlutterJNI中。将事件通知到JNI层。
platform_view_android.cc(之前DispatchMessage的类)
void PlatformViewAndroid::InvokePlatformMessageResponseCallback(
JNIEnv* env,
jint response_id,
jobject java_response_data,
jint java_response_position) {
if (!response_id)
return;
// 找到正在等待处理的请求(之前HandlePlaformMessage时做的缓存),也就是dart端发送过来的message请求
auto it = pending_responses_.find(response_id);
if (it == pending_responses_.end())
return;
uint8_t* response_data =
static_cast<uint8_t*>(env->GetDirectBufferAddress(java_response_data));
std::vector<uint8_t> response = std::vector<uint8_t>(
response_data, response_data + java_response_position);
auto message_response = std::move(it->second);
// 清楚记录
pending_responses_.erase(it);
// 调用Complete方法。
message_response->Complete(
std::make_unique<fml::DataMapping>(std::move(response)));
}
platform_message_response_dart.cc
void PlatformMessageResponseDart::Complete(std::unique_ptr<fml::Mapping> data) {
if (callback_.is_empty())
return;
FML_DCHECK(!is_complete_);
is_complete_ = true;
ui_task_runner_->PostTask(fml::MakeCopyable(
[callback = std::move(callback_), data = std::move(data)]() mutable {
std::shared_ptr<tonic::DartState> dart_state =
callback.dart_state().lock();
if (!dart_state)
return;
tonic::DartState::Scope scope(dart_state);
Dart_Handle byte_buffer = WrapByteData(std::move(data));
// 释放callback 将结果传到 dart 端
tonic::DartInvoke(callback.Release(), {byte_buffer});
}));
}