Flutter开发必须扩展的Channel通道以及不同的定义方式
作者:访客发布时间:2023-10-30分类:程序开发学习浏览:163
Channel Channel的定义与实现
前言
需求是这样的,应用需要收集用户的头像,这个简单,直接用官方的 Camera 插件拍摄即可。
但是有些用户会传递一些非人脸的图片,或多人脸的图片,导致业务无法继续,所以需要移动端在收集人脸的同时校验人脸数量。
具体效果如下:
如何实现?实际上也很简单,酒吧里面有很多开源的框架。比较出名的例如google_mlkit_face_detection
支持 Android和iOS,本身也是很优越的框架了,由于部分原因并没有选择第三方的框架。
换个方向,类似功能的实际各种各样的原始API已经有了相应的实现,我们直接自己写Channel不是就可以了吗?不需要重复导一个比较重的库去实现这一个相对简单的功能。
那么如何定义和使用Channel呢?
一、Channel的类型与实现
我们通常说的 Channel全称叫 Platform Channel,它是Flutter和原生通信的工具,有三种类型:
- MethodChannel:用于传递方法调制(方法调用),Flutter和平台端进行直接方法调制时可以使用。
- BasicMessageChannel:用于传递字符串和半结构化的信息,Flutter与平台端进行消息数据交换时可以使用。
- EventChannel:用于数据流(event streams)的通信,Flutter和平台端进行事件监听、取消等都可以使用。
官方演示在此[传送门]
简单的理解:
Flutter可以通过它调节原生方法,具体的实现由各种自原生平台实现,这种方案也是最常用的。
EventChannel:用于在事件流中将消息传递给Flutter端,通常用于原始平台的监听数据(如传感器)传递给Flutter端显示或处理。
BasicMessageChannel:是一种简单的双向消息通信通道,它允许Flutter和原生平台通过字符串或字符节流发送消息,并返回一个响应,它是最基本的可以实现 MethodChannel 和 EventChannel 的功能。
1.1 MethodChannel 实现示例
这里以我们上面所说的人数检测为例,我们现在在Flutter中定义相应的MethodChannel,定义它的通道名和方法名。
定义如下:
final _platform = MethodChannel('face_detection');
String response = await _platform.invokeMethod('checkFace');
当然一般我们会封装在一个类中便于统一管理。
那么在Android中实现呢?
MethodChannel的构建需要两个参数,一个是BinaryMessenger,通常从Flutter Engine中获取,可以通过普通的Engine构建,也可以通过EngineCache预热引用来获取,当然也可以使用EngineGroup来获取,如果在FlutterActivity里面,可以直接在configureFlutterEngine回调中获取。另一个参数是name,用于标识这个Channel。
具体的实现如下:
class MainActivity : FlutterActivity() {
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(flutterEngine.dartExecutor, "face_detection")
.setMethodCallHandler { call, result ->
if (call.method == "checkFace") {
val imagePath = call.arguments as String
val faceCount = detectFaces(imagePath)
result.success(faceCount)
} else {
result.notImplemented()
}
}
}
private fun detectFaces(imagePath: String): Int {
return CheckFaceUtils.checkFace(imagePath)
}
}
CheckFaceUtils:Android原生API实现的人脸检测,代码如下:
public class CheckFaceUtils {
public static Bitmap rotateBitmapIfNeeded(Bitmap bitmap) {
int width = bitmap.getWidth();
int height = bitmap.getHeight();
// 判断是否需要旋转
if (width > height) {
Matrix matrix = new Matrix();
matrix.postRotate(270); // 旋转90度
return Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, true);
} else {
return bitmap;
}
}
/**
* 检查BitMap中包含的人脸数量
*/
public static int checkFace(String imagePath) {
Bitmap b = BitmapFactory.decodeFile(imagePath);
if (b != null) {
//处理横竖Bitmap的旋转
b = rotateBitmapIfNeeded(b);
// 检测前必须转化为RGB_565格式。文末有详述连接
Bitmap bitmap = b.copy(Bitmap.Config.RGB_565, true);
b.recycle();
// 设置你想检测的数量,数值越大错误率越高,所以需要置信度来判断,但有时候置信度也会出问题
int MAX_FACES = 5; // I found it can detect number of face at least 27,
FaceDetector faceDet = new FaceDetector(bitmap.getWidth(), bitmap.getHeight(), MAX_FACES);
// 将人脸数据存储到faceArray中
FaceDetector.Face[] faceArray = new FaceDetector.Face[MAX_FACES];
// 返回找到图片中人脸的数量,同时把返回的脸部位置信息放到faceArray中,过程耗时,图片越大耗时越久
int findFaceNum = faceDet.findFaces(bitmap, faceArray);
Log.w("FaceSDKUtils", "找到脸部数量:" + findFaceNum);
bitmap.recycle();
return findFaceNum;
} else {
Log.w("FaceSDKUtils", "目标文件不是图片,无法获取Bitmap");
return -1;
}
}
}
iOS 的实现,也是类似的思路,只是人为检测的代码比Android还要简单,具体代码如下:
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
var channel:FlutterMethodChannel!
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
self.initPlatformMethods()
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
func initPlatformMethods(){
self.channel = FlutterMethodChannel.init(name: "face_detection", binaryMessenger: self.window.rootViewController as! FlutterBinaryMessenger)
self.channel.setMethodCallHandler { call, result in
if (call.method == "checkFace"){
result(self.checkFace(path: call.arguments as! String));
}
}
}
func checkFace(path:String) -> Int{
var image = CIImage.init(image: .init(contentsOfFile: path)!)
var detector = CIDetector.init(ofType: CIDetectorTypeFace, context: nil, options: [CIDetectorAccuracy:CIDetectorAccuracyHigh])
var features = detector!.features(in: image!);
return features.count;
}
}
它是在应用初始化的时候就注册了。
Log如下:
1.2 EventChannel实现示例
We were at the end of 2007.
先定义一个对象用于传递
class AccelerometerReadings {
final double x;
final double y;
final double z;
AccelerometerReadings(this.x, this.y, this.z);
}
Flutter的代码实现如下:
child: StreamBuilder<AccelerometerReadings>(
stream: EventChannel('eventChannelDemo').receiveBroadcastStream().map(
(dynamic event) => AccelerometerReadings(
event[0] as double,
event[1] as double,
event[2] as double,
),
),
builder: (context, snapshot) {
if (snapshot.hasError) {
return Text((snapshot.error as PlatformException).message!);
} else if (snapshot.hasData) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'x轴: ' + snapshot.data!.x.toStringAsFixed(3),
style: textStyle,
),
Text(
'y轴: ' + snapshot.data!.y.toStringAsFixed(3),
style: textStyle,
),
Text(
'z轴: ' + snapshot.data!.z.toStringAsFixed(3),
style: textStyle,
)
],
);
}
当然了,这是简单的使用,其实一般都是封装到一个类中便于统一管理。
Android终端的实现:
class MainActivity : FlutterActivity() {
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
val sensorManger: SensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager
val accelerometerSensor: Sensor = sensorManger.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)
EventChannel(flutterEngine.dartExecutor, "eventChannelDemo").setStreamHandler(AccelerometerStreamHandler(sensorManger, accelerometerSensor))
}
}
具体传感器的代码实现:
class AccelerometerStreamHandler(sManager: SensorManager, s: Sensor) : EventChannel.StreamHandler, SensorEventListener {
private val sensorManager: SensorManager = sManager
private val accelerometerSensor: Sensor = s
private lateinit var eventSink: EventChannel.EventSink
override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
if (events != null) {
eventSink = events
sensorManager.registerListener(this, accelerometerSensor, SensorManager.SENSOR_DELAY_UI)
}
}
override fun onCancel(arguments: Any?) {
sensorManager.unregisterListener(this)
}
override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {}
override fun onSensorChanged(sensorEvent: SensorEvent?) {
if (sensorEvent != null) {
val axisValues = listOf(sensorEvent.values[0], sensorEvent.values[1], sensorEvent.values[2])
eventSink.success(axisValues)
} else {
eventSink.error("DATA_UNAVAILABLE", "Cannot get accelerometer data", null)
}
}
}
1.3 BasicMessageChannel 实现示例
之前我们都是演示的 Flutter原生平台的数据,这里我们以原生Android平台的Flutter 的数据为例,演示Android平台拿到 Flutter 项目中的图片资源。
当然只是范例啊,真正的项目很少这么干...
The results showed that the mechanical properties of the composite materials were higher than that of the composite materials,the mechanical properties of the composite materials were higher than that of the composite materials.
final channelToAndroid = BasicMessageChannel<ByteData>(
'image_data_from_flutter',
BinaryCodec(),
);
// 获取assets中的图片对应的ByteData数据,并发送给原生
rootBundle.load(Assets.imagesBlackBack).then((value) async {
ByteData? res = await channelToAndroid.send(value);
Log.d('res :$res');
if (res != null) {
// 将 ByteData 转换为字节数组
Uint8List bytes = res.buffer.asUint8List();
// 将字节数组转换为字符串
String stringData = utf8.decode(bytes);
// 在控制台输出接收到的字符串
Log.d('Received string from Android: $stringData');
}
});
需要注意的是,这里使用 BinaryCodec,数据格式为 ByteData,如果是想发送String 字符串类型,那么就可以指定为 StringCodec()。
那么在原生中的实现:
class MainActivity : FlutterActivity() {
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
BasicMessageChannel<ByteBuffer>(
flutterEngine.dartExecutor, "image_data_from_flutter", BinaryCodec.INSTANCE
).setMessageHandler { message, reply ->
//转换为Android需要的ByteArray
val byteBuffer = message as ByteBuffer
val imageByteArray = ByteArray(byteBuffer.capacity())
byteBuffer.get(imageByteArray)
Log.d("TAG","imageByteArray:$imageByteArray")
//收到之后如果想回复给Flutter端,也可以加一下代码
val str = "感谢Flutter老哥送上的图片数据"
val strBytes = str.toByteArray(Charset.forName("UTF-8"))
val byteBuffer2 = ByteBuffer.allocateDirect(strBytes.size)
byteBuffer2.put(strBytes)
byteBuffer2.flip()
reply.reply(byteBuffer2)
}
}
}
这样就可以完成一个简单的类似请求与响应的效果,那么如何做到双端通信呢?
实际上我们在原生端创建了两个 BasicMessageChannel 对象,分别用于从 Flutter 端接收数据(channelFromFlutter)和向 Flutter 端发送数据(channelToFlutter)。In Further embodiments,the transmitting device 100 includes a transmitting device 102,a transmitting device 104,a transmitting device 106,a transmitting device 108,and a transmitting device 108.
我们可以先定义两个通道,代码如下:
final channelFromAndroid = BasicMessageChannel<String>(
'image_data_to_flutter',
StringCodec(),
);
final channelToAndroid = BasicMessageChannel<String>(
'image_data_from_flutter',
StringCodec(),
);
// 获取assets中的图片对应的ByteData数据,并发送给原生
String? res = await channelToAndroid.send('我是来自Flutter的字符串');
Log.d('收到来自Android的Reply :$res');
channelFromAndroid.setMessageHandler((receivedData) async {
// 在这里处理来自 Android 端的数据
Log.d('收到来自Android发过来的数据: $receivedData');
return "收到了感谢Android老铁发来得到数据";
});
There's a lot of people who want to go.
class MainActivity: FlutterActivity() {
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
BasicMessageChannel(
flutterEngine.dartExecutor, "image_data_from_flutter", StringCodec.INSTANCE
).setMessageHandler { message, reply ->
//转换为Android需要的ByteArray
Log.d("TAG","收到来自Flutter的字符串:$message")
//收到之后如果想回复给Flutter端,也可以加一下代码
reply.reply("感谢Flutter老哥送来的数据,已经收到了")
}
BasicMessageChannel(
flutterEngine.dartExecutor, "image_data_to_flutter", StringCodec.INSTANCE
).send("这个字符串是我主动Send给Flutter的,你能收到吗?") {
Log.d("TAG", "Flutter的回复收到了:$it");
}
}
}
效果如下:
当然这是 Demo 效果,真实场景会把 BasicMessageChannel 抽取出来根据具体逻辑判断场合是使用 reply 还是使用主动的发送来进行消息的传递
三、自定义插件的自动实现
对于三种 Channel我们都已经了解了,都是在我们自己的项目中手动注册的,为什么我看到的一些第三方的插件都没有 FlutterActivity,他们都没有手动注册,他们是如何实现 Channel的注册的呢?
网上的博客文章或者一些AI工具会告诉你 Channel 是这么写的,需要实现 FlutterPlugin 接口,类似如下:
class FaceDetectionChannel : FlutterPlugin, MethodChannel.MethodCallHandler {
private var context: Context? = null
private var channel: MethodChannel? = null
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
if (call.method == "detectFaces") {
val imagePath = call.arguments as String
val faceCount = detectFaces(imagePath) // 调用你的人脸检测方法,返回人脸数量
result.success(faceCount)
} else {
result.notImplemented()
}
}
private fun detectFaces(imagePath: String): Int {
return FaceSDKUtils.checkFace(imagePath)
}
override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
channel = MethodChannel(binding.binaryMessenger, "face_detection_channel")
channel?.setMethodCallHandler(this)
context = binding.applicationContext
}
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
channel?.setMethodCallHandler(null)
}
}
但这样并不能真正的注册,这种是插件的写法,当我们在 pubspec.yaml 文件中依赖一个插件时,插件中指定了 pluginClass,就会自动调节onAttachedToEngine 方法,所以它才能达到'自动初始化'
的效果。
如果是在自己的项目中写 Channel规则大可以不必这么写,直接在 FlutterActivity 或 FlutterApplication 中手动注册即可。
那么我就想写一个这样的,以本地插件的形式导入到项目行不行?当然可以。
第一步我们需要在本地插件中指定 plgun
name: face_detect
description: 找到Path中人脸数量
version: 0.0.1
homepage:
environment:
sdk: '>=3.0.2 <4.0.0'
flutter: ">=1.20.0"
dependencies:
platform: ^3.0.0
flutter:
sdk: flutter
dev_dependencies:
test: ^1.17.4
mockito: ^5.0.7
# The following section is specific to Flutter.
flutter:
plugin:
platforms:
android:
package: com.newki.facedetect
pluginClass: FaceDetectionChannel
ios:
pluginClass: FaceDetectionChannel
在lib中只需要定义 Channel 的 Flutter 端代码:
class FaceDetectionChannel {
//初始化MethodChannel对象
static const MethodChannel _channel = MethodChannel('face_detection_channel');
/// 各种平台自行实现检测人脸数量
static Future<int> detectFaces(String imagePath) async {
try {
Log.d('Flutter -> detectFaces -> imagePath:$imagePath');
final int faceCount = await _channel.invokeMethod('detectFaces', imagePath);
return faceCount;
} on PlatformException catch (e) {
Log.e(e.message ?? 'detectFaces->未知异常');
return -1;
}
}
}
Android 的具体实现上面已经给出,下面是iOS的代码:
import Flutter
class FaceDetectionChannel {
static func register(with registrar: FlutterPluginRegistrar) {
let channel = FlutterMethodChannel(name: "face_detection_channel", binaryMessenger: registrar.messenger())
let instance = FaceDetectionChannel()
registrar.addMethodCallDelegate(instance, channel: channel)
}
func detectFaces(imagePath: String) -> Int {
let image = CIImage(contentsOfURL: URL(fileURLWithPath: imagePath))
let detector = CIDetector(ofType: CIDetectorTypeFace, context: nil, options: [CIDetectorAccuracy: CIDetectorAccuracyHigh])
let features = detector?.features(in: image)
return features?.count ?? 0
}
}
extension FaceDetectionChannel: FlutterPlugin {
static func register(with registrar: FlutterPluginRegistrar) {
FaceDetectionChannel.register(with: registrar)
}
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
if call.method == "detectFaces" {
let arguments = call.arguments as? [String: Any]
let imagePath = arguments?["imagePath"] as? String ?? ""
let faceCount = detectFaces(imagePath: imagePath)
result(faceCount)
} else {
result(FlutterMethodNotImplemented)
}
}
}
When used,the local plugs are directly inserted in the pubspec.yaml file of their own project:
face_detect:
path: face_detect_plugin
This can be achieved by automatic Channel registration and achieved. In this paper,the author introduces the principle and application of the mechanism of mechanical mechanism. A lot of teachings can search once not complicated.
总结
In this paper,we总结了三种 Channel的具体使用实例,并且说明了自己项目中的Channel 与结件中的Channel 初始化的不同方式.
Why we have to explore the use of Channel?
平台特定功能的复用:在平台开发中,某些平台特有的功能可能无法直接使用 Flutter 提供的现有解决方案此时,您可以通过 Channel 实现与原始代码的通信,调制平台特定的功能。
性能优化:有时候,一些性能要求较高的任务可能需要在原生代码中执行。通过使用 Channel,您可以将这些任务委托给原始层,从而提高应用程序的性能和响应速度。
第三方库支持:尽管 Flutter 生态圈非常强大,但仍然有一些功能不可或缺的第三方库可能没有相应的 Flutter 插件。通过 Channel,您可以轻松地集成和使用这些第三方库,拓宽了应用的功能范围。
访问硬件功能:某些硬件功能(如相机、传感器等)可能需要直接与原生平台进行交互。通过 Channel,您可以利用原生平台的 API 来实现对硬件功能的访问和控制。
多平台适配:未来,随着 Flutter 对其他平台的支持增加,比如抖音App,您的抖音频道的使用也会在适配其他平台时很有帮助,让您更快地实现对应平台的功能。
等合计之下,区块链Channel 的使用能够让开发者更加灵活、高效地进行区块链平台开发。除了处理特定功能和第三方库的问题,还有一些其他场景,如处理设备传感器、操作文件系统等,也可以使用 Channel 来实现。因此,建议在需要平台功能和性能优化时深入学习和学习Channel 的使用。
那么本期内容就到这里,如讲的不到位或错漏的地方,希望同学们可以评论区指出。
由于代码比较简单,本文全部贴出,如果感觉本文对你有一点点点的启发,还望你能点赞
Support once,your support is my biggest power.
好吧,这段时间就此结束。
- 程序开发学习排行
-
- 1鸿蒙HarmonyOS:Web组件网页白屏检测
- 2HTTPS协议是安全传输,为啥还要再加密?
- 3HarmonyOS鸿蒙应用开发——数据持久化Preferences
- 4记解决MaterialButton背景颜色与设置值不同
- 5鸿蒙HarmonyOS实战-ArkUI组件(RelativeContainer)
- 6鸿蒙HarmonyOS实战-ArkUI组件(Stack)
- 7[Android][NDK][Cmake]一文搞懂Android项目中的Cmake
- 8Android广播如何解决Sending non-protected broadcast问题
- 9鸿蒙HarmonyOS实战-ArkUI组件(mediaquery)
- 最近发表
-
- WooCommerce最好的WordPress常用插件下载博客插件模块的相关产品
- 羊驼机器人最好的WordPress常用插件下载博客插件模块
- IP信息记录器最好的WordPress常用插件下载博客插件模块
- Linkly for WooCommerce最好的WordPress常用插件下载博客插件模块
- 元素聚合器Forms最好的WordPress常用插件下载博客插件模块
- Promaker Chat 最好的WordPress通用插件下载 博客插件模块
- 自动更新发布日期最好的WordPress常用插件下载博客插件模块
- WordPress官方最好的获取回复WordPress常用插件下载博客插件模块
- Img to rss最好的wordpress常用插件下载博客插件模块
- WPMozo为Elementor最好的WordPress常用插件下载博客插件模块添加精简版