Flutter Platform Channel 使用與源碼分析

級別: ★★☆☆☆
標籤:「Flutter」「Platform」「Channel」
做者: snow
審校: QiShare團隊php


1. 爲何要有 PlatformChannel

一、若是 Flutter 要獲取設備的電量信息怎麼辦?
二、若是 Flutter 要實時監控網絡狀態怎麼辦?
java

因爲 Flutter 特色以下:android

Flutter is Google’s UI toolkit for building beautiful, natively compiled applications for mobile, web, and desktop from a single codebase.c++

一、Flutter 是一個跨平臺的 UI 庫,專一於構建高效的 UI。
二、多平臺的支持,下圖是 Flutter 目前支持的平臺,每一個平臺的都有本身的平臺特性。
git


基於以上兩點,目前 Flutter 若是要和平臺相關的部分通訊須要有一個通道即 PlatformChannel。github

2. 架構圖

3. PlatformChannel 類型

  • BasicMessageChannel:用於數據傳遞。platform 和 dart 可互相傳遞數據(asynchronous message passing)
  • MethodChannel:用於傳遞方法調用。platform 和 dart 可互相調用方法(asynchronous method calls)
  • EventChannel:用於數據流通訊。創建鏈接以後,platform 發送消息,dart 接收消息(event streams)

三種類型的 channel 都定義在 platform_channel.dart 中,從源碼中能夠看到三種 channel 都用到了如下三個屬性。web

  • name:String 類型,表示 channel 的名字,全局惟一(The logical channel on which communication happens)
  • codec:MessageCodec 類型,消息的編碼解碼器(The message codec used by this channel)
  • binaryMessenger:BinaryMessenger類型,用於發送數據(The messenger used by this channel to send platform messages)

3.1 channel name

channel 的名字,每一個 Flutter 應用可能有多個 channel,可是每一個 channel 必須有一個惟一的名字。shell

3.2 codec

codec 用來對數據編碼解碼,以便兩端能夠正確讀取數據。 微信

3.3 binaryMessenger

用於發送數據網絡

4. PlatformChannel 使用

4.1 MethodChannel

  • Dart 調用 Android 方法

method_channel_page.dart 主要代碼

第一步
static const methodChannel = MethodChannel("method_channel_sample");

第二步  
Future<dynamic> getUserInfo(String method, {String userName}) async {
  return await methodChannel.invokeMethod(method, userName);
}

第三步    
MaterialButton(
  color: Colors.blue,
  textColor: Colors.white,
  child: new Text('獲取 snow 用戶信息'),
  onPressed: () {
    getUserInfo("getInfo", userName: "snow")
      ..then((result) {
        setState(() {
          messageFromNative = result;
        });
      });
  },
),
複製代碼

MainActivity.java 主要代碼

private void addMethodChannel() {
    mMethodChannel = new MethodChannel(getFlutterView(), "method_channel_sample");
    mMethodChannel.setMethodCallHandler((methodCall, result) -> {

        String method = methodCall.method;

        if ("getInfo".equals(method)) {

            String userName = (String) methodCall.arguments;

            if (userName.equals("rocx")) {
                String user = "name:rocx, age:18";
                result.success(user);
            } else {
                result.success("user not found");

                invokeSayHelloMethod();
            }
        }

    });
}
複製代碼

從以上代碼能夠看出
Dart 調用 Android 代碼分三步。首先在 Dart 端定義 MethodChannel 名字爲 method_channel_sample。而後定義getUserInfo方法,傳入要調用的方法名和參數。最後點擊按鈕執行方法,獲取用戶信息。
在 Android 端定一個 MethodChannel 名字和 Dart 端保持一致。設置 MethodCallHandler。當調用的是getInfo方法時,根據參數返回信息。

  • Android 調用 Dart 方法

MainActivity.java 主要代碼

private void invokeSayHelloMethod() {
    mMethodChannel.invokeMethod("sayHello", "", new MethodChannel.Result() {
        @Override
        public void success(Object o) {

            Toast.makeText(MainActivity.this, o.toString(), Toast.LENGTH_LONG).show();
        }

        @Override
        public void error(String s, String s1, Object o) {

        }

        @Override
        public void notImplemented() {

        }
    });
}
複製代碼

method_channel_page.dart 主要代碼

Future<dynamic> addHandler(MethodCall call) async {
  switch (call.method) {
    case "sayHello":
      return "Hello from Flutter";
      break;
  }
}

