Flutter插件

Flutter package:flutter插件

####建立插件java

官方方案

flutter create --org [包前缀]--template=plugin -a kotlin [plugin_name(同时也会做为包的后缀)]android

参数说明:c++

  • org 包前缀git

  • template: 插件类型,目前支持建立三种类型的flutter工程(参考flutter create -h)json

    1. [app] 默认 生成一个flutter app 工程
    2. [package] 生成一个可共享的纯 dart 库。
    3. [plugin] 生成一个桥接了原生方法的 dart 库。 也就是插件。
  • a 代表andoid插件语言类型:默认 java 能够改成kotlinswift

  • i 代表IOS插件语言类型:默认ObjectC 可修改成swiftapi

  • 最后一个参数: 插件名称。也会做为报名的最后一部分。建议使用下划线形式。这个名称会在如下文件内出现:缓存

    pubspec.yaml 做为插件名称。外部引用此插件也是用这个名字。
      pubspec.yam 中的 flutter 部分,主要用来自动生成相关代码。
      	plugin:
      		androidPackage: com.sd.test_plugin
      		pluginClass: TestPlugin
      		
      lib下根目录中的同名文件:
      		对外暴露的插件文件,能够写逻辑,也能够做为统一入口。export但愿导出的功能
      		method名称 static const MethodChannel _channel = const MethodChannel('test_plugin');
      test下的测试文件。
      example下的pubspec.yaml文件。
      android文件夹下:setting.gradle,build.gradle.
      				AndroidManifest中包名。
      				src中的包结构。
      				插件桥接原生的类(java,kotlin)文件。此文件会自动将 “_” 变为驼峰形式命名
      				插件类中的channel 名称,用来和dart端通讯
      					val channel = MethodChannel(registrar.messenger(), "test_plugin")
    复制代码

所以建议生成文件时采用下划线分隔,命名。若是已经生成了插件,想修更名字,记得仔细排查上述文件。注意:methodChannel中的名称必须保证惟一性。最好增长一个包前缀,防止和其余三方插件冲突。此处能够单独修改,不影响插件自己的名称。安全

官方给出的开发Package方法,适用于生成一个较为通用的插件。即和具体业务关系不是很大。逻辑相对独立。好比获取包名,http。打点等。若是和APP业务耦合较多,我的建议采用动态注册插件的方法。bash

动态注册插件

从上面命令生成的模版工程能够看出。插件的本质就是原生端(Android,IOS),经过Method和dart端进行通讯。将信息(数据)传递到Dart端。所以只要肯定好信息通道(methodChannel的名称),彻底能够直接在代码中编写相应逻辑。

  • 原生端(android)
// 在承载相应 Flutter 内容的 Activity 中 注册MethodChannel。
class MainActivity() : FlutterActivity() {
  private val CHANNEL = "com.sd.example.customPlugin"

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    GeneratedPluginRegistrant.registerWith(this)
	// new Method时,会进行channel的绑定。
    MethodChannel(flutterView, CHANNEL).setMethodCallHandler { call, result ->
      switch (methodCall.method) {
         case "getInformation":  
           // getInformation
         	 result.success("information");
           break;
         default:
           result.notImplemented();
      }
    }
  }
}
复制代码
  • dart 端
class _MyHomePageState extends State<MyHomePage> {
  static const platform = const MethodChannel('com.sd.example.customPlugin');

  platform.invokeMethod("getInformation").then((value) {
    // 获取到 “information” 字符串
    print(value);
  });
}
复制代码
优化内容

若是仔细观察系统生成插件的代码能够发现以下内容

public static void registerWith(PluginRegistry registry) {
    if (alreadyRegisteredWith(registry)) {
      return;
    }
    // 注册各个插件
}

private static boolean alreadyRegisteredWith(PluginRegistry registry) {
    final String key = GeneratedPluginRegistrant.class.getCanonicalName();
    if (registry.hasPlugin(key)) {
      return true;
    }
    registry.registrarFor(key);
    return false;
}
复制代码

