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});
      }));
}

複製代碼
相關文章
相關標籤/搜索