@override
void initState() {
  super.initState();

  methodChannel.setMethodCallHandler(addHandler);
}
複製代碼

從代碼能夠看出,在 Dart 端設置 MethodCallHandler 而後在 Android 端調用便可。

4.2 BasicMessageChannel

  • Dart 向 Android 發送消息

basic_message_channel_page.dart 主要代碼

第一步
static const basicMessageChannel = BasicMessageChannel(
      "basic_message_channel_sample", StandardMessageCodec());

第二步
Future<dynamic> sayHelloToNative(String message) async {
  String reply = await basicMessageChannel.send(message);

  setState(() {
    msgReplyFromNative = reply;
  });

  return reply;
}

第三步
MaterialButton(
  color: Colors.blue,
  textColor: Colors.white,
  child: new Text('say hello to native'),
  onPressed: () {
    sayHelloToNative("hello");
  },
),

複製代碼

MainActivity.java 主要代碼

private void addBasicMessageChannel() {
    mBasicMessageChannel = new BasicMessageChannel<>(getFlutterView(), "basic_message_channel_sample", StandardMessageCodec.INSTANCE);
    mBasicMessageChannel.setMessageHandler((object, reply) -> {

        reply.reply("receive " + object.toString() + " from flutter");

        mBasicMessageChannel.send("native say hello to flutter");
    });
}

複製代碼

從以上代碼能夠看出
Dart 向 Android 發送消息依然分爲三步。首先在 Dart 端定義 BasicMessageChannel 名字爲 basic_message_channel_sample。而後定義發送消息的方法sayHelloToNative。最後點擊按鈕向 Android 端發送消息。
在 Android 端定一個 BasicMessageChannel 名字和 Dart 端保持一致。設置 MethodCallHandler。當收到消息時發一個回覆。

  • Android 向 Dart 發送消息

MainActivity.java 主要代碼

mBasicMessageChannel.send("native say hello to flutter");
複製代碼

basic_message_channel_page.dart 主要代碼

Future<dynamic> addHandler(Object result) async {
  setState(() {
    msgReceiveFromNative = result.toString();
  });
}

void addMessageListener() {
  basicMessageChannel.setMessageHandler(addHandler);
}

@override
void initState() {
  super.initState();
  addMessageListener();
}

複製代碼

從代碼能夠看出,在 Dart 端設置 MessageHandler 而後在 Android 端直接發送消息便可。

4.3 EventChannel

event_channel_page.dart 主要代碼

第一步
static const eventChannel = EventChannel("event_channel_sample");

void _onEvent(Object event) {
  setState(() {
    if (_streamSubscription != null) {
      eventMessage = event.toString();
    }
  });
}

void _onError(Object error) {
  setState(() {
    if (_streamSubscription != null) {
      eventMessage = "error";
    }
  });
}

@override
void initState() {
  super.initState();
  eventMessage = "";
  第二步
  _streamSubscription = eventChannel
      .receiveBroadcastStream()
      .listen(_onEvent, onError: _onError);
}

複製代碼

MainActivity.java 主要代碼

private void addEventChannel() {
    mEventChannel = new EventChannel(getFlutterView(), "event_channel_sample");
    mEventChannel.setStreamHandler(new EventChannel.StreamHandler() {
        @Override
        public void onListen(Object o, EventChannel.EventSink eventSink) {

            task = new TimerTask() {
                @Override
                public void run() {
                    runOnUiThread(() -> eventSink.success("i miss you " + System.currentTimeMillis()));

                }
            };
            timer = new Timer();
            timer.schedule(task, 2000, 3000);
        }

        @Override
        public void onCancel(Object o) {
            task.cancel();
            timer.cancel();
            task = null;
            timer = null;
        }
    });
}
複製代碼

Dart 接受 Android stream event。首先在 Dart 端定義 EventChannel 名字爲 event_channel_sample。而後設置receiveBroadcastStream監聽,當 Android 端有消息發過來會回調_onEvent方法。
在 Android 端啓動一個定時器,每隔3s向 Dart 端發送一次消息。

4.4 總結

以下圖,在 Dart 與 Platform 通訊過程當中,經過 channel name 找到對方,而後把消息經過 codec 進行編解碼,最後經過 binaryMessenger 進行發送。

5. 源碼分析-以 MethodChannel 爲例

