前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Flutter与Native通信 - PlatformChannel源码分析

Flutter与Native通信 - PlatformChannel源码分析

原创
作者头像
DSoon
修改2018-12-13 20:14:54
3.4K0
修改2018-12-13 20:14:54
举报
文章被收录于专栏:Flutter知识集Flutter知识集

Flutter与Native通信 - PlatformChannel源码分析

Flutter是一个跨平台的方案,在UI、触控及基本的网络请求上已经基本做到平台无关,但是在某些平台特性的功能上,还是必须要对不同的平台做处理。这就涉及到与Native的通信。

Flutter提供了一套Platform Channel的机制,来满足与Native通信的功能要求。

PlatformChannel功能简介

Platf Channel分为BasicMessageChannel、MethodChannel以及EventChannel三种。其各自的主要用途如下:

  • BasicMessageChannel: 用于传递数据。Flutter与原生项目的资源是不共享的,可以通过BasicMessageChannel来获取Native项目的图标等资源。
  • MethodChannel: 传递方法调用。Flutter主动调用Native的方法,并获取相应的返回值。比如获取系统电量,发起Toast等调用系统API,可以通过这个来完成。
  • EventChannel: 传递事件。这里是Native将事件通知到Flutter。比如Flutter需要监听网络情况,这时候MethodChannel就无法胜任这个需求了。EventChannel可以将Flutter的一个监听交给Native,Native去做网络广播的监听,当收到广播后借助EventChannel调用Flutter注册的监听,完成对Flutter的事件通知。

其实可以看到,无论传方法还是传事件,其本质上都是数据的传递,不过上层包的一些逻辑不同而已。所以这三个Channel的通信实现基本是一致的,只是EventChannel在处理消息处理时会有一些特殊的附加逻辑,这个后文会做分析。

MethodChannel的用法(Android)

几个Channel的用法都很简单,就简单介绍MethodChannel的基本用法,之后MethodChannel为例分析一下Flutter是怎么和Native做到通信的。

用官方的Demo,Flutter获取设备电量。

在Native项目的Activity中,注册插件:

代码语言:txt
复制
    // Flutter在Native上是用一个SurfaceView承载的,getFlutterView()获取到这个View
    // BATTERY_CHANNEL是channel名称,必须唯一一般用包名加功能命名,如:"com.qq.fm/battery"
    new MethodChannel(getFlutterView(), BATTERY_CHANNEL).setMethodCallHandler(
        new MethodCallHandler() {
          @Override
          public void onMethodCall(MethodCall call, Result result) {
            if (call.method.equals("getBatteryLevel")) {
              int batteryLevel = getBatteryLevel(); // 调系统API获取电量

              if (batteryLevel != -1) {
                result.success(batteryLevel);
              } else {
                result.error("UNAVAILABLE", "Battery level not available.", null);
              }
            } else {
              result.notImplemented();
            }
          }
        }
    );

然后在Flutter项目中,直接通过ChannelName实例化MethodChannel,然后调用方法即可。

代码语言:txt
复制
    MethodChannel methodChannel =MethodChannel('com.qq.fm/battery');
    final int result = await methodChannel.invokeMethod('getBatteryLevel');

可以看到,这里就是在Native项目中注册一下MethodChannel,然后就可以在Flutter中用同样的ChannelName实例化一个MethodChannel,然后发起调用。

需要补充的是invokeMethod方法除了函数名,还可以带上参数传递过去。Native层则是通过Result类的相关方法,将结果回传给Flutter。

源码分析

Android平台逻辑

我们先看一下Native项目中那些代码到底做了什么事情。

代码语言:txt
复制
    // MethodChannel.java
    
    // 平平无奇的构造方法(默认codec是StandardMethodCodec,先可以简单理解codec就是一种数据编码方式)
    public MethodChannel(BinaryMessenger messenger, String name, MethodCodec codec) {
        // ...
        
        this.messenger = messenger;
        this.name = name;
        this.codec = codec;
    }
    // 设置handler时,会将name和handler一起传给messager。
    public void setMethodCallHandler(@Nullable MethodChannel.MethodCallHandler handler) {
        this.messenger.setMessageHandler(this.name, handler == null ? null : new MethodChannel.IncomingMethodCallHandler(handler));
    }
    
    // IncomingMethodCallHandler实现了BinaryMessageHandler接口,会将message处理转化为MethodCall后传给handler。
    private final class IncomingMethodCallHandler implements BinaryMessageHandler {
        // ...
        public void onMessage(ByteBuffer message, final BinaryReply reply) {
            // ...
        }
    }

回看一下前面的实际代码,messenger其实就是FlutterViewBinaryMessenger在Android里是一个接口,FlutterViewFlutterNativeView实现了它,实际上FlutterView只是wrap了一下FlutterNativeView,所以只用关注FlutterNativeView就好。

