前言
各人知道Flutter和鸿蒙通信方式和Flutter和其他平台通信方式都是一样的, 都是使用Platform Channel API来通信。
那么鸿蒙中这些通信的代码是写在哪里? 怎样编写的了?
下面我们简单的学习下。
鸿蒙项目内Plugin
在我们开发App的过程中,大概有如许的需求:
在鸿蒙平台上特有的,并且需要调用鸿蒙原生的API来完成的。那么我们可以在在ohos平台上创建一个Plugin的方式来支持这个功能。
示例的通信方式使用:MethodChannel的方式。
Flutter端实现
- // flutter端创建一个MethodChannel的通道,通道名称必须和鸿蒙指定, 如果创建的名称不一致,会导致无法通信
- final channel = const MethodChannel("com.test.channel");
- // flutter给鸿蒙端发送消息
- channel.invokeMapMethod("testData");
复制代码 鸿蒙端实现
创建Plugin的插件类
首先我们需要创建一个插件类, 继续自FlutterPlugin类, 并实现其中的方法
- export default class TestPlugin implements FlutterPlugin {
- // 通道
- private channel?: MethodChannel;
- //获取唯一的类名 类似安卓的Class<? extends FlutterPlugin ts无法实现只能用户自定义
- getUniqueClassName(): string {
- return 'TestPlugin'
- }
- // 当插件从engine上分离的时候调用
- onDetachedFromEngine(binding: FlutterPluginBinding): void {
- this.channel?.setMethodCallHandler(null);
- }
- // 当插件挂载到engine上的时候调用
- onAttachedToEngine(binding: FlutterPluginBinding): void {
- this.channel = new MethodChannel(binding.getBinaryMessenger(), "com.test.channel");
- // 给通道设置回调监听
- this.channel.setMethodCallHandler({
- onMethodCall(call: MethodCall, result: MethodResult) {
- switch (call.method) {
- case "testData":
- console.log(`接收到flutter传递过来的参shu ===================`)
- break;
- default:
- result.notImplemented();
- break;
- }
- }
- })
- }
- }
复制代码 注册Plugin
我们创建完Plugin了, 我们还需要再EntryAbility中去注册我们的插件
- export default class EntryAbility extends FlutterAbility {
- configureFlutterEngine(flutterEngine: FlutterEngine) {
- super.configureFlutterEngine(flutterEngine)
- this.addPlugin(new TestPlugin());
- }
- }
复制代码 完成上述两步之后,我们就可以使用这个专属鸿蒙的插件来完成鸿蒙上特有的功能了。
开发纯Dart的package
我们知道,flutter_flutter的仓库对于纯Dart开发的package是完全支持的, 对于纯Dart的package我们主要关注Dart的版本支持。
开发纯Dart的命令:flutter create --template=package hello
对于详细怎样开发纯Dart的package,Flutter官方已经讲的非常详细, 开发,集成详细 可以参考官方文档。Flutter中的Package开发
为现有插件项目添加ohos平台支持
在我们开发Flutter项目适配鸿蒙平台的时候,会有些插件还没有适配ohos平台, 这个时候我们等华为适配, 或者我们自己下载插件的源码, 然后我们自己在源码中编写适配ohos平台的代码
下面以image_picker插件为示例,来学习下怎样为已有插件项目添加ohos的平台支持
创建插件
首先我们需要下载image_picker源码, 然后使用Android studio打开flutter项目。 可以查看到项目标布局
然后通过命令行进入到项目根目次, 执行命令:flutter create . --template=plugin --platforms=ohos
执行完后的目次布局如下:
设置插件
我们创建完ohos平台的插件之后, 我们需要再Plugin工程的pubspec.yaml设置文件中设置ohos平台的插件。
当我们设置完成之后, 我们接下来就可以开始编写ohos平台插件相关内容了。
编写插件内容
在我们编写ohos平台的插件内容时, 我们首先需要知道这个插件是通过什么通道, 调用什么方法来和个个平台通信的。 ohos平台的通道名称、调用方法尽量和原来保持划一,有助于明白。
- // 执行flutter指令创建plugin插件时, 会自动创建这个类
- export default class ImagesPickerPlugin implements FlutterPlugin, MethodCallHandler, AbilityAware {
- private channel: MethodChannel | null = null;
- private pluginBinding: FlutterPluginBinding | null = null;
- // 当前处理代理对象
- private delegate: ImagePickerDelegate | null = null
- constructor() {
- }
- getUniqueClassName(): string {
- return "ImagesPickerPlugin"
- }
- onAttachedToEngine(binding: FlutterPluginBinding): void {
- // 后续用到的页面context,都是需要重binding对象中获取, 如果你直接this.getcontext 等方法获取, 可能不是页面的context
- this.pluginBinding = binding;
- this.channel = new MethodChannel(binding.getBinaryMessenger(), "chavesgu/images_picker");
- this.channel.setMethodCallHandler(this)
- }
- onDetachedFromEngine(binding: FlutterPluginBinding): void {
- this.pluginBinding = null;
- if (this.channel != null) {
- this.channel.setMethodCallHandler(null)
- }
- }
- // 插件挂载到ablitity上的时候
- onAttachedToAbility(binding: AbilityPluginBinding): void {
- if (!this.pluginBinding) {
- return
- }
- this.delegate = new ImagePickerDelegate(binding.getAbility().context, this.pluginBinding.getApplicationContext());
- }
- onDetachedFromAbility() {
- }
- onMethodCall(call: MethodCall, result: MethodResult): void {
- if (call.method === "pick") {
- // 解析参数
- let count = call.argument("count") as number;
- // let language = call.argument("language") as string;
- let pickType = call.argument("pickType") as string;
- // let supportGif = call.argument("gif") as boolean;
- // let maxTime = call.argument("maxTime") as number;
- let maxSize = call.argument("maxSize") as number;
- if (this.delegate !== null) {
- this.delegate.pick(count, pickType, maxSize, result)
- }
- } else if (call.method === "saveImageToAlbum" || call.method === "saveVideoToAlbum") {
- // 保存图片
- let filePath = call.argument("path") as string; // 图片路径
- if (this.delegate !== null) {
- this.delegate.saveImageOrVideo(filePath, call.method === "saveImageToAlbum", result)
- }
- } else if (call.method === "openCamera") {
- let pickType = call.argument("pickType") as string;
- let maxSize = call.argument("maxSize") as number;
- if (this.delegate !== null) {
- this.delegate.openCamear(pickType, maxSize, result)
- }
- } else {
- result.notImplemented()
- }
- }
- }
复制代码 注意:这个插件内开发代码是没有代码提示的, 也不会自动检车报错, 只有你运行测试demo时, 编译时才会报错,以是建议各人把插件的功能在一个demo中完成,在把代码拷贝过来。
逻辑实现代码:
- import ArrayList from '@ohos.util.ArrayList';
- import common from '@ohos.app.ability.common';
- import photoAccessHelper from '@ohos.file.photoAccessHelper';
- import { BusinessError } from '@kit.BasicServicesKit';
- import picker from '@ohos.multimedia.cameraPicker';
- import camera from '@ohos.multimedia.camera';
- import dataSharePredicates from '@ohos.data.dataSharePredicates';
- import { fileUri } from '@kit.CoreFileKit';
- import FileUtils from './FileUtils'
- import fs from '@ohos.file.fs';
- import {
- MethodResult,
- } from '@ohos/flutter_ohos';
- import abilityAccessCtrl, { PermissionRequestResult } from '@ohos.abilityAccessCtrl';
- export default class ImagePickerDelegate {
- // 当前UIAblitity的context
- private context: common.Context
- // 插件绑定的context
- private bindContext: common.Context
- // 构造方法
- constructor(context: common.Context, bindContext: common.Context) {
- this.context = context
- this.bindContext = bindContext
- }
- // 选择相册图片和视频
- pick(count: number, pickType: string, maxSize: number, callback: MethodResult) {
- // 创建一个选择配置
- const photoSelectOptions = new photoAccessHelper.PhotoSelectOptions();
- // 媒体选择类型
- let mineType: photoAccessHelper.PhotoViewMIMETypes = photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE;
- if (pickType === "PickType.all") {
- mineType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_VIDEO_TYPE;
- } else if (pickType === "PickType.video") {
- mineType = photoAccessHelper.PhotoViewMIMETypes.VIDEO_TYPE;
- }
- photoSelectOptions.MIMEType = mineType
- photoSelectOptions.maxSelectNumber = count; // 选择媒体文件的最大数目
- let uris: Array<string> = [];
- const photoViewPicker = new photoAccessHelper.PhotoViewPicker();
- // 通过photoViewPicker对象来打开相册图片
- photoViewPicker.select(photoSelectOptions).then((photoSelectResult: photoAccessHelper.PhotoSelectResult) => {
- uris = photoSelectResult.photoUris;
- console.info('photoViewPicker.select to file succeed and uris are:' + uris);
- this.hanlderSelectResult(uris, callback)
- }).catch((err: BusinessError) => {
- console.error(`Invoke photoViewPicker.select failed, code is ${err.code}, message is ${err.message}`);
- })
- }
- // 处理打开相机照相/录制
- async openCamear(type: string, maxSize: number, callback: MethodResult) {
- // 定义一个媒体类型数组
- let mediaTypes: Array<picker.PickerMediaType> = [picker.PickerMediaType.PHOTO];
- if (type === "PickType.all") {
- mediaTypes = [picker.PickerMediaType.PHOTO, picker.PickerMediaType.VIDEO]
- } else if (type === "PickType.video") {
- mediaTypes = [picker.PickerMediaType.VIDEO]
- }
- try {
- let pickerProfile: picker.PickerProfile = {
- cameraPosition: camera.CameraPosition.CAMERA_POSITION_BACK
- };
- let pickerResult: picker.PickerResult = await picker.pick(this.context,
- mediaTypes, pickerProfile);
- console.log("the pick pickerResult is:" + JSON.stringify(pickerResult));
- // 获取uri的路径和媒体类型
- let resultUri = pickerResult["resultUri"] as string
- let mediaTypeTemp = pickerResult["mediaType"] as string
- // 需要把uri转换成沙河路径
- let realPath = FileUtils.getPathFromUri(this.bindContext, resultUri);
- if (mediaTypeTemp === "video") {
- // 需要获取缩略图
- callback.success([{thumbPath: realPath, path: realPath, size: maxSize}])
- } else {
- // 图片无需设置缩略图
- callback.success([{thumbPath: realPath, path: realPath, size: maxSize}])
- }
- } catch (error) {
- let err = error as BusinessError;
- console.error(`the pick call failed. error code: ${err.code}`);
- }
- }
- // 处理保存图片
- async saveImageOrVideo(path: string, isImage: boolean, callback: MethodResult) {
- let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
- atManager.requestPermissionsFromUser(this.context,
- ['ohos.permission.WRITE_IMAGEVIDEO', 'ohos.permission.READ_IMAGEVIDEO'],
- async (err: BusinessError, data: PermissionRequestResult) => {
- if (err) {
- console.log(`requestPermissionsFromUser fail, err->${JSON.stringify(err)}`);
- } else {
- console.info('data:' + JSON.stringify(data));
- console.info('data permissions:' + data.permissions);
- console.info('data authResults:' + data.authResults);
- //转换成uri
- let uriTemp = fileUri.getUriFromPath(path);
- //打开文件
- let fileTemp = fs.openSync(uriTemp, fs.OpenMode.READ_ONLY);
- //读取文件大小
- let info = fs.statSync(fileTemp.fd);
- //缓存照片数据
- let bufferImg: ArrayBuffer = new ArrayBuffer(info.size);
- //写入缓存
- fs.readSync(fileTemp.fd, bufferImg);
- //关闭文件流
- fs.closeSync(fileTemp);
- let phHelper = photoAccessHelper.getPhotoAccessHelper(this.context);
- try {
- const uritemp = await phHelper.createAsset(isImage ? photoAccessHelper.PhotoType.IMAGE :
- photoAccessHelper.PhotoType.VIDEO, isImage ? 'jpg' : "mp4"); // 指定待创建的文件类型、后缀和创建选项,创建图片或视频资源
- const file = await fs.open(uritemp, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
- await fs.write(file.fd, bufferImg);
- await fs.close(file.fd);
- callback.success(true);
- } catch (error) {
- console.error(`error=========${JSON.stringify(error)}`)
- callback.success(false);
- }
- }
- });
- }
- // 处理选中结果
- hanlderSelectResult(uris: Array<string>, callback: MethodResult) {
- // 定义一个path数组
- let pathList: ArrayList<string> = new ArrayList();
- for (let path of uris) {
- // if (path.search("video") < 0) {
- // path = await this.getResizedImagePath(path, this.pendingCallState.imageOptions);
- // }
- this.getVideoThumbnail(path)
- let realPath = FileUtils.getPathFromUri(this.bindContext, path);
- pathList.add(realPath);
- }
- let uriModels: UriModel[] = [];
- pathList.forEach(element => {
- uriModels.push({
- thumbPath: element,
- path: element,
- size: 500
- })
- });
- callback.success(uriModels)
- }
- // 获取视频的缩略图
- async getVideoThumbnail(uri: string) {
- //建立视频检索条件,用于获取视频
- console.log("开始获取缩略图==========")
- let predicates: dataSharePredicates.DataSharePredicates = new dataSharePredicates.DataSharePredicates();
- predicates.equalTo(photoAccessHelper.PhotoKeys.URI, uri);
- let fetchOption: photoAccessHelper.FetchOptions = {
- fetchColumns: [],
- predicates: predicates
- };
- // let size: image.Size = { width: 720, height: 720 };
- let phelper = photoAccessHelper.getPhotoAccessHelper(this.context)
- let fetchResult: photoAccessHelper.FetchResult<photoAccessHelper.PhotoAsset> = await phelper.getAssets(fetchOption);
- console.log(`fetchResult=========${JSON.stringify(fetchResult)}`)
- let asset = await fetchResult.getFirstObject();
- console.info('asset displayName = ', asset.displayName);
- asset.getThumbnail().then((pixelMap) => {
- console.info('getThumbnail successful ' + pixelMap);
- }).catch((err: BusinessError) => {
- console.error(`getThumbnail fail with error: ${err.code}, ${err.message}`);
- });
- }
- }
- // 定义一个返回的对象
- interface UriModel {
- thumbPath: string;
- path: string;
- size: number;
- }
复制代码 工具类代码 :
- /*
- * Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd.
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- import common from '@ohos.app.ability.common';
- import fs from '@ohos.file.fs';
- import util from '@ohos.util';
- import Log from '@ohos/flutter_ohos/src/main/ets/util/Log';
- const TAG = "FileUtils";
- export default class FileUtils {
- static getPathFromUri(context: common.Context | null, uri: string, defExtension?: string) {
- Log.i(TAG, "getPathFromUri : " + uri);
- let inputFile: fs.File;
- try {
- inputFile = fs.openSync(uri);
- } catch (err) {
- Log.e(TAG, "open uri file failed err:" + err)
- return null;
- }
- if (inputFile == null) {
- return null;
- }
- const uuid = util.generateRandomUUID();
- if (!context) {
- return
- }
- {
- const targetDirectoryPath = context.cacheDir + "/" + uuid;
- try {
- fs.mkdirSync(targetDirectoryPath);
- let targetDir = fs.openSync(targetDirectoryPath);
- Log.i(TAG, "mkdirSync success targetDirectoryPath:" + targetDirectoryPath + " fd: " + targetDir.fd);
- fs.closeSync(targetDir);
- } catch (err) {
- Log.e(TAG, "mkdirSync failed err:" + err);
- return null;
- }
- const inputFilePath = uri.substring(uri.lastIndexOf("/") + 1);
- const inputFilePathSplits = inputFilePath.split(".");
- Log.i(TAG, "getPathFromUri inputFilePath: " + inputFilePath);
- const outputFileName = inputFilePathSplits[0];
- let extension: string;
- if (inputFilePathSplits.length == 2) {
- extension = "." + inputFilePathSplits[1];
- } else {
- if (defExtension) {
- extension = defExtension;
- } else {
- extension = ".jpg";
- }
- }
- const outputFilePath = targetDirectoryPath + "/" + outputFileName + extension;
- const outputFile = fs.openSync(outputFilePath, fs.OpenMode.CREATE);
- try {
- Log.i(TAG, "copyFileSync inputFile fd:" + inputFile.fd + " outputFile fd:" + outputFile.fd);
- fs.copyFileSync(inputFile.fd, outputFilePath);
- } catch (err) {
- Log.e(TAG, "copyFileSync failed err:" + err);
- return null;
- } finally {
- fs.closeSync(inputFile);
- fs.closeSync(outputFile);
- }
- return outputFilePath;
- }
- }
- }
复制代码 编写完上述代码就可以运行example工程去测试相关功能了。 当测试完成之后 , 我们可以把整个源码工程拷贝到flutter工程中, 通过集成本地package的方式来集成这个package。或者你可以在发一个新的pacage到pub.dev上, 然后在按照原有方式集成即可。
参考资料
Flutter官方的package开发和使用
开发Plugin
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |