Home Flutter中的Hybrid Coding详解
Post
Cancel

Flutter中的Hybrid Coding详解

混合编程的例子

Flutter中,通过MethodChannel与原生通讯的。当创建一个插件项目时,生成的项目会自带MethodChannel的例子。

可以用Android Stuido创建一个插件项目,或者使用命令行创建:

1
flutter create --template=plugin -i swift -a kotlin hybrid_plugin

当项目创建完,可以看到生成的关键代码:

lib/hybrid_plugin.dart

1
2
3
4
5
6
7
8
9
10
11
12
13
import 'dart:async';

import 'package:flutter/services.dart';

class HybridPlugin {
  static const MethodChannel _channel =
      const MethodChannel('hybrid_plugin');

  static Future<String> get platformVersion async {
    final String version = await _channel.invokeMethod('getPlatformVersion');
    return version;
  }
}

android/src/../hybrid_plugin/HybridPlugin.kt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.example.hybrid_plugin

import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result
import io.flutter.plugin.common.PluginRegistry.Registrar

class HybridPlugin: MethodCallHandler {
  companion object {
    @JvmStatic
    fun registerWith(registrar: Registrar) {
      val channel = MethodChannel(registrar.messenger(), "hybrid_plugin")
      channel.setMethodCallHandler(HybridPlugin())
    }
  }

  override fun onMethodCall(call: MethodCall, result: Result) {
    if (call.method == "getPlatformVersion") {
      result.success("Android ${android.os.Build.VERSION.RELEASE}")
    } else {
      result.notImplemented()
    }
  }
}

ios/Classes/SwiftHybridPlugin.swift

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import Flutter
import UIKit

public class SwiftHybridPlugin: NSObject, FlutterPlugin {
  public static func register(with registrar: FlutterPluginRegistrar) {
    let channel = FlutterMethodChannel(name: "hybrid_plugin", binaryMessenger: registrar.messenger())
    let instance = SwiftHybridPlugin()
    registrar.addMethodCallDelegate(instance, channel: channel)
  }

  public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
    result("iOS " + UIDevice.current.systemVersion)
  }
}

以上就是一个完整的Flutterandroid nativeiOS native通讯的例子。下面来详细分析一下。

Flutter中创建MethodChannel

Flutter项目中,创建MethodChannel实例。并且要约定一个Channel名字,因为这个名字在native端需要用到。

创建一个MethodChannel实例:

1
static const MethodChannel _channel = const MethodChannel('hybrid_plugin');

然后,通过MethodChannel.invokeMethod<T>来调起原生中的方法。

invokeMethod方法接收两个入参:

  • String method. 方法名(并非原生中的方法名,只相当于是一个Key)。原生端会根据不同的名字去调用不同的逻辑。
  • dynamic arguments. 可选,入参。

返回一个Future<T>, 是原生端返回的内容T.

1
final String version = await _channel.invokeMethod('getPlatformVersion');

到此,Flutter端的工作就结束了。下面是原生端的代码实现。

原生端对接MethodChannel

Android端为例,需要实现MethodCallHandler 接口。在上面的例子中,是创建了HybridPlugin类。

MethodCallHandler接口只有一个方法:

1
2
3
public interface MethodCallHandler {
      void onMethodCall(MethodCall var1, MethodChannel.Result var2);
}

onMethodCall的第一个参数MethodCall, 有两个属性:

  • String method
  • Object arguments

这跟在Flutter中调用inokeMethod方法的入参完全一致。在Flutter端调用inokeMethod之后,原生端就会回调onMethodCall方法,并通过MethodCall将入参传递过来。

第二个参数MethodChannel.Result, 明显就是返回结果用的。它有三个方法:

它的定义如下:

1
2
3
4
5
6
7
public interface Result {
        void success(@Nullable Object var1);

        void error(String var1, @Nullable String var2, @Nullable Object var3);

        void notImplemented();
    }

suceess方法在成功时调用。接收一个参数,并会将值返回到Flutter中,即inovkeMethod的返回值。

error方法在失败时调用。接收三个参数,第一个参数是错误码;第二个参数是错误信息;第三个参数是错误详情。

notImplemented方法表示没有匹配到方法名。

从源码中能够看到一些本质, 下面是一部分源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
private final class IncomingMethodCallHandler implements BinaryMessageHandler {
       //...
        public void onMessage(ByteBuffer message, final BinaryReply reply) {
			//...
            try {
                this.handler.onMethodCall(call, new MethodChannel.Result() {
                    public void success(Object result) {
                     	reply.reply(MethodChannel.this.codec.encodeSuccessEnvelope(result));
                    }

                    public void error(String errorCode, String errorMessage, Object errorDetails) {
                        	reply.reply(MethodChannel.this.codec.encodeErrorEnvelope(errorCode, errorMessage, errorDetails));
                    }

                    public void notImplemented() {
                        reply.reply((ByteBuffer)null);
                    }
                });
            } catch (RuntimeException var5) {
				//...
            }

        }
    }