在注册插件以前,判断插件是否已经注册过。避免重复注册,浪费资源。(重复注册, 会在Map中覆盖以前注册过的channel,能够参考DartMessenger中setMessageHandler方法的实现)建议参照官方实现方法对动态注册过程进行优化。

public static void registerCustomPlugin(PluginRegistry registry) {
		 // 声明一个Key用来判断是否在此registry中注册过此插件。不必和methodChannel中的名字相同,每个注册过程有一个名字便可。
        String key = JtSdk.application.getPackageName() + ".customPlugin";
        if (!registry.hasPlugin(key)) {
            new MethodChannel(registry.registrarFor(key).messenger(),
                    Application.getPackageName() + "/nativePlugin").setMethodCallHandler(
                    // 注意:此方法调用在单独线程中,小心线程同步问题
                    (methodCall, result) -> {
                        switch (methodCall.method) {
                            case "getInformation":  
           						// getInformation
           						result.success("information");
           						break;
                            default:
                                result.notImplemented();
                                break;
                        }
                    });
        }
    }
复制代码

#####出场角色介绍

  • PluginRegistry: 插件注册表,主要实现类 FlutterPluginRegistry。注册插件的入口,内部经过Map维护插件列表。他不直接管理每一个插件。充当一个相似门户的角色,持有activity,flutterView,flutterNative等关键对象。
  • MethodChannel: 负责 1.绑定BinaryMessenger和具体channel名称,暴露原生方法给Dart。2. 和dart端进行通讯,调用Dart端方法。

构造方法

MethodChannel(BinaryMessenger messenger, String name, MethodCodec codec)
复制代码
  • BinaryMessenger: 原生Native通讯角色。主要实现类就是FlutterNativeView。上面的构造方法中,能够看出每一个MethodChannel适合一个Messenger绑定的。若是新启动了一个FlutterActivity,默认状况下,是须要从新注册全部插件的(Boost由于复用了FlutterNativeView,所以不须要从新注册)

  • MethodCodec: 消息编解码器。负责编解码MethodCall,和返回结果。使数据能够经过Native(c++)层在原生,Dart端进行传递。实现类有两种:

    1. StandardMethodCodec :直接以二进制形式传递数据传递,要求数据的写入,读取顺序必须一致,相似Parcelable;
    2. JSONMethodCodec:数据先封装为Json格式,而后再变为二进制。返过来也是,现将bytebuffer变为String,而后转为Json。

各平台类型对应关系

Dart Android iOS
null null nil (NSNull when nested)
bool java.lang.Boolean NSNumber numberWithBool:
int java.lang.Integer NSNumber numberWithInt:
int, if 32 bits not enough java.lang.Long NSNumber numberWithLong:
int, if 64 bits not enough java.math.BigInteger FlutterStandardBigInteger
double java.lang.Double NSNumber numberWithDouble:
String java.lang.String NSString
Uint8List byte[] FlutterStandardTypedData typedDataWithBytes:
Int32List int[] FlutterStandardTypedData typedDataWithInt32:
Int64List long[] FlutterStandardTypedData typedDataWithInt64:
Float64List double[] FlutterStandardTypedData typedDataWithFloat64:
List java.util.ArrayList NSArray
Map java.util.HashMap NSDictionary
  • MethodCallHandler: 负责处理dart 端发送过来的请求,接受两个参数:MethodCall,Result
  • MethodCall:请求的封装类:
    1. MethodCall.method: 是dart端但愿调用的方法名称;
    2. MethodCall.arguments: 是dart端传递过来的参数,多是上述对照表中的任意类型。
  • Result: 负责回调给dart端结果,结果分为三种:
    1. success(result) : result会自动转换为上述表中类型,传递给dart端。
    2. error(code,messege,details): dart端会抛出一个 PlatformException 的异常。参数为异常中的code等值
    3. notImplemented():dart端抛出 MissingPluginException

开发使用插件

