天天看點

Flutter Chanel通信流程

目錄介紹

  • 01.flutter和原生之間互動
  • 02.MethodChanel流程
  • 03.MethodChanel使用流程
  • 04.MethodChanel代碼實踐
  • 05.EventChannel流程
  • 06.EventChannel基本流程
  • 07.EventChannel代碼實作
  • 08.BasicMessageChannel流程
  • 09.BasicMessageChannel基本流程
  • 10.BasicMessageChannel代碼實作
  • 11.Channel編解碼器說明
  • 12.Channel通信可以子線程嗎
  • 13.Channel通信傳遞穩定性
  • 14.onActivityResult如何實作

#%E6%8E%A8%E8%8D%90 推薦

#01flutter%E5%92%8C%E5%8E%9F%E7%94%9F%E4%B9%8B%E9%97%B4%E4%BA%A4%E4%BA%92

#11-%E4%BA%A4%E4%BA%92%E7%AE%80%E5%8D%95%E4%BB%8B%E7%BB%8D 1.1 互動簡單介紹

  • 官方給的通信方式
    • 看圖檔,channel通信方式
    • 從底層來看,Flutter和平台端通信的方式是發送異步的二進制消息,該基礎通信方式在Flutter端由BinaryMessages來實作, 而在Android端是一個接口BinaryMessenger,其具體實作為FlutterNativeView,在iOS端是一個協定 FlutterBinaryMessenger,FlutterViewController遵守并實作了這個協定。
  • flutter可以與native之間進行通信,幫助我們使用native提供的能力。
    • 通信是雙向的,我們可以從Native層調用flutter層的dart代碼,同時也可以從flutter層調用Native的代碼。
  • 我們需要使用Platform Channels APIs進行通信,主要包括下面三種:
    • MethodChannel:用于傳遞方法調用(method invocation)
    • EventChannel:用于事件流的發送(event streams)
    • BasicMessageChannel:用于傳遞字元串和半結構化的消息,這裡什麼叫做半結構化?下面會解釋……
  • channel通信是異步還是同步的
    • 為了保證使用者界面在互動過程中的流暢性,無論是從Flutter向Native端發送消息,還是Native向Flutter發送消息都是以異步的形式進行傳遞的。那為何不使用同步來操作,下面會說到……
  • 幾種channel應用場景分析
    • MethodChannel使用場景:無論是Flutter端還是Native端都可以通過MethodChannel向對方平台發送兩端提前定義好的方法名來調用對方平台相對應的消息處理邏輯并且帶回傳回值給被調用方。
    • EventChannel的使用場景:更側重于Native平台主動向Flutter平台,單向給Flutter平台發送消息,Flutter無法傳回任何資料給Native端,EventChannel描述是單通的。可以類比Android裡面的廣播……
    • BasicMessageChannel的使用場景:比如flutter想拍照,拍完照後的圖檔路徑需要傳給flutter,照片的路徑發送可以使用BasicMessageChannel.Reply回複,也可以使用sendMessage主動再發一次消息。個人認為接收消息并回複消息屬于一次通信,是以傾向于使用BasicMessageChannel.Reply。
  • 混合開發通常用那種channel
    • 隻是混合開發通常涉及到兩端頻繁通信,個人更加傾向使用BasicMessageChannel,不分主客,使用和通信更友善。

#12-%E6%A0%B8%E5%BF%83%E7%B1%BB%E9%87%8D%E7%82%B9%E8%AF%B4%E6%98%8E 1.2 核心類重點說明

  • MethodCall
    • 方法調用Java層封裝,主要是資料類
  • MethodChannel
    • 這個主要使用者和dart進行方法通信,類
  • MethodCallHandler
    • 這個java層處理dart層時間的接口,在通訊協定中屬于上層接口,接口
  • BinaryMessageHandler
    • java層和dart層通訊的最底層抽象接口,面向二進制資料包,接口
  • DartMessenger
    • 最底層用于接收JNI發送過來的資料。實作類
  • DartExecutor
    • 配置、引導并開始執行Dart代碼。BinaryMessenger的具體實作類
  • FlutterView
    • NA用來承載flutter的容器view
  • IncomingMethodCallHandler
    • BinaryMessageHandler的實作類,使用者接收底層發送過來的資料包,然後轉發給MethodCallHandler,并對MethodCallHandler 發送過的結果進行打包發送給dart層。實作類
  • FlutterJNI
    • JNI層的封裝用于跟底層引擎側進行通訊

#02methodchannel%E6%B5%81%E7%A8%8B 02.MethodChannel流程

  • 其中最常用的是MethodChanel,MethodChanel的使用與在Android的JNI調用非常類似,但是MethodChanel更加簡單,而且相對于JNI的同步調用MethodChanel的調用是異步的:
  • 從flutter架構圖上可以看到,flutter與native的通信發生在Framework和Engine之間,framewrok内部會将MethodChannel以BinaryMessage的形式與Engine進行資料交換。

#03methodchanel%E4%BD%BF%E7%94%A8%E6%B5%81%E7%A8%8B

#31-flutter%E8%B0%83%E7%94%A8native 3.1 flutter調用native

  • flutter調用native步驟
    • [native] 使用MethodChannel#setMethodCallHandler注冊回調
    • [flutter] 通過MethodChannel#invokeMethod發起異步調用
    • [native] 調用native方法通過Result#success傳回Result,出錯時傳回error
    • [flutter] 收到native傳回的Result
  • 如圖所示

#32-native%E8%B0%83%E7%94%A8flutter 3.2 native調用flutter

  • native調用flutter
    • 與flutter調用native的順序完全一緻,隻是[native]與[flutter]角色反調
  • NA端使用MethodChannel
    • 首先定義Channel名稱,需要保證是唯一的,在Flutter端需要使用同樣的名稱來建立MethodChannel。如果名稱不一樣,則會導緻比對不上……
    • 第一個參數:是messenger,類型是BinaryMessenger,是一個接口,代表消息信使,是消息發送與接收的工具;
    • 第二個參數:是name,就是Channel名稱,和flutter定義的要一樣;
    • 第三個參數:是codec,類型是MethodCodec,代表消息的編解碼器,如果沒有傳該參數,預設使用StandardMethodCodec。