代码语言:txt
复制
    // FlutterNativeView.java
    private final Map<String, BinaryMessageHandler> mMessageHandlers;
    
    public void setMessageHandler(String channel, BinaryMessageHandler handler) {
        if (handler == null) {
            this.mMessageHandlers.remove(channel);
        } else {
            this.mMessageHandlers.put(channel, handler);
        }

    }

mMessageHandlers其实是一个HashMap,key就是ChannelName(这也是为什么ChannelName必须要唯一的原因)。

平台的逻辑就是这些了,好像没什么和通信有关的,只是维护了一个ChannelName和对应Handler的Map。

Flutter逻辑

再看看Flutter发起调用时,做了什么事情。

代码语言:txt
复制
    // platform_channel.dart
    
    // 构造方法没啥好看的
    const MethodChannel(this.name, [this.codec = const StandardMethodCodec()]);
    
    Future<dynamic> invokeMethod(String method, [dynamic arguments]) async {
        assert(method != null);
        final dynamic result = await BinaryMessages.send( // focus
          name,
          codec.encodeMethodCall(MethodCall(method, arguments)),
        );
        if (result == null)
          throw MissingPluginException('No implementation found for method $method on channel $name');
        return codec.decodeEnvelope(result);
    }

可以看到,invoke里面,主要是把方法名和参数通过codec转化为二进制数据,通过BinaryMessages send出去,并等待这个方法返回result。

跟踪到BinaryMessages:

代码语言:txt
复制
  // platform_messages.dart
  
  static Future<ByteData> send(String channel, ByteData message) {
    final _MessageHandler handler = _mockHandlers[channel];
    if (handler != null) // (1)
      return handler(message);
    return _sendPlatformMessage(channel, message); // (2)
  }
  // window.dart
  
  void sendPlatformMessage(String name,
                           ByteData data,
                           PlatformMessageResponseCallback callback) {
    final String error = _sendPlatformMessage(name, _zonedPlatformMessageResponseCallback(callback), data);
    if (error != null)
      throw new Exception(error);
  }
  
  String _sendPlatformMessage(String name,
                              PlatformMessageResponseCallback callback,
                              ByteData data) native 'Window_sendPlatformMessage';

注释(1)处,_mockHandlers是一个<String, _MessageHandler>的Map,可以在Flutter中mock住某个channelName,这样的话,发送这类消息就会走你自己的handler。我们要和Native做通信,这里自然没有mock住。

继续看(2)的代码,从方法名就可以看出,这里就是和平台通信有关的逻辑了,跟踪进去,这里会处理一下回调,及错误情况,不过主要的是调用了ui.window.sendPlatformMessage()这个方法,这是FlutterEngine中的库方法,在这个方法里会调用dart的Native方法。

Flutter Engine逻辑

Flutter Engine是开源的,我们可以继续跟下去。

Dart的Native机制是先注册一个对Native方法的映射表:

代码语言:txt
复制
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},
  });
}

我们可以找到对应的方法是_SendPlatformMessage,这个方法会调到SendPlatformMessage

代码语言:txt
复制
  Dart_Handle SendPlatformMessage(Dart_Handle window,
                                const std::string& name,
                                Dart_Handle callback,
                                const tonic::DartByteData& data) {
  // ...
  if (Dart_IsNull(data.dart_handle())) {
    dart_state->window()->client()->HandlePlatformMessage( // (1)
        fml::MakeRefCounted<PlatformMessage>(name, response));
  } else {
    const uint8_t* buffer = static_cast<const uint8_t*>(data.data());

    dart_state->window()->client()->HandlePlatformMessage( // (1)
        fml::MakeRefCounted<PlatformMessage>(
            name, std::vector<uint8_t>(buffer, buffer + data.length_in_bytes()),
            response));
  }

  return Dart_Null();
}

可以看(1)位置,最后都是会调到WindowClient的HandlePlatformMessage方法,WindowClient的具体实现是RuntimeController,然后RuntimeController会将方法交给RuntimeDelegate来实现,而RuntimeDelegate的具体实现则是Engine类,这个类中的方法实现如下:

代码语言:txt
复制
static constexpr char kAssetChannel[] = "flutter/assets";

void Engine::HandlePlatformMessage(
    fml::RefPtr<blink::PlatformMessage> message) {
  if (message->channel() == kAssetChannel) {
    HandleAssetPlatformMessage(std::move(message));
  } else {
    delegate_.OnEngineHandlePlatformMessage(std::move(message));
  }
}

这个方法会首先检查是否是Flutter获取平台资源的,如果是就转到获取资源的逻辑里去,否则走Delegate的逻辑,这里Delegate是的实现类是Shell。