5.1 調用 MethodChannel 的 invokeMethod 方法,會調用到 binaryMessenger.send 方法。即 binaryMessenger.send 傳入 channel name 和編碼好的參數。
@optionalTypeArgs
  Future<T> invokeMethod<T>(String method, [ dynamic arguments ]) async {
    assert(method != null);
    final ByteData result = await binaryMessenger.send(
      name,
      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;
  }
複製代碼
5.2 binary_messenger.dart 的 send 方法會調用當前對象的 _sendPlatformMessage 方法,最終會調用 window.sendPlatformMessage 方法。
@override
  Future<ByteData> send(String channel, ByteData message) {
    final MessageHandler handler = _mockHandlers[channel];
    if (handler != null)
      return handler(message);
    return _sendPlatformMessage(channel, message);
  }
複製代碼
Future<ByteData> _sendPlatformMessage(String channel, ByteData message) {
    final Completer<ByteData> completer = Completer<ByteData>();
    // ui.window is accessed directly instead of using ServicesBinding.instance.window
    // because this method might be invoked before any binding is initialized.
    // This issue was reported in #27541. It is not ideal to statically access
    // ui.window because the Window may be dependency injected elsewhere with
    // a different instance. However, static access at this location seems to be
    // the least bad option.
    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;
  }
複製代碼
5.3 在 window.dart 中又調用了 native 方法 _sendPlatformMessage。
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';
複製代碼
5.4 接下來進入 engine 中的 window.cc,能夠看到最終調用的是 dart_state->window()->client()->HandlePlatformMessage。
void Window::RegisterNatives(tonic::DartLibraryNatives* natives) {
  natives->Register({
      {"Window_defaultRouteName", DefaultRouteName, 1, true},
      {"Window_scheduleFrame", ScheduleFrame, 1, true},
      {"Window_sendPlatformMessage", _SendPlatformMessage, 4, true},
      {"Window_respondToPlatformMessage", _RespondToPlatformMessage, 3, true},
      {"Window_render", Render, 2, true},
      {"Window_updateSemantics", UpdateSemantics, 2, true},
      {"Window_setIsolateDebugName", SetIsolateDebugName, 2, true},
      {"Window_reportUnhandledException", ReportUnhandledException, 2, true},
      {"Window_setNeedsReportTimings", SetNeedsReportTimings, 2, true},
  });
}
複製代碼
void _SendPlatformMessage(Dart_NativeArguments args) {
  tonic::DartCallStatic(&SendPlatformMessage, args);
}
複製代碼
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());
    dart_state->window()->client()->HandlePlatformMessage(
        fml::MakeRefCounted<PlatformMessage>(
            name, std::vector<uint8_t>(buffer, buffer + data.length_in_bytes()),
            response));
  }

  return Dart_Null();
}
複製代碼

window.cc 源碼

5.5 咱們進入 window.h 中找到 client 實際上是 WindowClient。
WindowClient* client() const { return client_; }
複製代碼

window.h 源碼

5.6 在 runtime_controller.h 中能夠看到 RuntimeController 是 WindowClient 的實際實現,調用的是 RuntimeController 的 HandlePlatformMessage 方法。
class RuntimeController final : public WindowClient {

...
  // |WindowClient|
  void HandlePlatformMessage(fml::RefPtr<PlatformMessage> message) override;
...
}
複製代碼

runtime_controller.h 源碼

5.7 在 runtime_controller.cc 中,HandlePlatformMessage 調用了 client_ 的 HandlePlatformMessage 方法,client_ 實際是代理對象 RuntimeDelegate。
void RuntimeController::HandlePlatformMessage(
    fml::RefPtr<PlatformMessage> message) {
  client_.HandlePlatformMessage(std::move(message));
}
複製代碼
RuntimeDelegate& p_client
複製代碼

runtime_controller.cc 源碼

5.8 engine.h 是 RuntimeDelegate 的具體實現類。
class Engine final : public RuntimeDelegate {
...
  // |RuntimeDelegate|
  void HandlePlatformMessage(fml::RefPtr<PlatformMessage> message) override;
...  
}  
複製代碼

engine.h 源碼

5.9 engine.cc 中調用了 delegate_ 的 OnEngineHandlePlatformMessage 方法。
void Engine::HandlePlatformMessage(fml::RefPtr<PlatformMessage> message) {
  if (message->channel() == kAssetChannel) {
    HandleAssetPlatformMessage(std::move(message));
  } else {
    delegate_.OnEngineHandlePlatformMessage(std::move(message));
  }
}
複製代碼

