天天看點

Flutter與Android iOS 的雙向通信

本文章中的完整代碼在這裡:​​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)和宿主(平台)之間傳遞,如下圖所示:

Flutter與Android iOS 的雙向通信

消息和響應以異步的形式進行傳遞,以確定使用者界面能夠保持響應。

簡單介紹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 方式實作出來了)

Flutter與Android iOS 的雙向通信

ios 中的效果(本圖還是 BasicMessageChannel 的效果圖,隻不過是通過 MethodChannel 方式實作出來了)

Flutter與Android iOS 的雙向通信

前言

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