本文章中的完整代碼在這裡:GitHub - zhaolongs/Flutter-Android-iOS: Flutter 與 Android iOs 的雙向通信 demo
demo運作注意:
1.Json資料解析異常:org.json.JSONException: Unterminated object at character 40
解決辦法:将({"method":"test","ontent":"flutter中的資料","code":100}); 把裡面的空格去掉就可以了
平台通道
消息使用平台通道在用戶端(UI)和宿主(平台)之間傳遞,如下圖所示:

消息和響應以異步的形式進行傳遞,以確定使用者界面能夠保持響應。
簡單介紹Platfrom Channel的三個API
- MethodChannel : Flutter與原生方法互相調用,用于方法掉用。
- EventChannel : 原生發送消息,Flutter接收,用于資料流通信
- BasicMessageChannel : Flutter與原生互相發送消息,用于資料傳遞
EventChannel用于Android原生事件流向Flutter端的發送,例如通過原生監聽重力感應等狀态變化後向Flutter發送通知,一對多通知,類似于原生廣播。EventChannel隻能原生向flutter發送消息,不能和flutter互相調用和發送消息,其他兩個均可實作和flutter互相調用和發送消息。
三種Channel之間互相獨立,各有用途,但它們在設計上卻非常相近。每種Channel均有三個重要成員變量:
name: String類型,代表Channel的名字,也是其唯一辨別符。
messager:BinaryMessenger類型,代表消息信使,是消息的發送與接收的工具。
codec: MessageCodec類型或MethodCodec類型,代表消息的編解碼器。
例:MethodChannel(BinaryMessenger messenger, String name, MethodCodec codec)
注:一個Flutter應用中可能存在多個Channel,每個Channel在建立時必須指定一個獨一無二的name,Channel之間使用name來區分彼此。當有消息從Flutter端發送到Platform端時,會根據其傳遞過來的channel name找到該Channel對應的Handler(消息處理器)。
我們先來了解 BinaryMessenger、Codec、Handler 的概念。
BinaryMessenger
BinaryMessenger 是 PlatformChannel 與 Flutter 端的通信的工具,其通信使用的消息格式為二進制格式資料,BinaryMessenger 在 Android 中是一個接口,它的實作類為 FlutterNativeView。
Codec
Codec 是消息編解碼器,主要用于将二進制格式的資料轉化為 Handler 能夠識别的資料,Flutter 定義了兩種 Codec:MessageCodec 和 MethodCodec。MessageCodec 用于二進制格式資料與基礎資料之間的編解碼,BasicMessageChannel 所使用的編解碼器是 MessageCodec。MethodChannel 和 EventChannel 所使用的編解碼均為 MethodCodec。
Handler
Flutter 定義了三種類型的 Handler,它們與 PlatformChannel 類型一一對應,分别是 MessageHandler、MethodHandler、StreamHandler。在使用 PlatformChannel 時,會為它注冊一個 Handler,PlatformChannel 會将該二進制資料通過 Codec 解碼為轉化為 Handler 能夠識别的資料,并交給 Handler 處理。當 Handler 處理完消息之後,會通過回調函數傳回 result,将 result 通過編解碼器編碼為二進制格式資料,通過 BinaryMessenger 發送回 Flutter 端。
本文将實作:(通過 MethodChannel)
- 實作 Flutter 調用 Android 、iOS 原生的方法并回調Flutter
- 實作 Flutter 調用 Android 、iOS 原生并打開Android 原生的一個Activity頁面,iOS原生的一個ViewController 頁面
- 實作 Android 、iOS 原生主動發送消息到 Flutter 中
- 實作 Android 、iOS 原生中的 TestActivity 頁面主動發送消息到Flutter中
Android 中的效果(本圖還是 BasicMessageChannel 的效果圖,隻不過是通過 MethodChannel 方式實作出來了)
ios 中的效果(本圖還是 BasicMessageChannel 的效果圖,隻不過是通過 MethodChannel 方式實作出來了)
前言
1.例如我們要實作 A 調用 B,B就會觸發,B再調用A,A就會觸發這樣的功能,
2. 那麼我們就需要在 A 中設定 被B調用的監聽方法,在B中設定被A 調用的監聽方法
實作Flutter 調用 Andoid iOS原生方法并回調
實作 Flutter 中調用方法
//建立MethodChannel
// flutter_and_native_101 為通信辨別
// StandardMessageCodec() 為參數傳遞的 編碼方式
static const methodChannel = const MethodChannel('flutter_and_native_101');
//封裝 Flutter 向 原生中 發送消息 的方法
//method 為方法辨別
//arguments 為參數
static Future<dynamic> invokNative(String method, {Map arguments}) async {
if (arguments == null) {
//無參數發送消息
return await methodChannel.invokeMethod(method);
} else {
//有參數發送消息
return await methodChannel.invokeMethod(method, arguments);
}
}
觸發調用 ,分别在 三個 Button 的點選事件中觸發,下面均無向 原生 Android iOS 傳遞參數
invokNative("test")
..then((result) {
//第一種 原生回調 Flutter 的方法
//此方法隻能使用一次
int code = result["code"];
String message = result["message"];
setState(() {
recive = "invokNative 中的回調 code $code message $message ";
});
});
//用來實作 Android iOS 主動觸發 向 Flutter 中發送消息
invokNative("test2");
//用來實作 Flutter 打開 Android iOS 中的一個新的頁面
invokNative("test3");
實作實作 Android 中監聽方法并回調
Android 的 MainActivity 中注冊消息監聽
private MethodChannel mMethodChannel;
//記着要在 onCreat方法中調用
private void methodChannelFunction() {
mMethodChannel = new MethodChannel(getFlutterView(), "flutter_and_native_101");
//設定監聽
mMethodChannel.setMethodCallHandler(
new MethodChannel.MethodCallHandler() {
@Override
public void onMethodCall(MethodCall call, MethodChannel.Result result) {
String lMethod = call.method;
// TODO
if (lMethod.equals("test")) {
Toast.makeText(mContext, "flutter 調用到了 android test", Toast.LENGTH_SHORT).show();
Map<String, Object> resultMap = new HashMap<>();
resultMap.put("message", "result.success 傳回給flutter的資料");
resultMap.put("code", 200);
//發消息至 Flutter
//此方法隻能使用一次
result.success(resultMap);
} else if (lMethod.equals("test2")) {
Toast.makeText(mContext, "flutter 調用到了 android test2", Toast.LENGTH_SHORT).show();
Map<String, Object> resultMap = new HashMap<>();
resultMap.put("message", "android 主動調用 flutter test 方法");
resultMap.put("code", 200);
//主動向Flutter 中發送消息
mMethodChannel.invokeMethod("test", resultMap);
//延遲2秒再主動向 Flutter 中發送消息
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
Map<String, Object> resultMap2 = new HashMap<>();
resultMap2.put("message", "android 主動調用 flutter test 方法");
resultMap2.put("code", 200);
mMethodChannel.invokeMethod("test2", resultMap2);
}
}, 2000);
} else if (lMethod.equals("test3")) {
//測試通過Flutter打開Android Activity
Toast.makeText(mContext, "flutter 調用到了 android test3", Toast.LENGTH_SHORT).show();
Intent lIntent = new Intent(MainActivity.this, TestMethodChannelActivity.class);
MainActivity.this.startActivity(lIntent);
} else {
result.notImplemented();
}
}
}
);
}
實作實作 iOS 中監聽方法 并回調
iOS 的 AppDelegate 中
#include "AppDelegate.h"
#include "GeneratedPluginRegistrant.h"
#import <Flutter/Flutter.h>
//TestViewController 是建立的一個 測試頁面
#import "TestViewController.h"
@implementation AppDelegate{
FlutterMethodChannel* methodChannel;
}
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[GeneratedPluginRegistrant registerWithRegistry:self];
... ...
//FlutterMethodChannel 與 Flutter 之間的雙向通信
[self methodChannelFunction];
... ...
return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
-(void) methodChannelFunction{
FlutterViewController* controller = (FlutterViewController*)self.window.rootViewController;
//建立 FlutterMethodChannel
// flutter_and_native_101 是通信辨別
methodChannel = [FlutterMethodChannel
methodChannelWithName:@"flutter_and_native_101"
binaryMessenger:controller];
//設定監聽
[methodChannel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
// TODO
NSString *method=call.method;
if ([method isEqualToString:@"test"]) {
NSLog(@"flutter 調用到了 ios test");
NSMutableDictionary *dic = [NSMutableDictionary dictionary];
[dic setObject:@"result.success 傳回給flutter的資料" forKey:@"message"];
[dic setObject: [NSNumber numberWithInt:200] forKey:@"code"];
//FlutterResult回調 發消息至 Flutter 中
//此方法隻能調用一次
result(dic);
}else if ([method isEqualToString:@"test2"]) {
NSLog(@"flutter 調用到了 ios test2");
NSMutableDictionary *dic = [NSMutableDictionary dictionary];
[dic setObject:@"result.success 傳回給flutter的資料" forKey:@"message"];
[dic setObject: [NSNumber numberWithInt:200] forKey:@"code"];
//通過此方法 可以主動向Flutter中發送消息
//可以多次調用
[methodChannel invokeMethod:@"test" arguments:dic];
}else if ([method isEqualToString:@"test3"]) {
NSLog(@"flutter 調用到了 ios test3 打開一個新的頁面 ");
TestViewController *testController = [[TestViewController alloc]initWithNibName:@"TestViewController" bundle:nil];
[controller presentViewController:testController animated:YES completion:nil];
}
}];
}
@end
Android 、iOS 原生主動發送消息到 Flutter 中
實作Android 中主動調動調用方法
在MainActivity中,建立了 MethodChannel 的執行個體 mMethodChannel,可以在MainActivity 中直接使用 mMethodChannel 執行個體來向 Flutter 中發送消息。
Map<String, Object> resultMap = new HashMap<>();
resultMap.put("message", "android 主動調用 flutter test 方法");
resultMap.put("code", 200);
//主動向Flutter 中發送消息
mMethodChannel.invokeMethod("test", resultMap);
在其他的 Activity 頁面中,我們就使用不到這個執行個體的,我這裡的一個實作 Android 中建立的Activity 頁面向 Flutter 中發送消息的方法 是廣播機制
在 MainActivity 中注冊廣播,在廣播接收者中通過 BasicMessageChannel 的執行個體 mMessageChannel 來發送消息。
在 Android 中其他的頁面中 發送廣播到 MainActivity 中的廣播接收者中,這樣就實作了Android 中建立的Activity 頁面向 Flutter 中發送消息
public class MainActivity extends FlutterActivity {
... ...
Handler mHandler = new Handler(Looper.myLooper());
private MainReceiver mMainReceiver;
@Override
protected void onDestroy() {
super.onDestroy();
//登出廣播
unregisterReceiver(mMainReceiver);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
... ...
//注冊廣播
mMainReceiver = new MainReceiver();
IntentFilter lIntentFilter = new IntentFilter("android.to.flutter");
registerReceiver(mMainReceiver, lIntentFilter);
}
public class MainReceiver extends BroadcastReceiver {
public MainReceiver() {
}
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context, "接收到自定義的廣播", Toast.LENGTH_SHORT).show();
mHandler.post(new Runnable() {
@Override
public void run() {
Map<String, Object> resultMap2 = new HashMap<>();
resultMap2.put("message", "android 主動調用 flutter test 方法");
resultMap2.put("code", 200);
if (mMethodChannel != null) {
// 向Flutter 發送消息
mMethodChannel.invokeMethod("test2", resultMap2);
}
}
});
}
}
}
實作 Flutter 中監聽調用方法
//建立MethodChannel
// flutter_and_native_101 為通信辨別
// StandardMessageCodec() 為參數傳遞的 編碼方式
static const methodChannel = const MethodChannel('flutter_and_native_101');
//設定消息監聽
Future<dynamic> nativeMessageListener() async {
methodChannel.setMethodCallHandler((resultCall) {
//處理原生 Android iOS 發送過來的消息
MethodCall call = resultCall;
String method = call.method;
Map arguments = call.arguments;
int code = arguments["code"];
String message = arguments["message"];
setState(() {
recive += " code $code message $message and method $method ";
print(recive);
});
});
}
實作 iOS 中主動調動調用方法
#include "AppDelegate.h"
#include "GeneratedPluginRegistrant.h"
#import <Flutter/Flutter.h>
#import "TestViewController.h"
@implementation AppDelegate{
FlutterBasicMessageChannel* messageChannel;
}
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[GeneratedPluginRegistrant registerWithRegistry:self];
//注冊通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(notificationFuncion:) name:@"ios.to.flutter" object:nil];
... ...
return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
... ...
- (void)notificationFuncion: (NSNotification *) notification {
// iOS 中其他頁面向Flutter 中發送消息通過這裡
// 本頁中 可以直接使用 [messageChannel sendMessage:dic];
//處理消息
NSLog(@"notificationFuncion ");
NSMutableDictionary *dic = [NSMutableDictionary dictionary];
if (messageChannel!=nil) {
[dic setObject:@" [messageChannel sendMessage:dic]; 向Flutter 發送消息 " forKey:@"message"];
[dic setObject: [NSNumber numberWithInt:401] forKey:@"code"];
[messageChannel sendMessage:dic];
}
}
- (void)dealloc {
//單條移除觀察者
//[[NSNotificationCenter defaultCenter] removeObserver:self name:@"REFRESH_TABLEVIEW" object:nil];
//移除所有觀察者
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
@end
延伸
更新flutter版本後需要注意的地方:
自 1.12 版本釋出後, Android 平台已可以使用新的 Android 插件 API 。基于 PluginRegistry.Registrar 的 API 不會立刻廢棄,但我們鼓勵您向基于 FlutterPlugin 的 API 進行遷移。
此次涉及最大的調整,應該是 Android 插件的改進 Android plugins APIs 的相關變化,該調整需要使用者重新調整 Flutter 項目中 Android 子產品和插件的代碼進行适配。
新舊版本差異請看這裡:https://github.com/flutter/flutter/wiki/Upgrading-pre-1.12-Android-projects
我在flutter與native原生通信開發完後出現了這樣的錯誤:
[ERROR:flutter/lib/ui/ui_dart_state.cc(166)] Unhandled Exception: MissingPluginException(No implementation found for method getAll on channel plugins.flutter.io/shared_preferences)
解決辦法:https://github.com/flutter/flutter/issues/10912
我解決問題是通過在public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine)方法裡加上了GeneratedPluginRegistrant.registerWith(flutterEngine);
-import io.flutter.app.FlutterActivity;
-import io.flutter.plugin.common.MethodCall;
+import androidx.annotation.NonNull;
+import io.flutter.embedding.android.FlutterActivity;
+import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.plugin.common.MethodChannel;
-import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
-import io.flutter.plugin.common.MethodChannel.Result;
+import io.flutter.plugins.GeneratedPluginRegistrant;
public class MainActivity extends FlutterActivity {
private static final String CHANNEL = "samples.flutter.dev/battery";
- //以前的注冊方法
- @Override
- public void onCreate(Bundle savedInstanceState) {
-
- super.onCreate(savedInstanceState);
- GeneratedPluginRegistrant.registerWith(this);
-
- new MethodChannel(getFlutterView(), CHANNEL).setMethodCallHandler(
- new MethodCallHandler() {
- @Override
- public void onMethodCall(MethodCall call, Result result) {
- // Your existing code
- }
- });
- }
+ //現在的注冊方法
+ @Override
+ public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
+ //GeneratedPluginRegistrant.registerWith(flutterEngine);//父類方法已經實作注冊
+ new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), CHANNEL)
+ .setMethodCallHandler(
+ (call, result) -> {
+ // Your existing code
+ }
+ );
+ }
}
FlutterActivity中configureFlutterEngine方法如下所示
@Override
public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
if (delegate.isFlutterEngineFromHost()) {
// If the FlutterEngine was explicitly built and injected into this FlutterActivity, the
// builder should explicitly decide whether to automatically register plugins via the
// FlutterEngine's construction parameter or via the AndroidManifest metadata.
return;
}
GeneratedPluginRegister.registerGeneratedPlugins(flutterEngine);
}
下面展示一個flutter高版本的簡單用法
Flutter 調用 Android
這裡實作一個 Android 的簡單的功能:彈出一個 AlertDialog,然後在 Flutter 中調用這一功能。
Android 端實作
先在 MainActivity 中實作功能,如下所示。
package com.example.flutterdemo;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import io.flutter.embedding.android.FlutterActivity;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugins.GeneratedPluginRegistrant;
public class MainActivity extends FlutterActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
super.configureFlutterEngine(flutterEngine);
GeneratedPluginRegistrant.registerWith(flutterEngine);
MethodChannel methodChannel = new MethodChannel(flutterEngine.getDartExecutor(), "com.example.platform_channel/dialog");//2
methodChannel.setMethodCallHandler(new MethodChannel.MethodCallHandler() {
@Override
public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
if ("dialog".equals(call.method)) {
if (call.hasArgument("content")) {
result.success("成功");
} else {
result.error("error", "失敗", "content is null");
}
} else {
result.notImplemented();
}
}
});
}
}
Flutter 端實作
在 main.dart 中加入如下代碼。
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
static const platformChannel =
const MethodChannel('com.example.platform_channel/dialog');//1
@override
Widget build(BuildContext context) {
return MaterialApp(
title: "Flutter Demo",
home: Scaffold(
appBar: AppBar(
title: Text("Flutter調用Android"),
),
body: Padding(
padding: EdgeInsets.all(40.0),
child: RaisedButton(
child: Text("調用Dialog"),
onPressed: () {
showDialog("Flutter調用AlertDialog");
},
),
),
),
);
}
void showDialog(String content) async {
var arguments = Map();
arguments['content'] = content;
try {
String result = await platformChannel.invokeMethod('dialog', arguments);//2
print('showDialog ' + result);
} on PlatformException catch (e) {
print('showDialog ' + e.code + e.message + e.details);
} on MissingPluginException catch (e) {
print('showDialog ' + e.message);
}
}
}
3.Android 調用 Flutter
package com.example.flutterdemo;
import android.os.Bundle;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.HashMap;
import java.util.Map;
import io.flutter.embedding.android.FlutterActivity;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugins.GeneratedPluginRegistrant;
public class MainActivity extends FlutterActivity {
private static MethodChannel methodChannel;
private static final String TAG = "MainActivity";
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
super.configureFlutterEngine(flutterEngine);
GeneratedPluginRegistrant.registerWith(flutterEngine);
methodChannel = new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(),"com.example.platform_channel/text");
}
@Override
protected void onResume() {
super.onResume();
Map map = new HashMap();
map.put("content","Android進階三部曲");
methodChannel.invokeMethod("showText", map, new MethodChannel.Result() {//2
@Override
public void success(Object o) {
Log.d(TAG,(String)o);
}
@Override
public void error(String errorCode, String errorMsg, Object errorDetail) {
Log.d(TAG,"errorCode:"+errorCode+" errorMsg:"+errorMsg+" errorDetail:"+(String)errorDetail);
}
@Override
public void notImplemented() {
Log.d(TAG,"notImplemented");
}
});
}
}
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
@override
State<StatefulWidget> createState() {
// TODO: implement createState
return MyAppState();
}
}
class MyAppState extends State<MyApp> {
static const platformChannel =
const MethodChannel('com.example.platform_channel/text');
String textContent = 'Flutter端初始文字';
@override
void initState() {
// TODO: implement initState
super.initState();
platformChannel.setMethodCallHandler((methodCall) async {
switch (methodCall.method) {
case 'showText':
String content = await methodCall.arguments['content'];
if (content != null && content.isNotEmpty) {
setState(() {
textContent = content;
});
return 'success';
} else {
throw PlatformException(
code: 'error', message: '失敗', details: 'content is null');
}
break;
default:
throw MissingPluginException();
}
});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
title: "Flutter Demo",
home: Scaffold(
appBar: AppBar(
title: Text('Android調用Flutter'),
),
body: Padding(
padding: EdgeInsets.all(40.0),
child: Text(textContent),
),
),
);
}
}