#04methodchanel%E4%BB%A3%E7%A0%81%E5%AE%9E%E8%B7%B5

#41-native%E8%B0%83%E7%94%A8flutter 4.1 native調用flutter

  • 定義好了MethodChannel之後調用setMethodCallHandler()方法設定消息處理回調,參數是MethodHandler類型,需要實作它的onMethodCall()方法。onMethodCall()方法有兩個參數methodCall和result,methodCall記錄了調用的方法資訊,包括方法名和參數,result用于方法的傳回值,可以通過result.success()方法傳回資訊給Flutter端。
private void createChannel() {
    nativeChannel = new MethodChannel(binaryMessenger, METHOD_CHANNEL, StandardMethodCodec.INSTANCE);
    // 注冊Handler實作
    nativeChannel.setMethodCallHandler(new MethodChannel.MethodCallHandler() {
        @Override
        public void onMethodCall(@NonNull MethodCall methodCall, @NonNull MethodChannel.Result result) {
            if ("android".equals(methodCall.method)) {
                //接收來自flutter的指令
                String flutter = methodCall.argument("flutter");
                //傳回給flutter的參數
                result.success("Na收到指令");
            } 
        }
    });
}      
  • 可以通過invokeMethod方法讓NA執行調用flutter方法。那麼執行了flutter方法後需要回傳資料,這個時候就需要用到Result接口呢,代碼如下所示:
HashMap<String , String> map = new HashMap<>();
map.put("invokeKey","你好,這個是從NA傳遞過來的資料");
//nativeChannel.resizeChannelBuffer(100);
nativeChannel.invokeMethod("getFlutterResult", map , new MethodChannel.Result() {
    @SuppressLint("SetTextI18n")
    @Override
    public void success(@Nullable Object result) {
        tvContent.setText("測試内容:"+result);
    }
    @SuppressLint("SetTextI18n")
    @Override
    public void error(String errorCode, @Nullable String errorMessage, @Nullable Object errorDetails) {
        tvContent.setText("測試内容:flutter傳遞給na資料傳遞錯誤");
    }
    @Override
    public void notImplemented() {
    }
});      
  • 事件接收處理端
    • 接收處理回調時onMethodCall(MethodCall call, MethodChannel.Result result)通過methodCall接收事件發送者傳遞回來的資訊,通過Result把處理完的結果發送給事件發送方。
    • 通過methodCall.method:來區分不同函數名(方法)名以執行不同的業務邏輯,
    • 通過methodCall.hasArgument("key"):判斷是否有某個key對應的value
    • 通過methodCall.argument("key"):擷取key對應的value值
    • 通過result.success(object):把處理完的結果傳回給事件發送方
  • 事件發送端
    • 處理事件發送方通過methodChannel.invokeMethod("方法名","要傳遞的參數")把需要傳遞的參數傳遞給事件監聽者。 其中
    • 方法名:不能為空
    • 要傳遞的參數:可以為空,若不為空則必須為可Json序列化的對象。
    • callback:可以為空,若不為空則表示執行了flutter方法後的回調監聽狀态

#42-flutter%E8%B0%83%E7%94%A8native 4.2 flutter調用native

  • Flutter使用MethodChannel
    • 在Flutter端同樣需要定義一個MethodChannel,使用MethodChannel需要引入services.dart包,Channel名稱要和Android端定義的相同。
static const method = const MethodChannel('com.ycbjie.android/method');      
  • 添加監聽NA調用flutter方法的監聽,flutter代碼是setMethodCallHandler方法實作。return則表示flutter回傳給NA的資料操作。
method.setMethodCallHandler(nativeCallHandler);
  // 注冊方法,等待被原生通過invokeMethod喚起
  Future<dynamic> nativeCallHandler(MethodCall methodCall) async {
    switch (methodCall.method) {
      case "getFlutterResult":
      //擷取參數
        String paramsFromNative = await methodCall.arguments["invokeKey"];
        print("原生android傳遞過來的參數為------ $paramsFromNative");
        return "你好,這個是從flutter回傳給NA的資料";
        break;
    }
  }      
  • flutter是如何給NA發送消息的呢,直接調用invokeMethod方法,代碼如下所示
Future<Null> _jumpToNativeWithParams1() async {
    Map<String, String> map = { "flutter": "這是一條來自flutter的參數" };
    String result = await method.invokeMethod('android', map);
    print(result);
  }      

#05eventchannel%E6%B5%81%E7%A8%8B

  • EventChannel用于從native向flutter發送通知事件,例如flutter通過其監聽Android的重力感應變化等。與MethodChannel不同,EventChannel是native到flutter的單向調用,調用是多點傳播(一對多)的,可以類比成Android的Brodecast廣播。

#06eventchannel%E5%9F%BA%E6%9C%AC%E6%B5%81%E7%A8%8B

  • 照例先看一下API使用的基本流程:
    • [native]EventChannel#setStreamHandler注冊Handler實作
    • [native]EventChannel初始化結束後,在StreamHandler#onLister回調中擷取EventSink引用并儲存
    • [flutter]EventChannel#receiveBroadcastStream注冊listener,建立監聽
    • [native]使用EventSink#sucess發送通知事件
    • [flutter]接受到事件通知
    • [native]通知結束時調用endOfStream結束

#07eventchannel%E4%BB%A3%E7%A0%81%E5%AE%9E%E7%8E%B0

  • flutter端
    • 建立EventChannel,注冊“包名/辨別符”的channel名
    • 通過StreamSubscription#listen注冊listener,其中cancelOnError參數表示遇到錯誤時是否自動結束監聽
