flutter在移动端开发领域凭借其一处代码,多端运行的特点,备受关注。但是目前来说,市面上的app功能复杂多变,因此也很难见到纯flutter的项目,其中会夹杂着很多native的插件及相关代码。因此,了解flutter与native端是如何建立数据通信的对于开发而言至关重要。下面将以集成百度语音识别的SDK到flutter项目中为例,介绍如何实现两端通信。
插件代码编写
【flutter|从集成百度语音SDK说起---flutter与native数据通信初试】首先在百度AI开放平台上下载SDK,同时,在本地项目中用android studio打开android目录,新建asr_plugin文件夹存放与插件相关的代码。之后按需提取相关代码到该目录下。具体教程可以参考官网,因为此处主要讲述如何实现通信,设计功能开发类的部分简单跳过。
文章图片
为了能让我们的包被本地识别,需要设置asr_plugin Module下的build.gradle,
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
localPropertiesFile.withReader('UTF-8') { reader ->
localProperties.load(reader)
}
}
def flutterRoot = localProperties.getProperty('flutter.sdk')
...
flutter {
source '../..'
}
MethodChannel使用
android端 c++层通过调用flutterJNI的handlePlatformMessage方法将消息传递给java层,在java层通过调用handleMessageFormDart
public void handleMessageFromDart(final String channel, byte[] message, final int replyId) {
FlutterNativeView.this.assertAttached();
BinaryMessageHandler handler = (BinaryMessageHandler)FlutterNativeView.this.mMessageHandlers.get(channel);
if (handler != null) {
try {
ByteBuffer buffer = message == null ? null : ByteBuffer.wrap(message);
handler.onMessage(buffer, new BinaryReply() {
private final AtomicBoolean done = new AtomicBoolean(false);
public void reply(ByteBuffer reply) {
if (!FlutterNativeView.this.isAttached()) {
Log.d("FlutterNativeView", "handleMessageFromDart replying ot a detached view, channel=" + channel);
} else if (this.done.getAndSet(true)) {
throw new IllegalStateException("Reply already submitted");
} else {
if (reply == null) {
FlutterNativeView.this.mFlutterJNI.invokePlatformMessageEmptyResponseCallback(replyId);
} else {
FlutterNativeView.this.mFlutterJNI.invokePlatformMessageResponseCallback(replyId, reply, reply.position());
}}
}
});
} catch (Exception var6) {
Log.e("FlutterNativeView", "Uncaught exception in binary message listener", var6);
FlutterNativeView.this.mFlutterJNI.invokePlatformMessageEmptyResponseCallback(replyId);
}} else {
FlutterNativeView.this.mFlutterJNI.invokePlatformMessageEmptyResponseCallback(replyId);
}
}
在MainActivity.java中进行设置,注册我们的自定义插件到activity身上。(注: flutter升级之后,很多搜到的代码均是使用的== onCreate 进行注册,升级flutter后,需使用 configureFlutterEngine ==进行设置。)
//channel的名称,由于app中可能会有多个channel,这个名称需要在app内是唯一的。
private static final String CHANNEL = "flutter_asr_plugin";
private MethodChannel mothodChannel;
private static String TAG = "MainActivity";
@Override
public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
//这里的CHANNEL即是我们在dart端定义的名称,需保持一致
mothodChannel = new MethodChannel(flutterEngine.getDartExecutor(), CHANNEL);
ShimPluginRegistry shimPluginRegistry = new ShimPluginRegistry(flutterEngine);
FlutterWebviewPlugin.registerWith(shimPluginRegistry.registrarFor("com.flutter_webview_plugin.FlutterWebviewPlugin"));
mothodChannel.setMethodCallHandler((methodCall, result) -> {
//call.method是获取调用的方法名字//call.arguments 是获取参数
});
}
如下(可参考已导入的第三方插件注册过程)
MainActivity.java
@Override
public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
Log.i(TAG, "start");
ShimPluginRegistry shimPluginRegistry = new ShimPluginRegistry(flutterEngine);
FlutterWebviewPlugin.registerWith(shimPluginRegistry.registrarFor("com.flutter_webview_plugin.FlutterWebviewPlugin"));
AsrPlugin.registerWith(shimPluginRegistry.registrarFor("flutter_asr_plugin"));
}
AsrPlugin.java
//做旧版本兼容
public static void registerWith(PluginRegistry.Registrar registrar) {
if (registrar.activity() != null) {
channel = new MethodChannel(registrar.messenger(), CHANNEL_NAME);
final AsrPlugin instance = new AsrPlugin(registrar.activity());
registrar.addActivityResultListener(instance);
channel.setMethodCallHandler(instance);
}
}
flutter端 首先,在dart端创建一个MethodChannel对象,定义其名称为flutter_asr_plugin,这个名字需要与android端保持一致,方便之后调用。之后,我们为其定义一些与native通信的相关接口,在dart端通过调用这些接口来触发android端SDK的相对应的功能。
class AsrManager {
//MethodChannel flutter与native通信的通道
static const MethodChannel _channel = const MethodChannel('flutter_asr_plugin');
/// 定义一些与native通信的接口
/// 开始录音 --- start回调中可以拿到语音识别返回的数据
static Future> start({Map params}) async {
return await _channel.invokeMethod('start', params ?? {});
}
/// 停止录音
static Future> stop() async {
return await _channel.invokeMethod('stop');
}
/// 取消录音
static Future> cancel() async {
return await _channel.invokeMethod('cancel');
}
}
这里使用了invokeMethod这个方法,具体实现如下:
//method 为方法名;arguments 为相关的传入参数
Future _invokeMethod(String method, { bool missingOk, dynamic arguments }) async {
assert(method != null);
//使用binaryMessenger.send这个方法传递给android端
final ByteData result = await binaryMessenger.send(
name,
//使用codec对根据方法名和参数构建的MethodCall对象进行编码得到的对象
codec.encodeMethodCall(MethodCall(method, arguments)),
);
if (result == null) {
if (missingOk) {
return null;
}
//抛出插件不存在的错误
throw MissingPluginException('No implementation found for method $method on channel $name');
}
return codec.decodeEnvelope(result) as T;
}
最终Dart本地接口方法==_sendPlatformMessage使用的是ui.window.sendPlatformMessage==来启用C++层进行数据传递。
Future _sendPlatformMessage(String channel, ByteData message) {
final Completer completer = Completer();
// 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;
}
踩坑
- W/FlutterJNI(18011): Tried to send a platform message to Flutter, but FlutterJNI was detached from native C++. Could not send. Channel: xyz.luan/audioplayers. Response ID: 0
在flutter启用真机调试时发生,与native通信失败。 - Error connecting to the service protocol: failed to connect to http://127.0.0.1:61203
连接模拟器出错,重启模拟器或调整网络在同一个局域网内 - cannot resolve symbol MethodChannel
相关插件未注册成功,build.gradle建议修改 - error: cannot find symbol new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), CHANNEL)
- Plugin key com.example.plugin.asr.AsrPlugin is already in use
register注册插件时,多次注册 - PlatformException(No recognition result match
语音识别未返回内容
推荐阅读
- web网页模板|如此优秀的JS轮播图,写完老师都沉默了
- 接口|axios接口报错-参数类型错误解决
- JavaScript|vue 基于axios封装request接口请求——request.js文件
- JavaScript|JavaScript — 初识数组、数组字面量和方法、forEach、数组的遍历
- JavaScript|JavaScript — call()和apply()、Date对象、Math、包装类、字符串的方法
- 前端|web前端dya07--ES6高级语法的转化&render&vue与webpack&export
- vue|Vue面试常用详细总结
- javascript|vue使用js-xlsx导出excel,可修改格子样式,例如背景颜色、字体大小、列宽等
- css|我用css精灵图拼接了自己的英文名字,不会还有人不知道精灵图技术吧()
- css|css三角的做法及其案例