Flutter Plugin插件开发之利用BasicMessageChannel传递复杂对象

Flutter Plugin插件开发之利用BasicMessageChannel在Platform和Flutter之间通信这篇文章中我们介绍了如何通过BasicMessageChannel通信,但是示例中只简单演示了传递String消息,如果消息中包含复杂的对象,又该如何使用呢?

发送String消息的时候我们用的是StringCodec的消息编解码器

至于发送复杂的消息,我们需要用JSONMessageCodec编解码器

JSONMessageCodec源码解析

一开始我以为用JSONMessageCodec发送消息可以直接用BasicMessageChannel.send()把整个java对象发送过去,但是在JSONMessageCodec的encodeMessage()方法内的wrapped.toString()处却报空指针的错误,说明JSONUtils.wrap()方法返回了null。

public ByteBuffer encodeMessage(Object message) {
    if (message == null) {
        return null;
    } else {
        Object wrapped = JSONUtil.wrap(message);
        // JSONMessageCodec最总还是依赖StringCodec编解码器
        return wrapped instanceof String ? StringCodec.INSTANCE.encodeMessage(JSONObject.quote((String)wrapped)) : StringCodec.INSTANCE.encodeMessage(wrapped.toString());
    }
}

那就研究下JSONUtils.wrap()这个方法里的源码,注意看以下源码中我添加的注释,可以找打发生异常的原因,因为wrap()方法并不支持封装自定义的java对象,会返回null,导致了NullPointerException异常异常

public static Object wrap(Object o) {
    if (o == null) { // 支持null,返回一个JSONObject.NULL对象表示null,而不是直接返回null
        return JSONObject.NULL;
    } else if (!(o instanceof JSONArray) && !(o instanceof JSONObject)) { // 支持JSONObject和JSONArray
        if (o.equals(JSONObject.NULL)) {
            return o;
        } else {
            try {
                Iterator var2;
                JSONArray result;
                if (o instanceof Collection) { // 支持Collection集合
                    result = new JSONArray();
                    var2 = ((Collection)o).iterator();

                    while(var2.hasNext()) {
                        Object e = var2.next();
                        result.put(wrap(e));
                    }

                    return result;
                }

                if (o.getClass().isArray()) { // 支持数组
                    result = new JSONArray();
                    int length = Array.getLength(o);

                    for(int i = 0; i < length; ++i) {
                        result.put(wrap(Array.get(o, i)));
                    }

                    return result;
                }

                if (o instanceof Map) { // 支持Map
                    JSONObject result = new JSONObject();
                    var2 = ((Map)o).entrySet().iterator();

                    while(var2.hasNext()) {
                        Entry<?, ?> entry = (Entry)var2.next();
                        result.put((String)entry.getKey(), wrap(entry.getValue()));
                    }

                    return result;
                }

                // 支持Boolean、Byte、Character、Double、Float、Integer、Long、Short、String等java基本数据类型
                if (o instanceof Boolean || o instanceof Byte || o instanceof Character || o instanceof Double || o instanceof Float || o instanceof Integer || o instanceof Long || o instanceof Short || o instanceof String) {
                    return o;
                }

                // 支持包名以"java."开头的类,返回toString()内容
                if (o.getClass().getPackage().getName().startsWith("java.")) {
                    return o.toString();
                }
            } catch (Exception var4) {

            }
            // 若不符合以上类型的数据则直接返回null,而不是返回JSONObject.NULL
            // 所以传入自定义的java对象,会走到这里,返回的null对象导致了NullPointerException异常
            // 所以JSONMessageCodec不支持传输自定义的赋值java对象,需转成以上几种支持的类型
            return null;
        }
    } else { // 直接返回JSONObject或JSONArray
        return o;
    }
}

由以上源码可以知道JSONMessageCodec编解码器是通过JSONUtils.wrap()这个方法这个方法来封装处理数据,支持以下数据类型:

  • null:返回JSONObject.NULL
  • JSONObject和JSONArray:直接返回不做任何处理
  • Collection:封装成JSONArray返回
  • Array数组:封装成JSONArray返回
  • Map:封装成JSONObject返回
  • 基本数据类型:Boolean、Byte、Character、Double、Float、Integer、Long、Short、String,直接返回
  • 包名以"java."开头的类:返回toString()内容

虽然JSONMessageCodec不支持传输复杂的java对象,但是我们可以把java对象转成JSONMessageCodec支持的对象类型,在接收端再把它重新解析出来,以下是几种解决的思路:

  1. java对象转成Collection,约定不同位置存放对象的不同属性
  2. java对象转成Array,约定不同位置存放对象的不同属性
  3. java对象转成Map,约定不同的键存放不同的对象属性
  4. 自定义编解码器,实现MessageCodec接口的encodeMessage和decodeMessage方法