建立完成插件以后,就须要在两端编写相应的逻辑代码。一个简单的插件,Dart端几乎不须要编写代码,只负责调用便可。惟一须要注意的是Flutter和原生端运行在不一样的线程中。所以全部跨端的调用都是异步执行的。

dart端
class _MyHomePageState extends State<MyHomePage> {
  static const platform = const MethodChannel('com.sd.example.customPlugin');

  // invodeMethod返回的是一个Future<T> 类型
  platform.invokeMethod("getInformation").then((value) {
    // 获取到 “information” 字符串
    print(value);
  });
}
复制代码

错误写法

getInformation() async {
  return await platform.invokeMethod("getInformation");
}

test() {
  Strng information = getInformation();
  print(information);
}
复制代码
原生端

原生端主要实现的方法,就在 onMethodCall 中,针对不一样的method,执行不一样逻辑,并经过result,返回结果。

@Override
 public void onMethodCall(MethodCall methodCall, Result result) {
   switch (methodCall.method) {
     case "method1":
          var param1 = methodCall.argument("param1"),
          var param2 = methodCall.argument("param2");
          result.success(param1 + param2);
          break;
     case "method2":
       try{
           // do business
           result.success(res);
       } catch (e) {
         result.error("101", "系统错误", "错误缘由");
       }
         break;
     default:
       result.notImplemented();
       break;
 }
复制代码

须要注意如下几点

  1. onMethod方法调用在UI线程,要注意不要执行耗时操做。
  2. result中的相应方法必定要调用。不然Dart端会有一个待完成的Future对象,一直在挂起等待。虽然不会阻塞dart线程,可是会有其余不可预期的异常。
  3. result下的方法。须要在主线程调用。 flutter engine中的组件都是非线程安全的。为避免出现未知错误,和engine通讯的相关方法。都要在主线程调用执行。

####使用方法:

和使用第三方库相同,在pubspec.yaml 文件中添加相关依赖。

dependencies:
  #经过git形式进行依赖
  jt_base_http:
    git:
      url: ssh://gerrit.rong360.com:29418/flutter_plugins
      path: jt_base_http
  # 经过版本号依赖
  json_annotation: ^2.4.0
  # 经过相对路径依赖
  jt_base_log:
    path: ../
复制代码

而后建立methodChannel,调用相应方法便可。

class _MyHomePageState extends State<MyHomePage> {
  static const platform = const MethodChannel('com.sd.example.customPlugin');

  platform.invokeMethod("getInformation").then((value) {
    // 获取到 “information” 字符串
    print(value);
  });
}
复制代码

原理

原理

dart -> 原生

// dart自己是单线程模型,对与原生的调用必须经过异步方式执行。
platform.invokeMethod("xxx") 方法自己也是返回一个Future对象。
  const platform = const MethodChannel('channel_name');
  
await  platform.invokeMethod("xxx");

-> invokeMethod源码
platform_channel.dart

@optionalTypeArgs
  Future<T> invokeMethod<T>(String method, [ dynamic arguments ]) async {
    assert(method != null);
    // 关键代码,经过 BinaryMessenger 以ByteData形式发送 message
    final ByteData result = await binaryMessenger.send(
    	//  channel_name
      name,
      // ByteData 包含方法名称和相关参数。不管使用Json仍是Standard codec。都会编码为二进制
      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;
  }

->
binary_messenger.dart

@override
  Future<ByteData> send(String channel, ByteData message) {
    final MessageHandler handler = _mockHandlers[channel];
    if (handler != null)
      return handler(message);
    // 真正发送message的方法
    return _sendPlatformMessage(channel, message);
  }
  
   Future<ByteData> _sendPlatformMessage(String channel, ByteData message) {
    // 注意:Completer 其内部包含一个future对象,供dart isolate执行。至于这个future对象什么时候结束,又completer 本身控制
    final Completer<ByteData> completer = Completer<ByteData>();
	// 发送message方法到JNI层,同时封装一个回调 PlatformMessageResponseCallback 用来接受原生端的respone,并结束这个 future
    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;
  }
->
window.dart
// callback定义
typedef PlatformMessageResponseCallback = void Function(ByteData data);

// JNI方法。
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';
复制代码

到这里,咱们能够知道,invokeMethod方法,其实就是包装一些请求参数(methodname,params),经过codec加密。并返回一个 Future 放在dart event queue中执行,至于这个Future什么时候结束,彻底由native端什么时候调用complete决定。

继续JNI层,看下咱们的请求到底去了哪里。切入点是'Window_sendPlatformMessage'

lib/ui/window/window.cc
{"Window_sendPlatformMessage", _SendPlatformMessage, 4, true}

->
engine/lib/ui/window/window.cc

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());
    // 通知具体 client 处理message
    dart_state->window()->client()->HandlePlatformMessage(
        fml::MakeRefCounted<PlatformMessage>(
            name, std::vector<uint8_t>(buffer, buffer + data.length_in_bytes()),
            response));
  }

省略其余过程。平台判断等
->
platform_view_android.cc

// |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()) {
    // 缓存一下respone。等待客户端处理完成在通知回Dart
    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.
    // 调用原生方法处理 message
    FlutterViewHandlePlatformMessage(env, view.obj(), java_channel.obj(),
                                     message_array.obj(), response_id);
  } else {
    message = nullptr;
    // This call can re-enter in InvokePlatformMessageXxxResponseCallback.
    // 调用原生方法处理 message
    FlutterViewHandlePlatformMessage(env, view.obj(), java_channel.obj(),
                                     nullptr, response_id);
  }
}

platform_view_android_jni.cc
// 原生端方法名为 io/flutter/embedding/engine/FlutterJNI.handlePlatformMessage() 

g_flutter_jni_class = new fml::jni::ScopedJavaGlobalRef<jclass>(
      env, env->FindClass("io/flutter/embedding/engine/FlutterJNI"));
      
 g_handle_platform_message_method =
      env->GetMethodID(g_flutter_jni_class->obj(), "handlePlatformMessage",
                       "(Ljava/lang/String;[BI)V");
                       
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));
}

复制代码

消息发送到Java端后,天然须要Java端处理方法,并将结果返回给dart

先看 java端注册Messanger方法
简单版本,不须要flutter create 
// messenger通常就是 FlutterView
new MethodChannel(messenger, "channel_name").setMethodCallHandler(
	new MethodChannel.MethodCallHandler(){
	
		onMethodCal((@NonNull MethodCall call, @NonNull MethodChannel.Result result){
		// 根据call中的method(方法名)arguments(参数)执行操做。
		// 调用result中的api: success,error,notImplemented。发送结果到dart
		}
	})

setMethodCallHandler时 实际调用了

 @UiThread
    public void setMethodCallHandler(@Nullable MethodChannel.MethodCallHandler handler) {
        this.messenger.setMessageHandler(this.name, handler == null ? null : 
        new MethodChannel.IncomingMethodCallHandler(handler)); // 对handler的封装,内部管理什么时候调用onMethodCall。并传递Result。
    }

messenger就是上面提到的FlutterView。其内部又直接调用了 FlutterNativeView 的 setMessageHandler 方法

FlutterNativeView.java
@UiThread
    public void setMessageHandler(String channel, BinaryMessageHandler handler) {
        this.mNativeView.setMessageHandler(channel, handler);
    }
    
复制代码

// 注:关于 FlutterNativeView 他就是FlutterView(原生View)和native Fluter 引擎通讯的关键对象。官方所给的混合方案中。生成的 Flutter 类 在 createView 的时候都会建立一个新的FlutterNativeView,对于内存和性能都有所损耗。FlutterBoost方案中,在建立新页面的时候复用了FutterView和FlutterNativeView。所以能够提升部分性能。

以后又陆续调用了 DartExecutor, DartMessenger的setMessageHandler 方法。最终 handler 以Map的形式,保存在DartMessenger中的messageHandlers map中,key是以前提到的 "channel_name" .

仔细阅读DartMessenger 类,能够发现一个 handleMessageFromDart 方法,这个方法,就是Native端(FlutterViewHandlePlatformMessage)最终调用的java方法

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);
                // 关键函数,将dart端发送过来的 message 传递给最终的 Handler 
                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);
        }

    }