其中,BinaryReply.reply方法只接收一个ByteBuffer, BinaryReply定义如下:

1
2
3
public interface BinaryReply {
    void reply(ByteBuffer var1);
}

也就是说,不管是successerrornotImplemented, 都是返回的ByteBuffer。那ByteBuffer的内容是如何区分成功、失败数据的呢?

StandardMethodCodec是默认的codec,看下它的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public final class StandardMethodCodec implements MethodCodec {
   //...
    public ByteBuffer encodeSuccessEnvelope(Object result) {
        ExposedByteArrayOutputStream stream = new ExposedByteArrayOutputStream();
        stream.write(0);
        this.messageCodec.writeValue(stream, result);
        ByteBuffer buffer = ByteBuffer.allocateDirect(stream.size());
        buffer.put(stream.buffer(), 0, stream.size());
        return buffer;
    }

    public ByteBuffer encodeErrorEnvelope(String errorCode, String errorMessage, Object errorDetails) {
        ExposedByteArrayOutputStream stream = new ExposedByteArrayOutputStream();
        stream.write(1);
        this.messageCodec.writeValue(stream, errorCode);
        this.messageCodec.writeValue(stream, errorMessage);
        this.messageCodec.writeValue(stream, errorDetails);
        ByteBuffer buffer = ByteBuffer.allocateDirect(stream.size());
        buffer.put(stream.buffer(), 0, stream.size());
        return buffer;
    }
    //...
}

如果是成功,先写入0,再写入返回的内容;如果是失败,先写入1,再分别写入错误信息。

源码分析部分到此结束,接着回到上面的分析中。

细心的同学可能注意到,在android/HybridPlugin中,还有一个静态方法:

1
2
3
4
5
6
7
companion object {
    @JvmStatic
    fun registerWith(registrar: Registrar) {
      val channel = MethodChannel(registrar.messenger(), "hybrid_plugin")
      channel.setMethodCallHandler(HybridPlugin())
    }
  }

它有两个作用:

  • 将原生端与Flutter端连接起来。

    在上面Flutter端创建Channel时,有约定一个Channel名hybrid_plugin。这里就是通过这个Channel名来匹配对应的Channel。

  • 框架会自动查找项目中的registerWith(Registrar)方法,并通过代码生成的方式生成GeneratedPluginRegistrant类。在该类中,会自动调用HybridPlugin.register方法注册。

对于第二个作用,可以看下demo中代码。我们在创建这个插件时,在根目录下会自动生成一个example项目,并且已经引入了这个插件。

example/pubspec.yaml:

1
2
3
4
5
6
7
8
9
10
11
12
name: hybrid_plugin_example
description: Demonstrates how to use the hybrid_plugin plugin.

#...

dev_dependencies:
  flutter_test:
    sdk: flutter

  hybrid_plugin:
    path: ../
#...

example项目中的GeneratedPluginRegistrant类:

1
2
3
4
5
6
7
8
9
10
public final class GeneratedPluginRegistrant {
  public static void registerWith(PluginRegistry registry) {
    if (alreadyRegisteredWith(registry)) {
      return;
    }
    HybridPlugin.registerWith(registry.registrarFor("com.example.hybrid_plugin.HybridPlugin"));
  }

  //...
}

看到了吗?它已经调用HybridPlugin.registerWith去注册了。

而在example项目的MainActivity中,会调用GeneratedPluginRegistrant.registerWith:

1
2
3
4
5
6
class MainActivity: FlutterActivity() {
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    GeneratedPluginRegistrant.registerWith(this)
  }
}

这样,我们写的插件就会自动注册完成。


总结

开发一个混编插件,可以总结成下面几步:

  • 约定协议。包括Channel名,方法名,方法入参与出参。
  • Flutter端创建MethodChanel。根据业务逻辑,通过MethodChannel.invokeMethod实现调原生。
  • 在原生端(以Android为例),实现MethodCallHandler接口。根据不同的方法名,调用不同的逻辑。
  • 创建registerWith静态方法,通过Channel名将MethodCallHandler实现类绑定到MethodChannel中。

当然,如果只是项目中需要混编,而不想实现一个插件的话,可以直接在MainActivty中直接进行MethodChannel绑定,无需创建静态方法registerWith

This post is licensed under CC BY 4.0 by the author.