Flutter与Native通信 - PlatformChannel源码分析

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中,注册插件:

    // 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,然后调用方法即可。

    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项目中那些代码到底做了什么事情。

    // 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就好。

    // 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发起调用时,做了什么事情。

    // 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:

  // 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方法的映射表:

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

  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类,这个类中的方法实现如下:

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。

// |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中对其的实现。

// |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发起调用。

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方法,看这里的源码:

    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==)

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

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

编辑于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏大魏分享(微信公众号:david-share)

实战:应用对持久数据访问| 从开发角度看应用架构9

JPA的API有主要以下几个:实体(entity)、持久性单元(persistence units)、持久性上下文( persistence context)、...

10430
来自专栏程序猿DD

死磕Java并发:深入分析synchronized的实现原理

记得刚刚开始学习Java的时候,一遇到多线程情况就是synchronized。对于当时的我们来说,synchronized是如此的神奇且强大。我们赋予它一个名字...

14470
来自专栏Kirito的技术分享

Spring Boot Dubbo应用启停源码分析

Dubbo Spring Boot 工程致力于简化 Dubbo RPC 框架在Spring Boot应用场景的开发。同时也整合了 Spring Boot 特性:

20720
来自专栏王清培的专栏

.NET应用程序调试—原理、工具、方法

阅读目录: 1.背景介绍 2.基本原理(Windows调试工具箱、.NET调试扩展SOS.DLL、SOSEX.DLL) 2.1.Windows调试工具箱 ...

24560
来自专栏chenssy

【死磕Java并发】-----深入分析synchronized的实现原理

记得刚刚开始学习Java的时候,一遇到多线程情况就是synchronized,相对于当时的我们来说synchronized是这么的神奇而又强大,那个时候我们赋予...

15630
来自专栏Java帮帮-微信公众号-技术文章全总结

day05.轻量级RPC框架【大数据教程】

day05.轻量级RPC框架【大数据教程】 轻量级RPC框架开发 1. RPC原理学习 1.1. 什么是RPC RPC(Remote Procedure Cal...

36070
来自专栏逸鹏说道

Python3 与 C# 并发编程之~ 进程篇下

看看 connection.Pipe方法的定义部分,是不是双向通信就看你是否设置 duplex=True

20730
来自专栏禁心尽力

Web层框架对网站中所有异常的统一处理

  一个网站的异常信息作为专业的人士,是不会轻易暴露给用户的,因为那样狠不安全,显得你漏是一回事,只要还是考虑到网站的数据安全问题,下面给大家分享一下一些常见的...

22980
来自专栏美团技术团队

不可不说的Java“锁”事

Java提供了种类丰富的锁,每种锁因其特性的不同,在适当的场景下能够展现出非常高的效率。本文旨在对锁相关源码(本文中的源码来自JDK 8)、使用场景进行举例,为...

15320
来自专栏大数据和云计算技术

hdfs auditlog(审计日志)

hdfs审计日志(Auditlog)记录了用户针对hdfs的所有操作,详细信息包括操作成功与否、用户名称、客户机地址、操作命令、操作的目录等。对于用户的每一个...

32330

扫码关注云+社区

领取腾讯云代金券