####建立插件java
flutter create --org [包前綴]--template=plugin -a kotlin [plugin_name(同時也會做爲包的後綴)]android
參數說明:c++
org 包前綴git
template: 插件類型,目前支持建立三種類型的flutter工程(參考flutter create -h)json
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的名稱),徹底能夠直接在代碼中編寫相應邏輯。
// 在承載相應 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();
}
}
}
}
複製代碼
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;
}
});
}
}
複製代碼
#####出場角色介紹
構造方法
MethodChannel(BinaryMessenger messenger, String name, MethodCodec codec)
複製代碼
BinaryMessenger: 原生Native通訊角色。主要實現類就是FlutterNativeView。上面的構造方法中,能夠看出每一個MethodChannel適合一個Messenger綁定的。若是新啓動了一個FlutterActivity,默認狀況下,是須要從新註冊全部插件的(Boost由於複用了FlutterNativeView,所以不須要從新註冊)
MethodCodec: 消息編解碼器。負責編解碼MethodCall,和返回結果。使數據能夠經過Native(c++)層在原生,Dart端進行傳遞。實現類有兩種:
各平臺類型對應關係
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 |
建立完成插件以後,就須要在兩端編寫相應的邏輯代碼。一個簡單的插件,Dart端幾乎不須要編寫代碼,只負責調用便可。惟一須要注意的是Flutter和原生端運行在不一樣的線程中。所以全部跨端的調用都是異步執行的。
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;
}
複製代碼
須要注意如下幾點
####使用方法:
和使用第三方庫相同,在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});
}));
}
複製代碼