代码语言:txt
复制
// |shell::Engine::Delegate|
void Shell::OnEngineHandlePlatformMessage(
    fml::RefPtr<blink::PlatformMessage> message) {
  FML_DCHECK(is_setup_);
  FML_DCHECK(task_runners_.GetUITaskRunner()->RunsTasksOnCurrentThread());

  task_runners_.GetPlatformTaskRunner()->PostTask(
      [view = platform_view_->GetWeakPtr(), message = std::move(message)]() {
        if (view) {
          view->HandlePlatformMessage(std::move(message));
        }
      });
}

可以看到,在Shell类里,会在PlatformTaskRunner中添加一个task,这时候执行会切换到Platform task,之前都是在UI task中执行的。PlatformViewHandlePlatformMessage是一个虚函数,可以看PlatformViewAndroid中对其的实现。

代码语言:txt
复制
// |shell::PlatformView|
void PlatformViewAndroid::HandlePlatformMessage(
    fml::RefPtr<blink::PlatformMessage> message) {
  JNIEnv* env = fml::jni::AttachCurrentThread();
  fml::jni::ScopedJavaLocalRef<jobject> view = java_object_.get(env);
  // ...
  auto java_channel = fml::jni::StringToJavaString(env, message->channel()); // (1)
  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);  // (2)
  } else {
    message = nullptr;

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

这里,可以看到,已经开始进行JNI相关的操作了。注释(1)处从message结构中,拿到ChannelName,然后(2)(3)的区别就是一个带参数,一个不带参数,通过FlutterViewHandlePlatformMessage发起调用。

代码语言:txt
复制
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); // (1)
  FML_CHECK(CheckException(env));
}
// ...
bool PlatformViewAndroid::Register(JNIEnv* env) {
  // ...
  g_flutter_native_view_class = new fml::jni::ScopedJavaGlobalRef<jclass>(
      env, env->FindClass("io/flutter/view/FlutterNativeView")); // (2)
  // ...
  g_handle_platform_message_method =
    env->GetMethodID(g_flutter_native_view_class->obj(),
                     "handlePlatformMessage", "(Ljava/lang/String;[BI)V"); // (3)
  // ...
}

(1)处可以看到,开始通过JNI调Java方法,注释(2)(3)处,指定了Java类和方法。调用了FlutterNativeViewhandlePlatformMessage方法,看这里的源码:

代码语言:txt
复制
    private void handlePlatformMessage(final String channel, byte[] message, final int replyId) {
        this.assertAttached();
        BinaryMessageHandler handler = (BinaryMessageHandler)this.mMessageHandlers.get(channel); // (1)
        if (handler != null) {
            try {
                ByteBuffer buffer = message == null ? null : ByteBuffer.wrap(message);
                handler.onMessage(buffer, new BinaryReply() {
                    // ...
                });
            } catch (Exception var6) {
                // ...
            }
        } else {
            Log.e("FlutterNativeView", "Uncaught exception in binary message listener", var6);
            nativeInvokePlatformMessageEmptyResponseCallback(this.mNativePlatformView, replyId);
        }
    }

看注释(1)处,就是从之前存入的map中取出相应的handler,如果找不到,则回调Native表示失败。否则调用BinaryMessageHandleronMessage方法,将方法信息传递过去。

然后就回到了最开始的逻辑,通过IncomingMethodCallHandler处理message数据,然后将结果通过nativeInvokePlatformMessageResponseCallback回调给Flutter。

总结分析

跟完MethodChannel的源码,会发现整个通信机制还挺简单的,先去不去理解Codec的话,等于就是将dart的变量,传到dart Native,然后交到java Native, 再传到java。然后相反的路径,再从java到dart。

然后再去看BasicMessageChannel就是没有MethodCall这个结构的,其他的也是走的BinaryMessages.send方法。然后在Android端,没有IncomingMethodCallHandler这个类,直接就是BinaryMessageHandler。所以了解了MethodChannelBasicMessageChannel原理自然就懂了。(可以说MethodChannel就是借助BasicMessageChannel实现的)

同样的EventChannel则是基于MethodChannel来实现的,只是两端的handler会有一些特殊的处理方式,这个倒是与通信没有多大关系了,不过设计的也很简单,比较有意思。之后另起一文细表。

Wrote by Kevin(a2V2aW56aGFuMDQxN0BvdXRsb29rLmNvbQ==)

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Flutter与Native通信 - PlatformChannel源码分析
    • PlatformChannel功能简介
      • MethodChannel的用法(Android)
        • 源码分析
          • Android平台逻辑
          • Flutter逻辑
          • Flutter Engine逻辑
        • 总结分析
        相关产品与服务
        腾讯云 BI
        腾讯云 BI(Business Intelligence,BI)提供从数据源接入、数据建模到数据可视化分析全流程的BI能力,帮助经营者快速获取决策数据依据。系统采用敏捷自助式设计,使用者仅需通过简单拖拽即可完成原本复杂的报表开发过程,并支持报表的分享、推送等企业协作场景。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档