目錄:
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++源碼,
架構圖:
- 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();
}
}
...
}