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支持的对象类型,在接收端再把它重新解析出来,以下是几种解决的思路:
- java对象转成Collection,约定不同位置存放对象的不同属性
- java对象转成Array,约定不同位置存放对象的不同属性
- java对象转成Map,约定不同的键存放不同的对象属性
- 自定义编解码器,实现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插件,可以先看笔者的其他相关文章,再来看本文,这样比较不会吃力