第四种方法自定义解码器可以使用Gson等json解析库,支持直接把java对象解析成json,当然在Flutter端也需要自定义一个对应的编解码器。关于自定义编解码器本文不再做深入的研究,以后会开另一篇文章讨论,有兴趣的读者也可以自己深入研究

1、2、3方法的思路都差不多,本文就以方法2为例继续为大家写个示例

实现代码

Android端代码

假设我们要写一个插件,可以获取各种小动物的对象,比如猫狗,先定义两个猫狗类,然后定义一个Zoo类表示Android端的原生api,用于获取小动物对象

public class Dog {
    public String name;
    public int age;
    public String owner;
}

public class Cat {
    public String name;
    public int age;
    public String home;
}

public class Zoo {
    public static Dog getDog() {
        Dog dog = new Dog();
        dog.name = "阿狗";
        dog.age = 1;
        dog.owner ="野猿新一";
    }

    public static CatgetCat() {
        Cat cat= new Cat();
        cat.name = "阿狗";
        cat.age = 1;
        cat.home ="野猿新一的家";
    }
}

 

在onMethodCall方法中根据method name调用不同的api获取不同的动物对象,然后用List保存不同对象的属性,其中第一个元素保存的是对象的类型,这样在Flutter端接收的时候,先取出第一个元素判断是什么类型的对象,然后再取出相应的属性拼装成新的动物对象

package com.himmy.plugin_version;

import android.os.Build;
import io.flutter.plugin.common.BasicMessageChannel;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.PluginRegistry;
import io.flutter.plugin.common.StringCodec;

public class PluginZoo implements MethodChannel.MethodCallHandler {

    private static BasicMessageChannel messageChannel;

    public static void registerWith(PluginRegistry.Registrar registrar) {
        MethodChannel channel = new MethodChannel(registrar.messenger(), "plugin_zoo");
        channel.setMethodCallHandler(new PluginVersionPlugin());
        messageChannel = new BasicMessageChannel(registrar.messenger(), "plugin_zoo", StringCodec.INSTANCE);
    }

    @Override
    public void onMethodCall(MethodCall methodCall, MethodChannel.Result result) {
        if ("getDog".equals(methodCall.method)) {
            Dog dog = Zoo.getDog();
            List<Object> msg = new ArrayList<>();
            msg.add("Dog");
            msg.add(dog.name);
            msg.add(dog.age);
            msg.add(dog.owner);
            messageChannel.send(msg);
        } else if("getCat".equals(methodCall.method)) {
            Cat cat = Zoo.getCat();
            List<Object> msg = new ArrayList<>();
            msg.add("Cat");
            msg.add(cat.name);
            msg.add(cat.age);
            msg.add(cat.home);
            messageChannel.send(msg);
        } else {
            result.notImplemented();
        }
    }
}

Flutter端代码

首先在Flutter端也要定义与Android端一样的动物类,用于接收发送过来的动物对象

class Dog {
    String name;
    int age;
    String owner;
}

class Cat {
    String name;
    int age;
    String home;
}
import 'dart:async';

import 'package:flutter/services.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:flutter/material.dart';

class PluginZoo {
  static const MethodChannel _methodChannel = const MethodChannel('plugin_zoo');
  static const BasicMessageChannel _messageChannel = const BasicMessageChannel('plugin_zoo', StringCodec());

  PluginVersion() {
    _messageChannel.setMessageHandler(_handleMessage);
  }

  Future<String> _handleMessage(message) async {
    // 第一个元素取出对象类型
    String type = message[0];
    // 根据类型还原不同对象
    if("Dog" == type) {
      Dog dog = Dog();
      dog.name = message[1];
      dog.age = message[2];
      dog.owner = message[3];
      // 做相应处理,看是要红烧还是炖汤
    } else if("Cat" == type) {
      Cat cat = Cat();
      cat.name = message[1];
      cat.age = message[2];
      cat.home = message[3];
      // 做相应处理,看是要撸猫还是要撸猫
    }
  }

  Future getDog() async {
    await _channel.invokeMethod('getDog');
  }

  Future getCat() async {
    await _channel.invokeMethod('getCat');
  }  
}

参考

以上示例代码只贴出关键的代码,如果看本文前您还未了解过Flutter插件的相关知识及如何创建一个Flutter插件,可以先看笔者的其他相关文章,再来看本文,这样比较不会吃力

 

 

 

 

 

 

 

 

 

posted @ 2019-10-31 14:07  野猿新一  阅读(101)  评论(0编辑  收藏  举报