class _MyHomePageState extends State<MyHomePage> {
  static const EventChannel _channel = const EventChannel('com.example.eventchannel/interop');
  StreamSubscription _streamSubscription;
  String _platformMessage;
  void _enableEventReceiver() {
    _streamSubscription = _channel.receiveBroadcastStream().listen(
        (dynamic event) {
          print('Received event: $event');
          setState(() {
            _platformMessage = event;
          });
        },
        onError: (dynamic error) {
          print('Received error: ${error.message}');
        },
        cancelOnError: true);
  }
  void _disableEventReceiver() {
    if (_streamSubscription != null) {
      _streamSubscription.cancel();
      _streamSubscription = null;
    }
  }
  @override
  initState() {
    super.initState();
    _enableEventReceiver();
  }
  @override
  void dispose() {
    super.dispose();
    _disableEventReceiver();
  }      
  • native(android)端
    • 通過EventChannel#setStreamHandler注冊Handler實作
    • 初始化完成後,擷取eventSink引用并儲存
    • eventSink發送事件通知
    • 通知結束時調用event#endOfStream,此時onCancel會被調用
    • 必要時,可通過evnetSink#error發送錯誤通知,flutter的StreamSubscription#onError會收到通知
class MainActivity: FlutterActivity() {
    private lateinit var channel: EventChannel
    var eventSink: EventSink? = null
    override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
        GeneratedPluginRegistrant.registerWith(flutterEngine)
        channel = EventChannel(flutterEngine.dartExecutor.binaryMessenger, "com.example.eventchannel/interop")
        channel.setStreamHandler(
                object : StreamHandler {
                    override fun onListen(arguments: Any?, events: EventSink) {
                        eventSink = events
                        Log.d("Android", "EventChannel onListen called")
                        Handler().postDelayed({
                            eventSink?.success("Android")
                            //eventSink?.endOfStream()
                            //eventSink?.error("error code", "error message","error details")
                        }, 500)
                    }
                    override fun onCancel(arguments: Any?) {
                        Log.w("Android", "EventChannel onCancel called")
                    }
                })
    }
}      

#08basicmessagechannel%E6%B5%81%E7%A8%8B

  • BasicMessageChannel用于在flutter和native互相發送消息,一方給另一方發送消息,收到消息之後給出回複。

#09basicmessagechannel%E5%9F%BA%E6%9C%AC%E6%B5%81%E7%A8%8B

  • flutter向native發送消息
    • [flutter]建立BasicMessageChannel
    • [native]通過BasicMessageChannel#MessageHandler注冊Handler
    • [flutter]通過BasicMessageChannel#send發送消息
    • [native]BasicMessageChannel#MessageHandler#onMessage中接收消息,然後reply
  • native向flutter發送消息
    • 流程也是一樣的,隻是将[flutter]與[native]反調

#10basicmessagechannel%E4%BB%A3%E7%A0%81%E5%AE%9E%E7%8E%B0

#101flutter%E7%AB%AF 10.1flutter端

  • flutter需要完成以下工作
    • 建立BasicMessageChannel
    • 通過BasicMessageChannel#send發送消息
  • 相對與其他Channel類型的建立,MessageChannel的建立除了channel名以外,還需要指定編碼方式:
BasicMessageChannel(String name, MessageCodec<T> codec, {BinaryMessenger binaryMessenger})      
  • 發送的消息會以二進制的形式進行處理,是以要針對不同類型的數進行二進制編碼
    • 編碼類型 消息格式
    • BinaryCodec 發送二進制消息時
    • JSONMessageCodec 發送Json格式消息時
    • StandardMessageCodec 發送基本型資料時
    • StringCodec 發送String類型消息時
  • 代碼
class _MyHomePageState extends State<MyHomePage> {
  static const _channel = BasicMessageChannel('com.ycbjie.android/basic', StringCodec());
  String _platformMessage;
  void _sendMessage() async {
    final String reply = await _channel.send('Hello World form Dart');
    print(reply);
  }
  @override
  initState() {
    super.initState();
    // Receive messages from platform
    _channel.setMessageHandler((String message) async {
      print('Received message = $message');
      setState(() => _platformMessage = message);
      return 'Reply from Dart';
    });
    // Send message to platform
    _sendMessage();
  }      

#102-nativeandroid%E7%AB%AF 10.2 native(android)端

  • android端完成以下工作:
    • 通過setHandler注冊MessageHandler
    • MessageHandler#onMessage回調中接收到message後,通過reply進行回複
class MainActivity: FlutterActivity() {
    override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
        GeneratedPluginRegistrant.registerWith(flutterEngine)
        val channel = BasicMessageChannel(
                flutterEngine.dartExecutor.binaryMessenger,
                "com.ycbjie.android/basic", StringCodec.INSTANCE)
        // Receive messages from Dart
        channel.setMessageHandler { message, reply ->
            Log.d("Android", "Received message = $message")
            reply.reply("Reply from Android")
        }
        // Send message to Dart
        Handler().postDelayed({
            channel.send("Hello World from Android") { reply ->
                Log.d("Android", "$reply")
            }
        }, 500)
    }
}      

#11channel%E7%BC%96%E8%A7%A3%E7%A0%81%E5%99%A8%E8%AF%B4%E6%98%8E

#111-%E4%BB%80%E4%B9%88%E6%98%AF%E6%B6%88%E6%81%AF%E7%BC%96%E8%A7%A3%E7%A0%81%E5%99%A8 11.1 什麼是消息編解碼器

  • 什麼是消息編解碼器
    • 在Flutter和平台間進行互相通信了,但是收發的資料都是二進制的,這就需要開發者考慮更多的細節,如位元組順序(大小端)和怎麼表示更進階的消息類型,如字元串,map等。
    • 是以,Flutter 還提供了消息編解碼器(Codec), 用于進階資料類型(字元串,map等)和二進制資料(byte)之間的轉換,即消息的序列化和反序列化。
  • 消息編解碼器種類有哪些
    • MethodCodec:方法傳遞的編解碼器抽象,接口
    • JSONMethodCodec:MethodCodec的實作類,會把資料打包成json結構發送給dart,類
    • StandardMethodCodec:MethodCodec的實作類,會把資料打包成預設格式發送給dart,類

#112-%E5%9B%9B%E7%A7%8D%E6%B6%88%E6%81%AF%E7%BC%96%E8%A7%A3%E7%A0%81%E5%99%A8%E7%B1%BB%E5%9E%8B 11.2 四種消息編解碼器類型

  • BinaryCodec
    • MessageCodec的實作類,直接發送二進制資料
    • BinaryCodec是最為簡單的一種Codec,因為其傳回值類型和入參的類型相同,均為二進制格式(Android中為ByteBuffer,iOS中為NSData)。實際上,BinaryCodec在編解碼過程中什麼都沒做,隻是原封不動将二進制資料消息傳回而已。或許你會是以覺得BinaryCodec沒有意義,但是在某些情況下它非常有用,比如使用BinaryCodec可以使傳遞記憶體資料塊時在編解碼階段免于記憶體拷貝。
  • StringCodec
    • MessageCodec的實作類,負責解碼和編碼String類型的消息
    • 使用 UTF-8 編碼格式對字元串資料進行編解碼,在Android平台轉換為 java.util.String 類型
  • JSONMessageCodec
    • MessageCodec的實作類,負責解碼和編碼Json類型的消息
    • JSONMessageCodec用于處理 JSON 資料類型(字元串型,數字型,布爾型,null,隻包含這些類型的數組,和key為string類型,value為這些類型的map),在編碼過程中,資料會被轉換為JSON字元串,然後在使用 UTF-8 格式轉換為位元組型。
  • StandardMessageCodec
    • MessageCodec的實作類,負責解碼和編碼預設類型的消息
    • StandardMessageCodec 可以認為是 JSONMessageCodec 的更新版,能夠處理的資料類型要比 JSONMessageCodec 更普遍一些,且在處理 int 型資料時,會根據 int 資料的大小來轉為平台端的32位類型(int)或者是64位類型(long),StandardMessageCodec 也是 Flutter Platform channel 的預設編解碼器

#113-%E7%BC%96%E7%A0%81%E5%99%A8%E7%9A%84%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90%E4%B8%8B 11.3 編碼器的源碼分析下

  • 首先看下MessageCodec
abstract class MessageCodec<T> {
  ByteData encodeMessage(T message);
  T decodeMessage(ByteData message);
}      

#114-%E7%9C%8Bstandardmessagecodec 11.4 看StandardMessageCodec

  • StandardMessageCodec稍微複雜
    • StandardMessageCodec在寫入資料的時候,顯示寫入這個資料的類型值定義,然後在寫入其對應的具體值,什麼意思呢?
  • 檢視一下如何寫入指定類型的值,代碼如下所示:
protected void writeValue(ByteArrayOutputStream stream, Object value) {
    if (value == null || value.equals(null)) {
      stream.write(NULL);
    } else if (value == Boolean.TRUE) {
      stream.write(TRUE);
    } else if (value == Boolean.FALSE) {
      stream.write(FALSE);
    } else if (value instanceof Number) {
      if (value instanceof Integer || value instanceof Short || value instanceof Byte) {
        stream.write(INT);
        writeInt(stream, ((Number) value).intValue());
      } else if (value instanceof Long) {
        stream.write(LONG);
        writeLong(stream, (long) value);
      } else if (value instanceof Float || value instanceof Double) {
        stream.write(DOUBLE);
        writeAlignment(stream, 8);
        writeDouble(stream, ((Number) value).doubleValue());
      } else if (value instanceof BigInteger) {
        stream.write(BIGINT);
        writeBytes(stream, ((BigInteger) value).toString(16).getBytes(UTF8));
      } else {
        throw new IllegalArgumentException("Unsupported Number type: " + value.getClass());
      }
    } else if (value instanceof String) {
      stream.write(STRING);
      writeBytes(stream, ((String) value).getBytes(UTF8));
    } else if (value instanceof byte[]) {
      stream.write(BYTE_ARRAY);
      writeBytes(stream, (byte[]) value);
    } else if (value instanceof int[]) {
      stream.write(INT_ARRAY);
      final int[] array = (int[]) value;
      writeSize(stream, array.length);
      writeAlignment(stream, 4);
      for (final int n : array) {
        writeInt(stream, n);
      }
    } else if (value instanceof long[]) {
      stream.write(LONG_ARRAY);
      final long[] array = (long[]) value;
      writeSize(stream, array.length);
      writeAlignment(stream, 8);
      for (final long n : array) {
        writeLong(stream, n);
      }
    } else if (value instanceof double[]) {
      stream.write(DOUBLE_ARRAY);
      final double[] array = (double[]) value;
      writeSize(stream, array.length);
      writeAlignment(stream, 8);
      for (final double d : array) {
        writeDouble(stream, d);
      }
    } else if (value instanceof List) {
      stream.write(LIST);
      final List<?> list = (List) value;
      writeSize(stream, list.size());
      for (final Object o : list) {
        writeValue(stream, o);
      }
    } else if (value instanceof Map) {
      stream.write(MAP);
      final Map<?, ?> map = (Map) value;
      writeSize(stream, map.size());
      for (final Entry<?, ?> entry : map.entrySet()) {
        writeValue(stream, entry.getKey());
        writeValue(stream, entry.getValue());
      }
    } else {
      throw new IllegalArgumentException("Unsupported value: " + value);
    }
  }      
  • 檢視一下如何讀取指定類型的值,代碼如下所示:
protected Object readValueOfType(byte type, ByteBuffer buffer) {
    final Object result;
    switch (type) {
      case NULL:
        result = null;
        break;
      case TRUE:
        result = true;
        break;
      case FALSE:
        result = false;
        break;
      case INT:
        result = buffer.getInt();
        break;
      case LONG:
        result = buffer.getLong();
        break;
      case BIGINT:
        {
          final byte[] hex = readBytes(buffer);
          result = new BigInteger(new String(hex, UTF8), 16);
          break;
        }
      case DOUBLE:
        readAlignment(buffer, 8);
        result = buffer.getDouble();
        break;
      case STRING:
        {
          final byte[] bytes = readBytes(buffer);
          result = new String(bytes, UTF8);
          break;
        }
      case BYTE_ARRAY:
        {
          result = readBytes(buffer);
          break;
        }
      case INT_ARRAY:
        {
          final int length = readSize(buffer);
          final int[] array = new int[length];
          readAlignment(buffer, 4);
          buffer.asIntBuffer().get(array);
          result = array;
          buffer.position(buffer.position() + 4 * length);
          break;
        }
      case LONG_ARRAY:
        {
          final int length = readSize(buffer);
          final long[] array = new long[length];
          readAlignment(buffer, 8);
          buffer.asLongBuffer().get(array);
          result = array;
          buffer.position(buffer.position() + 8 * length);
          break;
        }
      case DOUBLE_ARRAY:
        {
          final int length = readSize(buffer);
          final double[] array = new double[length];
          readAlignment(buffer, 8);
          buffer.asDoubleBuffer().get(array);
          result = array;
          buffer.position(buffer.position() + 8 * length);
          break;
        }
      case LIST:
        {
          final int size = readSize(buffer);
          final List<Object> list = new ArrayList<>(size);
          for (int i = 0; i < size; i++) {
            list.add(readValue(buffer));
          }
          result = list;
          break;
        }
      case MAP:
        {
          final int size = readSize(buffer);
          final Map<Object, Object> map = new HashMap<>();
          for (int i = 0; i < size; i++) {
            map.put(readValue(buffer), readValue(buffer));
          }
          result = map;
          break;
        }
      default:
        throw new IllegalArgumentException("Message corrupted");
    }
    return result;
  }      

#115-%E5%A6%82%E4%BD%95%E9%80%89%E6%8B%A9%E5%90%88%E9%80%82%E7%BC%96%E8%A7%A3%E7%A0%81%E5%99%A8 11.5 如何選擇合适編解碼器

  • 編解碼的實作類并不複雜
    • 可以先了解一下這個比較能更好的了解資料傳遞,其實不關java上層使用那種方式,最終傳遞給底層資料都是固定格式,約定統一的資料格式雙方才能識别出來,正常的來說用預設的編解碼格式就可以了。
  • 關于四種解碼器使用場景
      • 暫未找到使用的場景
      • 适用發送單一的字元串資料,資料量單一的情況,比如LifecycleChannel
public void appIsInactive() {
  Log.v(TAG, "Sending AppLifecycleState.inactive message.");
  channel.send("AppLifecycleState.inactive");
}      
      • 适用資料量比較複雜的情況,比如有攜帶多個資料字段的傳遞,比如KeyEventChannel
public void keyDown(@NonNull FlutterKeyEvent keyEvent) {
  Map<String, Object> message = new HashMap<>();
  message.put("type", "keydown");
  message.put("keymap", "android");
  encodeKeyEvent(keyEvent, message);
 
  channel.send(message);
}      
      • 預設的資料編解碼,絕大多數的情況下使用預設的就可以了。比如:MethodChannel,EventChannel

#12channel%E9%80%9A%E4%BF%A1%E5%8F%AF%E4%BB%A5%E5%AD%90%E7%BA%BF%E7%A8%8B%E5%90%97

#121-android%E5%8F%91%E9%80%81%E9%80%9A%E4%BF%A1%E4%BF%A1%E6%81%AF 12.1 Android發送通信資訊

  • 首先看一下Android發送通信資訊,主要分析入口是:nativeChannel.invokeMethod("setNum", a , null);
public void invokeMethod(String method, @Nullable Object arguments, Result callback) {
messenger.send(
    name,
    codec.encodeMethodCall(new MethodCall(method, arguments)),
    callback == null ? null : new IncomingResultHandler(callback));
}      
  • 最終定位找到DartMessenger類的send方法,代碼如下所示:
@Override
public void send(
      @NonNull String channel,
      @Nullable ByteBuffer message,
      @Nullable BinaryMessenger.BinaryReply callback) {
    Log.v(TAG, "Sending message with callback over channel '" + channel + "'");
    int replyId = 0;
    if (callback != null) {
      replyId = nextReplyId++;
      pendingReplies.put(replyId, callback);
    }
    if (message == null) {
      flutterJNI.dispatchEmptyPlatformMessage(channel, replyId);
    } else {
      flutterJNI.dispatchPlatformMessage(channel, message, message.position(), replyId);
    }
}      
  • 嘗試一下子線程發送消息,發現會出現崩潰
new Thread(new Runnable() {
    @Override
    public void run() {
        nativeChannel.invokeMethod("setNum", a , null);
    }
}).start();      
  • 崩潰資訊如下所示
java.lang.RuntimeException: Methods marked with @UiThread must be executed on the main thread. Current thread: Thread-2574
    at io.flutter.embedding.engine.FlutterJNI.ensureRunningOnMainThread(FlutterJNI.java:992)
    at io.flutter.embedding.engine.FlutterJNI.dispatchPlatformMessage(FlutterJNI.java:736)
    at io.flutter.embedding.engine.dart.DartMessenger.send(DartMessenger.java:72)
    at io.flutter.embedding.engine.dart.DartExecutor$DefaultBinaryMessenger.send(DartExecutor.java:370)
    at io.flutter.plugin.common.MethodChannel.invokeMethod(MethodChannel.java:94)
    at com.ycbjie.ycandroid.channel.MethodChannelActivity.test1000(MethodChannelActivity.java:302)
    at com.ycbjie.ycandroid.channel.MethodChannelActivity.access$000(MethodChannelActivity.java:46)
    at com.ycbjie.ycandroid.channel.MethodChannelActivity$1.run(MethodChannelActivity.java:98)
    at java.lang.Thread.run(Thread.java:818)      

