6.Flutte3.0 遥遥领先系列|一文教你完全掌握原生交互(
目录:
1.3种方式比较, BasicMessageChannel, MethodChannel, EventChannel
2.原生交互的缺点
3.Pigeon插件的使用
4.fluttter 嵌套原生的view视图
5.fluttter 动态嵌套原生的view视图
解决问题: Flutter和原生交互分为2种, 数据传输和view的控制显示
形象的举例:
一次典型的方法调用过程类似网络调用!
由作为客户端的 Flutter,通过方法通道向作为服务端的原生代码宿主发送方法调用请求,原生代码宿主在监听到方法调用的消息后,调用平台相关的 API 来处理 Flutter 发起的请求,最后将处理完毕的结果通过方法通道回发至 Flutter
dart与Native之间多了一层C++写的Engine,至于flutter的C++源码,
架构图:
原生交互的类型.jpg
- 3种方式比较, BasicMessageChannel, MethodChannel, EventChannel
Flutter为开发者提供了一个轻量级的解决方案,即逻辑层的方法通道(Method Channel)机制。基于方法通道,我们可以将原生代码所拥有的能力,以接口形式暴露给Dart,从而实现Dart代码与原生代码的交互,就像调用了一个普通的Dart API一样。
Flutter定义了三种不同类型的Channel
1>. BasicMessageChannel:用于传递字符串和半结构化的信息。BasicMessageChannel支持数据双向传递,有返回值。
2> .MethodChannel:用于传递方法调用(method invocation)。MethodChannel支持数据双向传递,有返回值。通常用来调用 native 中某个方法
3>. EventChannel: 用于数据流(event streams)的通信。 EventChannel仅支持数据单向传递,无返回值。有监听功能,比如电量变化之后直接推送数据给flutter端
对比表格:
实现调用步骤 :
1). Flutter能够通过轻量级的异步方法调用,实现与原生代码的交互。一次典型的调用过程由Flutter发起方法调用请求开始,
2). 请求经由唯一标识符指定的方法通道到达原生代码宿主,
3). 而原生代码宿主则通过注册对应方法实现、响应并处理调用请求,
4). 最后将执行结果通过消息通道,回传至Flutter。
5). 方法调用请求的处理和响应,在Android中是通过FlutterView
1.1 问题:Flutter如何实现一次方法调用请求?
方法调用过程是异步的,所以我们需要使用非阻塞(或者注册回调)来等待原生代码给予响应
非阻塞: 就是单线程中的处理方案
1.1 .1 案例演示: flutter调用原生的方法, MethodChannel
Flutter代码
void getBatteryInfo() async {
// 声明 MethodChannel
const platform = MethodChannel('samples.chenhang/utils');
// 核心代码二
final int result = await platform.invokeMethod("getBatteryInfo");
// 返回值的类型是啥????,可以统一定义为json
setState(() {
_result = result;
});
}
}
伪代码: flutter发起原生调用
// 声明 MethodChannel
const platform = MethodChannel('samples.chenhang/utils');
// 处理按钮点击
handleButtonClick() async{
int result;
// 异常捕获
try {
// 异步等待方法通道的调用结果
result = await platform.invokeMethod('openAppMarket');
}
catch (e) {
result = -1;
}
print("Result:$result");
}
1.1.2 原生通过MethodChannel调用flutter的案例:
在原生代码中完成方法调用的响应(flutter监听原生调用)
android代码
考虑到打开应用市场的过程可能会出错,我们也需要增加 try-catch 来捕获可能的异常:
核心类:
· ) MethodChannel
· ) DartExecutor
· ) BinaryMessenger:是一个接口,在FlutterView中实现了该接口,在BinaryMessenger的方法中通过JNI来与系统底层沟通
· ) FlutterEngine: 发送数据必然要通过
内部都是通过DartMessenger来调用FlutterJNI的相关API完成通信的
public class MainActivity extends FlutterActivity {
private static final String CHANNEL = "coderwhy.com/battery";
@Override
public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
// 1.创建MethodChannel对象
MethodChannel methodChannel = new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), CHANNEL);
// 2.添加调用方法的回调
methodChannel.setMethodCallHandler(
(call, result) -> {
// 2.1.如果调用的方法是getBatteryInfo,那么正常执行
if (call.method.equals("getBatteryInfo")) {
// 2.1.1.调用另外一个自定义方法回去电量信息
int batteryLevel = getBatteryLevel();
// 2.1.2. 判断是否正常获取到
if (batteryLevel != -1) {
// 获取到返回结果
result.success(batteryLevel);
} else {
// 获取不到抛出异常
result.error("UNAVAILABLE", "Battery level not available.", null);
}
} else {
// 2.2.如果调用的方法是getBatteryInfo,那么正常执行
result.notImplemented();
}
}
);
}
需要注意的是,方法通道是非线程安全的。这意味着原生代码与 Flutter 之间所有接口调用必须发生在主线程
1.2. 1 MethodChannel(flutter----> 原生----(返回结果到)---> flutter)
源码分析:
原理: 从flutter开始调用:
调用栈图:
class MethodChannel {
/// Creates a [MethodChannel] with the specified [name].
///
/// The [codec] used will be [StandardMethodCodec], unless otherwise
/// specified.
///
/// The [name] and [codec] arguments cannot be null. The default [ServicesBinding.defaultBinaryMessenger]
/// instance is used if [binaryMessenger] is null.
const MethodChannel(this.name, [this.codec = const StandardMethodCodec(), BinaryMessenger? binaryMessenger ])
: _binaryMessenger = binaryMessenger;
@optionalTypeArgs
Future<T?> _invokeMethod<T>(String method, { required bool missingOk, dynamic arguments }) async {
final ByteData input = codec.encodeMethodCall(MethodCall(method, arguments)); // 编码数据
final ByteData? result =
!kReleaseMode && debugProfilePlatformChannels ?
await (binaryMessenger as _ProfiledBinaryMessenger).sendWithPostfix(name, '#$method', input) :
await binaryMessenger.send(name, input); // binaryMessenger发送数据
if (result == null) {
if (missingOk) {
return null;
}
throw MissingPluginException('No implementation found for method $method on channel $name');
}
return codec.decodeEnvelope(result) as T?;
}
_DefaultBinaryMessenger
@override
Future<ByteData?> send(String channel, ByteData? message) {
final Completer<ByteData?> completer = Completer<ByteData?>();
ui.PlatformDispatcher.instance.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;
}
调用到PlatformDispatcher的_sendPlatformMessage方法,进入flutter engine层的_SendPlatformMessage。
@Native<Handle Function(Handle, Handle, Handle)>(symbol: 'PlatformConfigurationNativeApi::SendPlatformMessage')
external static String? __sendPlatformMessage(String name, PlatformMessageResponseCallback? callback, ByteData? data);
PlatformConfiguration
void _SendPlatformMessage(Dart_NativeArguments args) {
tonic::DartCallStatic(&SendPlatformMessage, args);
}
}
void PlatformConfiguration::RegisterNatives(
tonic::DartLibraryNatives* natives) {
natives->Register({
{"PlatformConfiguration_sendPlatformMessage", _SendPlatformMessage, 4,
true},
...
});
}
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->platform_configuration()) {
return tonic::ToDart(
"Platform messages can only be sent from the main isolate");
}
fml::RefPtr<PlatformMessageResponse> response;
if (!Dart_IsNull(callback)) {
//PlatformMessageResponseDart对象中采用的是UITaskRunner
response = fml::MakeRefCounted<PlatformMessageResponseDart>(
tonic::DartPersistentValue(dart_state, callback),
dart_state->GetTaskRunners().GetUITaskRunner());
}
if (Dart_IsNull(data_handle)) {
dart_state->platform_configuration()->client()->HandlePlatformMessage(
std::make_unique<PlatformMessage>(name, response));
} else {
tonic::DartByteData data(data_handle);
const uint8_t* buffer = static_cast<const uint8_t*>(data.data());
dart_state->platform_configuration()->client()->HandlePlatformMessage(
std::make_unique<PlatformMessage>(
name, fml::MallocMapping::Copy(buffer, data.length_in_bytes()),
response));
}
return Dart_Null();
}
RuntimeController //flutter/runtime/runtime_controller.cc
void RuntimeController::HandlePlatformMessage(
std::unique_ptr<PlatformMessage> message) {
client_.HandlePlatformMessage(std::move(message));
}
Engine
static constexpr char kAssetChannel[] = "flutter/assets";
void Engine::HandlePlatformMessage(fml::RefPtr<PlatformMessage> message) {
if (message->channel() == kAssetChannel) {
HandleAssetPlatformMessage(std::move(message));
} else {
delegate_.OnEngineHandlePlatformMessage(std::move(message));
}
}
Shell //flutter/shell/common/shell.cc
constexpr char kSkiaChannel[] = "flutter/skia";
void Shell::OnEngineHandlePlatformMessage(
fml::RefPtr<PlatformMessage> message) {
if (message->channel() == kSkiaChannel) {
HandleEngineSkiaMessage(std::move(message));
return;
}
task_runners_.GetPlatformTaskRunner()->PostTask(
[view = platform_view_->GetWeakPtr(), message = std::move(message)]() {
if (view) {
view->HandlePlatformMessage(std::move(message));
}
});
}
核心: # PlatformViewAndroid
//flutter/shell/platform/android/platform_view_android.cc
这个view对于Android平台的实例为PlatformViewAndroid。
std::unordered_map<int, fml::RefPtr<flutter::PlatformMessageResponse>>
pending_responses_;
// |PlatformView|
void PlatformViewAndroid::HandlePlatformMessage(
std::unique_ptr<flutter::PlatformMessage> message) {
int response_id = 0;
if (auto response = message->response()) {
response_id = next_response_id_++;
pending_responses_[response_id] = response;
}
// This call can re-enter in InvokePlatformMessageXxxResponseCallback.
jni_facade_->FlutterViewHandlePlatformMessage(std::move(message),
response_id);
message = nullptr;
}
c调用android ,对应FlutterJNI.java中的handlePlatformMessage()方法
void FlutterViewHandlePlatformMessage(JNIEnv* env, jobject obj,
jstring channel, jobject message,
jint responseId) {
env->CallVoidMethod(obj, g_handle_platform_message_method, channel, message, responseId);
}
Android 端源码分析:
FlutterJN
@VisibleForTesting
public void handlePlatformMessage(
@NonNull final String channel,
ByteBuffer message,
final int replyId,
final long messageData) {
if (platformMessageHandler != null) {
platformMessageHandler.handleMessageFromDart(channel, message, replyId, messageData);
} else {
nativeCleanupMessageData(messageData);
}
// TODO(mattcarroll): log dropped messages when in debug mode
// (https://github.com/flutter/flutter/issues/25391)
}
public void handleMessageFromDart(
@NonNull String channel, @Nullable ByteBuffer message, int replyId, long messageData) {
Log.v(TAG, "Received message from Dart over channel '" + channel + "'");
synchronized (handlersLock) {
handlerInfo = messageHandlers.get(channel);
messageDeferred = (enableBufferingIncomingMessages.get() && handlerInfo == null);
if (messageDeferred) {
if (!bufferedMessages.containsKey(channel)) {
bufferedMessages.put(channel, new LinkedList<>());
}
List<BufferedMessageInfo> buffer = bufferedMessages.get(channel);
buffer.add(new BufferedMessageInfo(message, replyId, messageData));
}
}
if (!messageDeferred) {
dispatchMessageToQueue(channel, handlerInfo, message, replyId, messageData);
}
}
把消息添加到队列中去!
private void dispatchMessageToQueue(
@NonNull String channel,
@Nullable HandlerInfo handlerInfo,
@Nullable ByteBuffer message,
int replyId,
long messageData) {
// Called from any thread.
final DartMessengerTaskQueue taskQueue = (handlerInfo != null) ? handlerInfo.taskQueue : null;
TraceSection.beginAsyncSection("PlatformChannel ScheduleHandler on " + channel, replyId);
Runnable myRunnable =
() -> {
TraceSection.endAsyncSection("PlatformChannel ScheduleHandler on " + channel, replyId);
TraceSection.begin("DartMessenger#handleMessageFromDart on " + channel);
try {
invokeHandler(handlerInfo, message, replyId); // 调用
if (message != null && message.isDirect()) {
// This ensures that if a user retains an instance to the ByteBuffer and it
// happens to be direct they will get a deterministic error.
message.limit(0);
}
} finally {
// This is deleting the data underneath the message object.
flutterJNI.cleanupMessageData(messageData);
TraceSection.end();
}
};
final DartMessengerTaskQueue nonnullTaskQueue =
taskQueue == null ? platformTaskQueue : taskQueue;
nonnullTaskQueue.dispatch(myRunnable);
}
所有的Channel都会走上面的逻辑,从这里的handlerInfo.handler.onMessage开始有所不一样了,因为不同的Channel的handler不同。
private void invokeHandler(
@Nullable HandlerInfo handlerInfo, @Nullable ByteBuffer message, final int replyId) {
// Called from any thread.
if (handlerInfo != null) {
try {
Log.v(TAG, "Deferring to registered handler to process message.");
handlerInfo.handler.onMessage(message, new Reply(flutterJNI, replyId));
} catch (Exception ex) {
Log.e(TAG, "Uncaught exception in binary message listener", ex);
flutterJNI.invokePlatformMessageEmptyResponseCallback(replyId);
} catch (Error err) {
handleError(err);
}
} else {
Log.v(TAG, "No registered handler for message. Responding to Dart with empty reply message.");
flutterJNI.invokePlatformMessageEmptyResponseCallback(replyId);
}
}
public void onMessage(ByteBuffer message, final BinaryReply reply) {
final MethodCall call = codec.decodeMethodCall(message);
try {
handler.onMethodCall(
call,
new Result() {
@Override
public void success(Object result) {
reply.reply(codec.encodeSuccessEnvelope(result));
}
@Override
public void error(String errorCode, String errorMessage, Object errorDetails) {
reply.reply(codec.encodeErrorEnvelope(errorCode, errorMessage, errorDetails));
}
@Override
public void notImplemented() {
reply.reply(null);
}
});
} catch (RuntimeException e) {
Log.e(TAG + name, "Failed to handle method call", e);
reply.reply(
codec.encodeErrorEnvelopeWithStacktrace(
"error", e.getMessage(), null, Log.getStackTraceString(e)));
}
}
}
}
MethodChannel的执行流程涉及到主线程和UI线程的交互,代码从Dart到C++再到Java层,执行完相应逻辑后原路返回,从Java层到C++层再到Dart层。
1.2.2 EventChannel (原生------>flutter)
不存在: 原生向flutter调用, 然后flutter把结果返回给原生!
EventChannel 可以由 Android 原生主动向 Flutter 发起交互请求
相对于原生为主动式交互,类似于 Android 发送一个广播在 Flutter 端进行接收
android端:
new EventChannel(flutterView, CHANNEL).setStreamHandler(new EventChannel.StreamHandler() {
@Override
public void onListen(Object arguments, final EventChannel.EventSink events) {
events.success("我来自 " + TAG +" !! 使用的是 EventChannel 方式");
}
@Override
public void onCancel(Object arguments) {
}
});
flutter部分:
class _MyHomePageState extends State<MyHomePage> {
static const eventChannel = const EventChannel('ace_demo_android_flutter');
String _result = '';
StreamSubscription _streamSubscription;
@override
void initState() {
super.initState();
_getEventResult();
}
@override
void dispose() {
super.dispose();
if (_streamSubscription != null) {
_streamSubscription.cancel();
}
}
_getEventResult() async {
try {
_streamSubscription =
eventChannel.receiveBroadcastStream().listen((data) {
setState(() {
_result = data;
});
});
} on PlatformException catch (e) {
setState(() {
_result = "event get data err: '${e.message}'.";
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(widget.title)),
body: Center(
child: Text('${_result}',
style: TextStyle(color: Colors.blueAccent, fontSize: 18.0))),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter, child: Icon(Icons.arrow_back)));
}
}
1.2. 3 BasicMessageChannel
BasicMessageChannel 主要传递字符串和半结构化的数据交互;其编解码有多种类型,在使用时建议 Android 与 Flutter 两端一致;
1).BinaryCodec:基本二进制编码类型;
2).StringCodec:字符串与二进制之间的编码类型;
3).JSONMessageCodec:Json 与二进制之间的编码类型;
4).StandardMessageCodec:默认编码类型,包括基础数据类型、二进制数据、列表、字典等与二进制之间等编码类型;
Flutter -> Android
用途: Flutter 端向 Android 端发送 send 数据请求,Android 端接收到后通过 replay 向 Flutter 端发送消息,从而完成一次消息交互;
// Flutter 端
static const basicChannel = BasicMessageChannel<String>('ace_demo_android_flutter', StringCodec());
@override
void initState() {
super.initState();
_getBasicResult();
}
_getBasicResult() async {
final String reply = await basicChannel.send('ace_demo_user');
setState(() {
_result = reply;
});
}
\// Android 端
final BasicMessageChannel channel = new BasicMessageChannel<String> (flutterView, CHANNEL, StringCodec.INSTANCE);
channel.setMessageHandler(new BasicMessageChannel.MessageHandler() {
@Override
public void onMessage(Object o, BasicMessageChannel.Reply reply) {
reply.reply("我来自 " + TAG +" !! 使用的是 BasicMessageChannel 方式");
}
});
Android -> Flutter
根据上述继续由 Android 端主动向 Flutter 端发送数据,Android 通过 send 向 Flutter 发送数据请求,Flutter 通过 setMessageHandler 接收后向 Android 端 return 返回结果,再由 Android 回调接收,从而完成一次数据交互;
public void send(T message) {
this.send(message, (BasicMessageChannel.Reply)null);
}
public void send(T message, BasicMessageChannel.Reply<T> callback) {
this.messenger.send(this.name, this.codec.encodeMessage(message), callback == null ? null : new BasicMessageChannel.IncomingReplyHandler(callback));
}
分析源码 send 有两个构造函数,有两个参数的构造方法用来接收 Flutter 回调的数据;
// Flutter 端
static const basicChannel = BasicMessageChannel<String>('ace_demo_android_flutter', StringCodec());
@override
void initState() {
super.initState();
_getBasicResult();
}
_getBasicResult() async {
final String reply = await
channel.setMessageHandler((String message) async {
print('Flutter Received: ${message}');
setState(() {
_result = message;
});
return "{'name': '我不是老猪', 'gender': 1}";
});
}
// Android 端
channel.setMessageHandler(new BasicMessageChannel.MessageHandler() {
@Override
public void onMessage(Object o, BasicMessageChannel.Reply reply) {
reply.reply("我来自 " + TAG +" !! 使用的是 BasicMessageChannel 方式");
channel.send("ace_demo_user");
//channel.send("ace_demo_user", new BasicMessageChannel.Reply() {
// @Override
// public void reply(Object o) {
// Intent intent = new Intent();
// intent.putExtra("data", o!=null?o.toString():"");
// setResult(REQUEST_CODE, intent);
// MyFlutterViewActivity.this.finish();
// }
//});
}
});
3者的通信原理总结:
消息信使:BinaryMessenger
以ByteBuffer为数据载体,然后通过BinaryMessenger来发送与接收数据。整体设计如下。
在Android侧,BinaryMessenger是一个接口,在FlutterView中实现了该接口,在BinaryMessenger的方法中通过JNI来与系统底层沟通。在Flutter侧,BinaryMessenger是一个类,该类的作用就是与类window沟通,而类window才真正与系统底层沟通。
虽然三种Channel各有用途,但是他们与Flutter通信的工具却是相同的,均为BinaryMessager。
BinaryMessenger是Platform端与Flutter端通信的工具,其通信使用的消息格式为二进制格式数据。当我们初始化一个Channel,并向该Channel注册处理消息的Handler时,实际上会生成一个与之对应的BinaryMessageHandler,并以channel name为key,注册到BinaryMessenger中。当Flutter端发送消息到BinaryMessenger时,BinaryMessenger会根据其入参channel找到对应的BinaryMessageHandler,并交由其处理。
Binarymessenger在Android端是一个接口,其具体实现为FlutterNativeView。而其在iOS端是一个协议,名称为FlutterBinaryMessenger,FlutterViewController遵循了它。
Binarymessenger并不知道Channel的存在,它只和BinaryMessageHandler打交道。而Channel和BinaryMessageHandler则是一一对应的。由于Channel从BinaryMessageHandler接收到的消息是二进制格式数据,无法直接使用,故Channel会将该二进制消息通过Codec(消息编解码器)解码为能识别的消息并传递给Handler进行处理。
当Handler处理完消息之后,会通过回调函数返回result,并将result通过编解码器编码为二进制格式数据,通过BinaryMessenger发送回Flutter端。
1.3. Platform Channel的代码运行在什么线程
在文章《深入理解Flutter引擎线程模型》中提及,Flutter Engine自己不创建线程,其线程的创建于管理是由enbedder提供的,并且Flutter Engine要求Embedder提供四个Task Runner,分别是Platform Task Runner,UI Task Runner,GPU Task Runner和IO Task Runner。
实际上,在Platform侧执行的代码运行在Platform Task Runner中,而在Flutter app侧的代码则运行在UI Task Runner中。在Android和iOS平台上,Platform Task Runner跑在主线程上。因此,不应该在Platform端的Handler中处理耗时操作。
1.4. Platform Channel是否线程安全
Platform Channel并非是线程安全的,这一点在官方的文档也有提及。Flutter Engine中多个组件是非线程安全的,故跟Flutter Engine的所有交互(接口调用)必须发生在Platform Thread。故我们在将Platform端的消息处理结果回传到Flutter端时,需要确保回调函数是在Platform Thread(也就是Android和iOS的主线程)中执行的。
1.5. 是否支持大内存数据块的传递
Platform Channel实际上是支持大内存数据块的传递,当需要传递大内存数据块时,需要使用BasicMessageChannel以及BinaryCodec。而整个数据传递的过程中,唯一可能出现数据拷贝的位置为native二进制数据转化为Dart语言二进制数据。若二进制数据大于阈值时(目前阈值为1000byte)则不会拷贝数据,直接转化,否则拷贝一份再转化。
1.6. 如何将Platform Channel原理应用到开发工作中
实际上Platform Channel的应用场景非常多,我们这里举一个例子:
在平常的业务开发中,我们需要使用到一些本地图片资源,但是Flutter端是无法使用Platform端已存在的图片资源的。当Flutter端需要使用一个Platform端已有的图片资源时,只有将该图片资源拷贝一份到Flutter的Assert目录下才能使用。实际上,让Flutter端使用Platform端的资源并不是一件难事。
我们可以使用BasicMessageChannel来完成这个工作。Flutter端将图片资源名name传递给Platform端,Native端使用Platform端接收到name后,根据name定位到图片资源,并将该图片资源以二进制数据格式,通过BasicMessageChannel,传递回Flutter端。
2. 混合交互的缺点:
1). android 还是得写代码,ios端还是得写代码, 要维护!
2). 运行多个Flutter实例,或在屏幕局部上运行Flutter可能会导致不可以预测的行为;
3). 在后台模式使用Flutter的能力还在开发中(目前不支持);
4).将Flutter库打包到另一个可共享的库或将多个Flutter库打包到同一个应用中,都不支持;
5).添加到应用在Android平台的实现基于 FlutterPlugin 的 API,一些不支持 FlutterPlugin 的插件可能会有不可预知的行为。
混合开发的2种情况
- .flutter项目,里面用android或者ios
2).android项目中,加入flutter进行混合
3. 插件Pigeon
Pigeon工具生成的代码是基于BasicMessageChannel实现通信的。
- 生成的代码是 Java/Objective-C,但是由于 Kotlin 可以调用 Java,Swift 可以调用 Objective-C
4.fluttter 嵌套原生的view视图
Flutter提供了一个平台视图(Platform View)的概念。它提供了一种方法,允许开发者在Flutter里面嵌入原生系统(Android和iOS)的视图,并加入到Flutter的渲染树中,实现与Flutter一致的交互体验。
嵌套原生的view:
由于Flutter与原生渲染方式完全不同,因此转换不同的渲染数据会有较大的性能开销。如果在一个界面上同时实例化多个原生控件,就会对性能造成非常大的影响,所以我们要避免在使用Flutter控件也能实现的情况下去使用内嵌平台视图。
一方面, 需要分别在Android和iOS端写大量的适配桥接代码,违背了跨平台技术的本意,也增加了后续的维护成本;
另一方面, 毕竟除去地图、WebView、相机等涉及底层方案的特殊情况外,大部分原生代码能够实现的UI效果,完全可以用Flutter实现。
平台视图PlatformView
PlatformView跟add to app怎么选
FlutterView的创建:
public class MyFlutterViewActivity extends FlutterFragmentActivity {
private static final String CHANNEL = "ace_demo_android_flutter";
private static final String TAG = "MyFlutterViewActivity";
private static final int REQUEST_CODE = 1000;
FlutterView flutterView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_flutter);
DisplayMetrics outMetrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(outMetrics);
int widthPixels = outMetrics.widthPixels;
int heightPixels = outMetrics.heightPixels;
flutterView = Flutter.createView(MyFlutterViewActivity.this, getLifecycle(), "/");
FrameLayout.LayoutParams layout = new FrameLayout.LayoutParams(widthPixels, heightPixels);
addContentView(flutterView, layout);
在Flutter中嵌套原生视图: 通过平台视图,我们就可以将一个原生控件包装成Flutter控件,嵌入到Flutter页面中,
就像使用一个普通的Widget一样。把原生视图组装成一个 Flutter 控件
一次典型的平台视图使用过程与方法通道类似:
首先,由作为客户端的Flutter,通过向原生视图的Flutter封装类(在iOS和Android平台分别是UIKitView和AndroidView)传入视图标识符,用于发起原生视图的创建请求;
然后,原生代码侧将对应原生视图的创建交给平台视图工厂(PlatformViewFactory)实现;
最后,在原生代码侧将视图标识符与平台视图工厂进行关联注册,让Flutter发起的视图创建请求可以直接找到对应的视图创建工厂。
架构原理图:
实现步骤:
1. android 端,把view封装一个.PlatformView !
2. android端创建一个工厂
3. android通过高方法通道, 绑定注册view
flutter端: 直接返回一个绑定方法通道标识的view,AndroidView!
案例demo2:
问题: flutter如何实现原生视图的接口调用?
flutter端:
class SampleView extends StatelessWidget {
@override
Widget build(BuildContext context) {
// 使用 Android 平台的 AndroidView,传入唯一标识符 sampleView
if (defaultTargetPlatform == TargetPlatform.android) {
return AndroidView(viewType: 'sampleView');
} else {
// 使用 iOS 平台的 UIKitView,传入唯一标识符 sampleView
return UiKitView(viewType: 'sampleView');
}
}
}
Scaffold(
backgroundColor: Colors.yellowAccent,
body: Container(width: 200, height:200,
child: SampleView(controller: controller)
));
如何在原生系统实现接口?
android端:
// 视图工厂类
class SampleViewFactory extends PlatformViewFactory {
private final BinaryMessenger messenger;
// 初始化方法
public SampleViewFactory(BinaryMessenger msger) {
super(StandardMessageCodec.INSTANCE);
messenger = msger;
}
// 创建原生视图封装类,完成关联
@Override
public PlatformView create(Context context, int id, Object obj) {
return new SimpleViewControl(context, id, messenger);
}
}
// 原生视图封装类
class SimpleViewControl implements PlatformView {
private final View view;// 缓存原生视图
// 初始化方法,提前创建好视图
public SimpleViewControl(Context context, int id, BinaryMessenger messenger) {
view = new View(context);
view.setBackgroundColor(Color.rgb(255, 0, 0));
}
// 返回原生视图
@Override
public View getView() {
return view;
}
// 原生视图销毁回调
@Override
public void dispose() {
}
}
protected void onCreate(Bundle savedInstanceState) {
...
Registrar registrar = registrarFor("samples.chenhang/native_views");// 生成注册类
SampleViewFactory playerViewFactory = new SampleViewFactory(registrar.messenger());// 生成视图工厂
registrar.platformViewRegistry().registerViewFactory("sampleView", playerViewFactory);// 注册视图工厂
}
5.如何在程序运行时,动态地调整原生视图的样式?
flutter端我们会用到原生视图的一个初始化属性,即 onPlatformViewCreated
原生端: 会用到MethodChannel, 方法通道
实现原理:
flutter:
1.创建一个controller
- 初始化的时候在controller中创建方法通道
3.在controller中, invoke通道方法(声明方法)
android端实现: 方法通道, 动态调用flutter的方法
案例demo3:
flutter端的代码
// 原生视图控制器
class NativeViewController {
MethodChannel _channel;
// 原生视图完成创建后,通过 id 生成唯一方法通道
onCreate(int id) {
_channel = MethodChannel('samples.chenhang/native_views_$id');
}
// 调用原生视图方法,改变背景颜色
Future<void> changeBackgroundColor() async {
return _channel.invokeMethod('changeBackgroundColor');
}
}
// 原生视图 Flutter 侧封装,继承自 StatefulWidget
class SampleView extends StatefulWidget {
const SampleView({
Key key,
this.controller,
}) : super(key: key);
// 持有视图控制器
final NativeViewController controller;
@override
State<StatefulWidget> createState() => _SampleViewState();
}
class _SampleViewState extends State<SampleView> {
// 根据平台确定返回何种平台视图
@override
Widget build(BuildContext context) {
if (defaultTargetPlatform == TargetPlatform.android) {
return AndroidView(
viewType: 'sampleView',
// 原生视图创建完成后,通过 onPlatformViewCreated 产生回调
onPlatformViewCreated: _onPlatformViewCreated,
);
} else {
return UiKitView(viewType: 'sampleView',
// 原生视图创建完成后,通过 onPlatformViewCreated 产生回调
onPlatformViewCreated: _onPlatformViewCreated
);
}
}
// 原生视图创建完成后,调用 control 的 onCreate 方法,传入 view id
_onPlatformViewCreated(int id) {
if (widget.controller == null) {
return;
}
widget.controller.onCreate(id);
}
}
Android 端的代码
class SimpleViewControl implements PlatformView, MethodCallHandler {
private final MethodChannel methodChannel;
...
public SimpleViewControl(Context context, int id, BinaryMessenger messenger) {
...
// 用 view id 注册方法通道
methodChannel = new MethodChannel(messenger, "samples.chenhang/native_views_" + id);
// 设置方法通道回调
methodChannel.setMethodCallHandler(this);
}
// 处理方法调用消息
@Override
public void onMethodCall(MethodCall methodCall, MethodChannel.Result result) {
// 如果方法名完全匹配
if (methodCall.method.equals("changeBackgroundColor")) {
// 修改视图背景,返回成功
view.setBackgroundColor(Color.rgb(0, 0, 255));
result.success(0);
}else {
// 调用方发起了一个不支持的 API 调用
result.notImplemented();
}
}
...
}