复制代码

后续也就是一直调用到咱们以前设置的 onMethodCall 方法了。

还有一个疑问,就是以前dart端发送消息的时候,执行了一个 Futture 这个 Futture 须要持有它的 Completer对象调用complete方法,才能中止。这个方法何时调用呢?

以前提过 setMethodCall里,对与Handler有一个封装类 MethodChannel.IncomingMethodCallHandler(handler) 分析一下他的 onMesage 方法 就有答案了

@UiThread
        public void onMessage(ByteBuffer message, final BinaryReply reply) {
            // 解码MethodCall 注意要使用对应的Codec
            MethodCall call = MethodChannel.this.codec.decodeMethodCall(message);

            try {
            
                this.handler.onMethodCall(call, new MethodChannel.Result() {
                // 加密结果,并返回给Dard
                    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) {
                Log.e("MethodChannel#" + MethodChannel.this.name, "Failed to handle method call", var5);
                reply.reply(MethodChannel.this.codec.encodeErrorEnvelope("error", var5.getMessage(), (Object)null));
            }

        }
复制代码

能够看到,只要咱们调用了result的任意方法(success,error,notImplemented),都会经过这个reply 方法发送一个消息出去,通知最开始提到的Completer对象执行 complete。

简单看一下调用链。

DartMessenger.Reply 对象。

public void reply(@Nullable ByteBuffer reply) {
            if (this.done.getAndSet(true)) {
                throw new IllegalStateException("Reply already submitted");
            } else {
                if (reply == null) {
                    this.flutterJNI.invokePlatformMessageEmptyResponseCallback(this.replyId);
                } else {
                    this.flutterJNI.invokePlatformMessageResponseCallback(this.replyId, reply, reply.position());
                }

            }
        }

又回到了FlutterJNI中。将事件通知到JNI层。

platform_view_android.cc(以前DispatchMessage的类)

void PlatformViewAndroid::InvokePlatformMessageResponseCallback(
    JNIEnv* env,
    jint response_id,
    jobject java_response_data,
    jint java_response_position) {
  if (!response_id)
    return;
    // 找到正在等待处理的请求(以前HandlePlaformMessage时作的缓存),也就是dart端发送过来的message请求
  auto it = pending_responses_.find(response_id);
  if (it == pending_responses_.end())
    return;
  uint8_t* response_data =
      static_cast<uint8_t*>(env->GetDirectBufferAddress(java_response_data));
  std::vector<uint8_t> response = std::vector<uint8_t>(
      response_data, response_data + java_response_position);
  auto message_response = std::move(it->second);
  // 清楚记录
  pending_responses_.erase(it);
  // 调用Complete方法。
  message_response->Complete(
      std::make_unique<fml::DataMapping>(std::move(response)));
}

platform_message_response_dart.cc

void PlatformMessageResponseDart::Complete(std::unique_ptr<fml::Mapping> data) {
  if (callback_.is_empty())
    return;
  FML_DCHECK(!is_complete_);
  is_complete_ = true;
  ui_task_runner_->PostTask(fml::MakeCopyable(
      [callback = std::move(callback_), data = std::move(data)]() mutable {
        std::shared_ptr<tonic::DartState> dart_state =
            callback.dart_state().lock();
        if (!dart_state)
          return;
        tonic::DartState::Scope scope(dart_state);

        Dart_Handle byte_buffer = WrapByteData(std::move(data));
        // 释放callback 将结果传到 dart 端
        tonic::DartInvoke(callback.Release(), {byte_buffer});
      }));
}

复制代码
相关文章
相关标签/搜索