#12flutter%E7%BB%99na%E5%8F%91%E9%80%81%E6%95%B0%E6%8D%AE 12.Flutter給NA發送資料

  • 從method.invokeMethod('android', map);開始分析
@optionalTypeArgs
  Future<T> _invokeMethod<T>(String method, { bool missingOk, dynamic arguments }) async {
    assert(method != null);
    final ByteData result = await binaryMessenger.send(
      name,
      codec.encodeMethodCall(MethodCall(method, arguments)),
    );
    return codec.decodeEnvelope(result) as T;
  }      
  • 最後定位到_DefaultBinaryMessenger類中的send方法
Future<ByteData> _sendPlatformMessage(String channel, ByteData message) {
    final Completer<ByteData> completer = Completer<ByteData>();
    // ui.window is accessed directly instead of using ServicesBinding.instance.window
    // because this method might be invoked before any binding is initialized.
    // This issue was reported in #27541. It is not ideal to statically access
    // ui.window because the Window may be dependency injected elsewhere with
    // a different instance. However, static access at this location seems to be
    // the least bad option.
    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;
  }      

#13channel%E9%80%9A%E4%BF%A1%E4%BC%A0%E9%80%92%E7%A8%B3%E5%AE%9A%E6%80%A7

  • channel傳遞資料是否會丢失,如何測試呢?可以模拟,Android給flutter發送1000條資訊,然後flutter給Android發送1000條資訊,接下來看一下如何測試:

#131-android%E7%BB%99flutter%E5%8F%91%E9%80%81%E6%95%B0%E6%8D%AE 13.1 Android給flutter發送資料

  • Android給flutter發送資料,代碼如下所示
int a = 0;
private void test1000() {
    if (nativeChannel!=null){
        for (int i=0 ; i<1000 ; i++){
            a++;
            Log.i("測試資料test1000 :", a+"");
            nativeChannel.invokeMethod("setNum", a , new MethodChannel.Result() {
                @SuppressLint("SetTextI18n")
                @Override
                public void success(@Nullable Object result) {
                    if (result==null){
                        return;
                    }
                    Log.i("測試資料:",result.toString());
                }
                @SuppressLint("SetTextI18n")
                @Override
                public void error(String errorCode, @Nullable String errorMessage, @Nullable Object errorDetails) {
                    Log.i("測試資料異常:",errorMessage);
                }
                @Override
                public void notImplemented() {
                }
            });
        }
    }
}      
  • flutter接收資料并回傳資料給Android
//接受na端傳遞過來的方法,并做出響應邏輯處理
  method.setMethodCallHandler(nativeCallHandler);
  // 注冊方法,等待被原生通過invokeMethod喚起
  Future<dynamic> nativeCallHandler(MethodCall methodCall) async {
    switch (methodCall.method) {
      case "setNum":
      //擷取參數
        int message = await methodCall.arguments;
        print("原生android傳遞過來的參數為------ $message");
        return "flutter回調資料:${message.toString()}";
        break;
    }
  }      

#132-%E6%9F%A5%E7%9C%8B%E6%95%B0%E6%8D%AE%E7%A8%B3%E5%AE%9A%E6%80%A7%E5%92%8C%E5%8F%8A%E6%97%B6%E6%80%A7 13.2 檢視資料穩定性和及時性

  • Android發送消息日志
2021-08-26 11:58:03.837 23106-23106/com.ycbjie.ychybrid I/測試資料test1000:: 990
2021-08-26 11:58:03.837 23106-23106/com.ycbjie.ychybrid I/測試資料test1000:: 991
2021-08-26 11:58:03.837 23106-23106/com.ycbjie.ychybrid I/測試資料test1000:: 992
2021-08-26 11:58:03.837 23106-23106/com.ycbjie.ychybrid I/測試資料test1000:: 993
2021-08-26 11:58:03.838 23106-23106/com.ycbjie.ychybrid I/測試資料test1000:: 994
2021-08-26 11:58:03.838 23106-23106/com.ycbjie.ychybrid I/測試資料test1000:: 995
2021-08-26 11:58:03.838 23106-23106/com.ycbjie.ychybrid I/測試資料test1000:: 996
2021-08-26 11:58:03.838 23106-23106/com.ycbjie.ychybrid I/測試資料test1000:: 997
2021-08-26 11:58:03.838 23106-23106/com.ycbjie.ychybrid I/測試資料test1000:: 998
2021-08-26 11:58:03.838 23106-23106/com.ycbjie.ychybrid I/測試資料test1000:: 999
2021-08-26 11:58:03.838 23106-23106/com.ycbjie.ychybrid I/測試資料test1000:: 1000      
  • flutter接收Android發送資料
2021-08-26 11:52:39.708 23106-23627/com.ycbjie.ychybrid I/flutter: 原生android傳遞過來的參數為------ 992
2021-08-26 11:52:39.709 23106-23627/com.ycbjie.ychybrid I/flutter: 原生android傳遞過來的參數為------ 993
2021-08-26 11:52:39.709 23106-23627/com.ycbjie.ychybrid I/flutter: 原生android傳遞過來的參數為------ 994
2021-08-26 11:52:39.709 23106-23627/com.ycbjie.ychybrid I/flutter: 原生android傳遞過來的參數為------ 995
2021-08-26 11:52:39.709 23106-23627/com.ycbjie.ychybrid I/flutter: 原生android傳遞過來的參數為------ 996
2021-08-26 11:52:39.710 23106-23627/com.ycbjie.ychybrid I/flutter: 原生android傳遞過來的參數為------ 997
2021-08-26 11:52:39.710 23106-23627/com.ycbjie.ychybrid I/flutter: 原生android傳遞過來的參數為------ 998
2021-08-26 11:52:39.710 23106-23627/com.ycbjie.ychybrid I/flutter: 原生android傳遞過來的參數為------ 999
2021-08-26 11:52:39.710 23106-23627/com.ycbjie.ychybrid I/flutter: 原生android傳遞過來的參數為------ 1000      
  • flutter收到消息後,回調給Android資料。Android監聽回調資料,列印日志如下