engine.cc 源碼

5.10 shell.h 是 Engine 的代理。
// |Engine::Delegate|
  void OnEngineHandlePlatformMessage( fml::RefPtr<PlatformMessage> message) override;
複製代碼

shell.h 源碼

5.11 調用流程又進入了 shell.cc 的 HandleEngineSkiaMessage 方法,把消費放到 TaskRunner 中。
// |Engine::Delegate|
void Shell::OnEngineHandlePlatformMessage(
    fml::RefPtr<PlatformMessage> message) {
  FML_DCHECK(is_setup_);
  FML_DCHECK(task_runners_.GetUITaskRunner()->RunsTasksOnCurrentThread());

  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));
        }
      });
}
複製代碼

shell.cc 源碼

5.12 當 task 執行是會調用 platform_view_android.h 的 HandlePlatformMessage 方法。
class PlatformViewAndroid final : public PlatformView {
...
  // |PlatformView|
  void HandlePlatformMessage( fml::RefPtr<flutter::PlatformMessage> message) override;
...
}
複製代碼

platform_view_android.h 源碼

5.13 在 platform_view_android.cc 的 HandlePlatformMessage 中,開始經過 jni 調用 java 端的方法,java_channel 即要找的 channel。
// |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()) {
    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.
    FlutterViewHandlePlatformMessage(env, view.obj(), java_channel.obj(),
                                     message_array.obj(), response_id);
  } else {
    message = nullptr;

    // This call can re-enter in InvokePlatformMessageXxxResponseCallback.
    FlutterViewHandlePlatformMessage(env, view.obj(), java_channel.obj(),
                                     nullptr, response_id);
  }
}
複製代碼

platform_view_android.cc 源碼

5.14 在 platform_view_android_jni.cc 中能夠看到 g_handle_platform_message_method 就是 FindClass("io/flutter/embedding/engine/FlutterJNI") 類的 handlePlatformMessage 方法。至此 engine 代碼執行結束。
static jmethodID g_handle_platform_message_method = nullptr;
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));
}
複製代碼
g_handle_platform_message_method =
      env->GetMethodID(g_flutter_jni_class->obj(), "handlePlatformMessage",
                       "(Ljava/lang/String;[BI)V");
複製代碼
g_flutter_jni_class = new fml::jni::ScopedJavaGlobalRef<jclass>(
      env, env->FindClass("io/flutter/embedding/engine/FlutterJNI"));
  if (g_flutter_jni_class->is_null()) {
    FML_LOG(ERROR) << "Failed to find FlutterJNI Class.";
    return false;
  }
複製代碼

platform_view_android_jni.cc 源碼

5.15 在 FlutterJNI 中調用了 this.platformMessageHandler.handleMessageFromDart 方法。也就是 DartMessenger 的 handleMessageFromDart 方法。
private void handlePlatformMessage(@NonNull String channel, byte[] message, int replyId) {
        if (this.platformMessageHandler != null) {
            this.platformMessageHandler.handleMessageFromDart(channel, message, replyId);
        }

    }
複製代碼
5.16 DartMessenger 中 messageHandlers 經過 channel 名找到對應的 handler 進行處理,這個 handler 就是咱們在 java 代碼裏經過 channel 設置的,整個調用流程完成。
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);
                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);
        }

    }

複製代碼

Demo 地址

flutter_platform_channel 使用

參考資源

Writing custom platform-specific code
platform channel 官方示例
深刻理解Flutter Platform Channel


瞭解更多iOS及相關新技術,請關注咱們的公衆號:

小編微信:可加並拉入《QiShare技術交流羣》。

關注咱們的途徑有:
QiShare(簡書)
QiShare(掘金)
QiShare(知乎)
QiShare(GitHub)
QiShare(CocoaChina)
QiShare(StackOverflow)
QiShare(微信公衆號)

推薦文章:
開發沒切圖怎麼辦?矢量圖標(iconFont)上手指南
DarkMode、WKWebView、蘋果登陸是否必須適配?
iOS 接入 Google、Facebook 登陸(二)
iOS 接入 Google、Facebook 登陸(一)
Nginx 入門實戰 iOS中的3D變換(二)
iOS中的3D變換(一)
WebSocket 雙端實踐(iOS/ Golang)
今天咱們來聊一聊WebSocket(iOS/Golang)
奇舞團安卓團隊——aTaller
奇舞週刊

相關文章
相關標籤/搜索