Channel通道的界说与完成
前言
需求是这个姿态的,运用需求搜集用户的头像,这个简略,直接用官方的 Camera 插件摄影即可。
可是有些用户会传递一些非人脸的图片,或多人脸的图片,导致事务无法继续,所以需求移动端在搜集人脸的一起校验人脸数量。
详细作用如下:
如何完成?其实也简略,Pub里边有许多开源的结构。比较出名的例如 google_mlkit_face_detection
支撑 Android 与 iOS ,自身也是很优秀的结构了,由于部分原因并没有选择第三方的结构。
换个方向,相似的功用其实各自的原生API现已有对应的完成,咱们直接自己写Channel不是就能够了吗?不需求重复导一个比较重的库去完成这一个相对简略的功用。
那么如何界说与运用Channel呢?
一、Channel的类型与完成
咱们常说的 Channel 全名叫 Platform Channel,它是Flutter和原生通讯的东西,有三种类型:
- MethodChannel:用于传递办法调用(method invocation),Flutter平和大驾进行直接办法调用时分能够运用。
- BasicMessageChannel:用于传递字符串和半结构化的信息,Flutter平和大驾进行音讯数据交换时分能够运用。
- EventChannel:用于数据流(event streams)的通讯,Flutter平和大驾进行事情监听、取消等能够运用。
官方Demo在此【传送门】
简略的了解:
-
MethodChannel: 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完成示例
咱们以监听原生渠道的重力加速度传感器的值为例,把原生渠道的数据以 Stream 的办法传递给 Flutter 端。
先界说一个目标用于传递
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 项目中的图片资源。
当然仅仅示例啊,实在项目很少这么干…
同样的先在 Flutter 端先界说 BasicMessageChannel 目标,指明通道名,并发送数据:
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)。在 Flutter 端也创建了两个相应的 BasicMessageChannel 目标,用于和原生端进行双向通讯。
咱们能够先界说 Flutter 端的两个通道,代码如下:
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老铁发来得到数据";
});
下面便是界说 Android 端的两个通道,代码如下:
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 还是运用主动的 send 来进行音讯的传递。
三、自界说插件的主动完成
关于三种 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 中手动注册即可。
那么我就想写一个这样的,以本地插件的形式导入到项目行不行?当然能够。
第一步咱们需求在本地插件的 pubspec.yaml 中指定 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)
}
}
}
运用的时分直接在自己的项目的 pubspec.yaml 文件中引进自己的本地插件:
face_detect:
path: face_detect_plugin
这样就能够完成主动 Channel 的注册与完成了。当然你也能够把这个插件发布到Pub 上,这样就能够开源出去供其他人运用了,如何发布项目到 Pub? 许多教程自己能够查找一下并不杂乱…
总结
本文总结了三种 Channel 的详细运用示例,而且说明了自己项目中的 Channel 与插件中的 Channel 初始化的不同办法。
为什么咱们一定要把握 Channel 的运用?
-
渠道特定功用的调用:在跨渠道开发中,某些渠道特有的功用或许无法直接运用 Flutter 提供的现成解决方案。这时,您能够经过 Channel 完成与原生代码的通讯,调用渠道特定的功用。
-
功能优化:有时分,一些功能要求较高的任务或许需求在原生代码中执行。经过运用 Channel,您能够将这些任务委托给原生层,然后提高运用的功能和响应速度。
-
第三方库支撑:尽管 Flutter 生态圈十分强大,但仍然有一些功用不可或缺的第三方库或许没有对应的 Flutter 插件。经过 Channel,您能够轻松地集成和运用这些第三方库,拓宽了运用的功用范围。
-
拜访硬件功用:某些硬件功用(如相机、传感器等)或许需求直接与原生渠道进行交互。经过 Channel,您能够调用原生渠道的 API 来完成对硬件功用的拜访和控制。
-
多渠道适配:未来,跟着 Flutter 对其他渠道的支撑增加,例如鸿蒙 App,您把握 Channel 的运用也会在适配其他渠道时十分有帮助,让您更快速地完成对应渠道的功用。
等等总归,把握 Channel 的运用能够让开发者更灵活、高效地进行跨渠道开发。除了处理特定功用和第三方库的问题,还有一些其他场景,例如处理设备传感器、操作文件体系等,也能够运用 Channel 来完成。因此,建议在需求跨渠道功用和功能优化时深入学习和把握 Channel 的运用。
那么本期内容就到这里,如讲的不到位或错漏的地方,期望同学们能够评论区指出。
由于代码比较简略,本文悉数贴出,假如感觉本文对你有一点点点的启示,还望你能点赞
支撑一下,你的支撑是我最大的动力啦。
Ok,这一期就此完结。