2021-08-26 11:58:03.964 23106-23106/com.ycbjie.ychybrid I/測試資料:: flutter回調資料:600
2021-08-26 11:58:03.964 23106-23106/com.ycbjie.ychybrid I/測試資料:: flutter回調資料:601
2021-08-26 11:58:03.964 23106-23106/com.ycbjie.ychybrid I/測試資料:: flutter回調資料:602
2021-08-26 11:58:03.965 23106-23106/com.ycbjie.ychybrid I/測試資料:: flutter回調資料:603
2021-08-26 11:58:03.965 23106-23106/com.ycbjie.ychybrid I/測試資料:: flutter回調資料:604
2021-08-26 11:58:03.965 23106-23106/com.ycbjie.ychybrid I/測試資料:: flutter回調資料:605
2021-08-26 11:58:03.965 23106-23106/com.ycbjie.ychybrid I/測試資料:: flutter回調資料:606
2021-08-26 11:58:03.966 23106-23106/com.ycbjie.ychybrid I/測試資料:: flutter回調資料:607      
  • 然後再看一波列印日志,如下所示
2021-08-26 12:07:09.158 24237-24990/com.ycbjie.ychybrid I/flutter: 測試資料,flutter接收NA資料: 1
2021-08-26 12:07:09.237 24237-24237/com.ycbjie.ychybrid I/測試資料,NA接收flutter回調:: flutter回調資料:1
2021-08-26 12:07:09.240 24237-24990/com.ycbjie.ychybrid I/flutter: 測試資料,flutter接收NA資料: 2
2021-08-26 12:07:09.241 24237-24990/com.ycbjie.ychybrid I/flutter: 測試資料,flutter接收NA資料: 3
2021-08-26 12:07:09.241 24237-24237/com.ycbjie.ychybrid I/測試資料,NA接收flutter回調:: flutter回調資料:2
2021-08-26 12:07:09.241 24237-24237/com.ycbjie.ychybrid I/測試資料,NA接收flutter回調:: flutter回調資料:3
2021-08-26 12:07:09.241 24237-24990/com.ycbjie.ychybrid I/flutter: 測試資料,flutter接收NA資料: 4
2021-08-26 12:07:09.241 24237-24237/com.ycbjie.ychybrid I/測試資料,NA接收flutter回調:: flutter回調資料:4
2021-08-26 12:07:09.241 24237-24990/com.ycbjie.ychybrid I/flutter: 測試資料,flutter接收NA資料: 5
2021-08-26 12:07:09.241 24237-24237/com.ycbjie.ychybrid I/測試資料,NA接收flutter回調:: flutter回調資料:5
2021-08-26 12:07:09.242 24237-24990/com.ycbjie.ychybrid I/flutter: 測試資料,flutter接收NA資料: 6
2021-08-26 12:07:09.242 24237-24237/com.ycbjie.ychybrid I/測試資料,NA接收flutter回調:: flutter回調資料:6
2021-08-26 12:07:09.242 24237-24990/com.ycbjie.ychybrid I/flutter: 測試資料,flutter接收NA資料: 7
2021-08-26 12:07:09.242 24237-24237/com.ycbjie.ychybrid I/測試資料,NA接收flutter回調:: flutter回調資料:7
2021-08-26 12:07:09.272 24237-24990/com.ycbjie.ychybrid I/flutter: 測試資料,flutter接收NA資料: 131
2021-08-26 12:07:09.273 24237-24237/com.ycbjie.ychybrid I/測試資料,NA接收flutter回調:: flutter回調資料:131
2021-08-26 12:07:09.273 24237-24990/com.ycbjie.ychybrid I/flutter: 測試資料,flutter接收NA資料: 132
2021-08-26 12:07:09.273 24237-24237/com.ycbjie.ychybrid I/測試資料,NA接收flutter回調:: flutter回調資料:132
2021-08-26 12:07:09.273 24237-24990/com.ycbjie.ychybrid I/flutter: 測試資料,flutter接收NA資料: 133
2021-08-26 12:07:09.273 24237-24237/com.ycbjie.ychybrid I/測試資料,NA接收flutter回調:: flutter回調資料:133
2021-08-26 12:07:09.273 24237-24990/com.ycbjie.ychybrid I/flutter: 測試資料,flutter接收NA資料: 134
2021-08-26 12:07:09.273 24237-24237/com.ycbjie.ychybrid I/測試資料,NA接收flutter回調:: flutter回調資料:134
2021-08-26 12:07:09.273 24237-24990/com.ycbjie.ychybrid I/flutter: 測試資料,flutter接收NA資料: 135
2021-08-26 12:07:09.274 24237-24237/com.ycbjie.ychybrid I/測試資料,NA接收flutter回調:: flutter回調資料:135
2021-08-26 12:07:09.274 24237-24990/com.ycbjie.ychybrid I/flutter: 測試資料,flutter接收NA資料: 136
2021-08-26 12:07:09.274 24237-24237/com.ycbjie.ychybrid I/測試資料,NA接收flutter回調:: flutter回調資料:136
2021-08-26 12:07:09.274 24237-24990/com.ycbjie.ychybrid I/flutter: 測試資料,flutter接收NA資料: 137
2021-08-26 12:07:09.274 24237-24237/com.ycbjie.ychybrid I/測試資料,NA接收flutter回調:: flutter回調資料:137      
  • 是以檢視日志可以得知,傳遞資料保證了資料的時效性,發送消息和接收消息是一一對應。并沒有失敗的情況,是以傳遞資料是穩定的。
  • 重點說明,有小夥伴有疑惑,你這周遊1000次,每次傳遞都是int值,那實際開發中可能傳遞大json,資料量大的情況會怎樣,這個下面會說到……

