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

mac2024-03-06  25

在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.NULLJSONObject和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插件,可以先看笔者的其他相关文章,再来看本文,这样比较不会吃力

一步步实现一个Flutter plugin插件

Flutter Plugin插件开发之利用BasicMessageChannel在Platform和Flutter之间通信

 

 

 

 

 

 

 

 

 

最新回复(0)