#14onactivityresult%E5%A6%82%E4%BD%95%E5%AE%9E%E7%8E%B0

  • 先說一個場景
    • 在開發中我們經常會遇到關閉目前頁面的同時傳回給上一個頁面資料的場景,在Android中是通過startActivityForResult和onActivityResult()實作的。
    • 而純Flutter頁面之間可以通過在Navigator.of(context).pop()方法中添加參數來實作,那麼對于Flutter頁面和Android原生頁面之間如何在傳回上一頁時傳遞資料呢,通過MethodChannel就可以實作。

#141-flutter%E9%A1%B5%E9%9D%A2%E8%BF%94%E5%9B%9Eandroid%E5%8E%9F%E7%94%9F%E9%A1%B5%E9%9D%A2 14.1 Flutter頁面傳回Android原生頁面

  • 在Flutter端調用原生的傳回方法就可以了,首先在Flutter頁面添加一個按鈕,點選按鈕傳回原生頁面,代碼如下:
new Padding(
    padding: const EdgeInsets.only(
        left: 10.0, top: 10.0, right: 10.0),
    child: new RaisedButton(
        textColor: Colors.black,
        child: new Text('傳回上一界面,并攜帶資料'),
        onPressed: () {
            Map<String, dynamic> map = {'message': '我從Flutter頁面回來了'};
            String result = await method.invokeMethod('goBackWithResult', map);
        }),
  ),      
  • Android端依然是通過判斷methodCall.method的值來執行指定的代碼,通過methodCall.argument()擷取Flutter傳遞的參數。
nativeChannel.setMethodCallHandler(new MethodChannel.MethodCallHandler() {
    @Override
    public void onMethodCall(@NonNull MethodCall methodCall, @NonNull MethodChannel.Result result) {
        if ("goBackWithResult".equals(methodCall.method)) {
            // 傳回上一頁,攜帶資料
            Intent backIntent = new Intent();
            backIntent.putExtra("message", (String) methodCall.argument("message"));
            setResult(RESULT_OK, backIntent);
            finish();
        }
    }
});      

#142-android%E5%8E%9F%E7%94%9F%E9%A1%B5%E9%9D%A2%E8%BF%94%E5%9B%9Eflutter%E9%A1%B5%E9%9D%A2 14.2 Android原生頁面傳回Flutter頁面

  • Android原生頁面傳回Flutter頁面
    • 這種情況需要原生來調用Flutter代碼,和Flutter調用原生方法的步驟是一樣的。首先觸發flutter頁面按鈕,從flutter跳轉na頁面,然後觸發na頁面傳回操作,傳回到Flutter頁面,并傳遞資料。
  • 首先是flutter頁面觸發跳轉到na頁面的代碼操作邏輯,代碼如下所示
//flutter
  new Padding(
    padding: const EdgeInsets.only(left: 10.0, top: 10.0, right: 10.0),
    child: new RaisedButton(
        textColor: Colors.black,
        child: new Text('跳轉到原生逗比界面,回調結果:$_methodResult1'),
        onPressed: () {
          _jumpToNative();
        }),
  ),
//na,注意na接收到flutter指令後,na是調用startActivityForResult操作跳轉到na的新頁面
nativeChannel.setMethodCallHandler(new MethodChannel.MethodCallHandler() {
    @Override
    public void onMethodCall(@NonNull MethodCall methodCall, @NonNull MethodChannel.Result result) {
        if ("doubi".equals(methodCall.method)) {
            //接收來自flutter的指令
            //跳轉到指定Activity
            Intent intent = new Intent(MethodChannelActivity.this, MethodResultActivity.class);
            startActivityForResult(intent,RESULT_OK2);
            //傳回給flutter的參數
            result.success("Na收到指令");
        }
    }
});      
  • 然後接下來的一步是,從NA傳回到flutter頁面,然後再去調用flutter方法。具體操作代碼如下所示
//na flutter觸發打開na的新的頁面
public class MethodResultActivity extends AppCompatActivity {
    @SuppressLint("SetTextI18n")
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_android);
        TextView tv = findViewById(R.id.tv);
        tv.setText("flutter頁面打開NA頁面,測試Android原生頁面傳回Flutter頁面");
        tv.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent = new Intent();
                intent.putExtra("message", "我從原生頁面回來了");
                setResult(RESULT_OK2, intent);
                finish();
            }
        });
    }
}
// na flutter承載容器的na的原生頁面
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (data != null && resultCode==RESULT_OK2) {
        // MethodResultActivity傳回的資料
        String message = data.getStringExtra("message");
        Map<String, Object> result = new HashMap<>();
        result.put("message", message);
        // 調用Flutter端定義的方法
        nativeChannel.invokeMethod("onActivityResult", result, new MethodChannel.Result() {
            @SuppressLint("SetTextI18n")
            @Override
            public void success(@Nullable Object result) {
                tvContent.setText("測試内容2:"+result);
            }
            @SuppressLint("SetTextI18n")
            @Override
            public void error(String errorCode, @Nullable String errorMessage, @Nullable Object errorDetails) {
                tvContent.setText("測試内容:flutter傳遞給na資料傳遞錯誤2");
            }
            @Override
            public void notImplemented() {
            }
        });
    }
}
//flutter
  Future<dynamic> handler(MethodCall call) async {
    switch (call.method) {
      case 'onActivityResult':
        // 擷取原生頁面傳遞的參數
        print(call.arguments['message']);
        return "你好,這個是從flutter傳遞過來的資料";
    }
  }
  flutterChannel.setMethodCallHandler(handler);      

#%E6%8E%A8%E8%8D%90-2

繼續閱讀