鸿蒙二维码工具开发笔记
前言
最近在搞鸿蒙工具箱,心血来潮想加个二维码工具。说实话,一开始我还挺自信的,觉得不就是个简单的二维码嘛,能有多难?结果一上手才发现,我太天真了!各种格式、参数、保存、扫描,搞得我头都大了。特别是那个扫描功能,动不动就崩溃,气得我直想砸键盘。
写这个工具的时候踩了不少坑,比如扫描封装、图片保存、权限处理等等。记得有一次,我熬到凌晨三点还在调试那个该死的保存功能,差点把电脑砸了。不过功夫不负有心人,最后都搞定了,现在用起来还挺顺手的。看着用户反馈说"真好用",心里那个美啊!
一、功能说明
1.1 主要功能
- 支持二维码生成
- 支持二维码扫描
- 支持从相册选择图片
- 支持保存二维码到相册
- 支持复制扫描结果
- 收藏功能
1.2 界面功能
- 生成/扫描切换标签
- 内容输入框
- 二维码预览
- 扫描按钮
- 保存按钮
- 复制按钮
二、实现过程
2.1 开发小故事
故事一:扫描功能的崩溃风波
记得刚开始写这个功能的时候,我直接用了系统自带的扫描模块,心想:"这不就是调用个API的事嘛,简单!"结果各种问题接踵而至。最搞笑的是有个用户扫了个模糊的二维码,程序直接崩溃了。用户一看就炸了:"这什么鬼?我要的是友好的错误提示!"我心想:"不就是个错误提示嘛,简单!"结果一上手才发现,得加try-catch,还得给出具体的错误信息,真是让人头大。最后改了好几版,加了个加载动画,这才好多了。
故事二:保存功能的尴尬时刻
还有一次,有个用户说保存功能不好用,我一开始还觉得挺委屈:"这不是挺简单的吗?"结果自己试了一下,发现确实有点问题。最尴尬的是,保存的时候没有提示,而且有时候会保存失败。用户反馈说:"保存了半天,结果不知道存哪去了,气死我了!"我赶紧改了好几版,加了个保存成功的提示,这才好多了。现在想想,真是感谢那位用户的反馈,要不然我还真发现不了这个问题。
后来仔细研究了一下,发现保存功能其实挺复杂的:
- 首先得把二维码转换成图片格式,这个过程中可能会遇到内存问题(有时候一不小心就OOM了,真服了)
- 然后要申请相册权限,这个权限申请还挺麻烦的(有时候弹窗都不弹,气死个人)
- 最后保存到相册的时候,还得处理各种异常情况
最搞笑的是,有一次我测试的时候,保存成功了,但是图片死活找不到。折腾了半天才发现,原来是保存路径的问题。鸿蒙系统的相册路径和普通安卓不太一样,得用专门的API。这个坑踩得我真是哭笑不得。
故事三:大图片的卡死危机
最搞笑的是处理大图片的问题。一开始没考虑性能,结果有个用户发了个超大的图片过来,程序直接卡死了。我心想:"不就是个图片嘛,简单!“结果一上手才发现,得考虑内存占用,还得做异步处理。最后改了好几遍,加了个加载状态,这才好多了。现在想想,真是感谢那位用户的"大图片”,要不然我还真发现不了这个性能问题。
故事四:复制功能的意外发现
还有个小插曲,有个用户说复制功能不好用,我一开始还觉得挺委屈:"这不是挺简单的吗?"结果自己试了一下,发现确实有点问题。最搞笑的是,复制的时候没有提示,而且有时候会复制失败。用户说:"复制了半天,结果不知道复制成功没有,气死我了!"我赶紧改了好几版,加了个复制成功的提示,这才好多了。现在想想,真是感谢那位用户的反馈,要不然我还真发现不了这个问题。
2.2 核心功能实现
- 导入模块
import router from '@ohos.router'
import promptAction from '@ohos.promptAction'
import { scanCore, scanBarcode } from '@kit.ScanKit'
import { hilog } from '@kit.PerformanceAnalysisKit'
import pasteboard from '@ohos.pasteboard'
import common from '@ohos.app.ability.common'
import image from '@ohos.multimedia.image'
import fileio from '@ohos.fileio'
import { photoAccessHelper } from '@kit.MediaLibraryKit'
import { StorageUtil } from '../../utils/storage'
import { componentSnapshot } from '@kit.ArkUI'
import { fileIo } from '@kit.CoreFileKit'
import { BusinessError } from '@kit.BasicServicesKit'
- 扫描功能实现
private startScan() {
try {
let options: scanBarcode.ScanOptions = {
scanTypes: [scanCore.ScanType.ALL],
enableMultiMode: true,
enableAlbum: true
}
scanBarcode.startScanForResult(getContext(this), options, (error, result: scanBarcode.ScanResult) => {
if (error) {
hilog.error(0x0001, 'QRCodeTool', `Failed to get ScanResult. Code: ${error.code}, message: ${error.message}`)
promptAction.showToast({ message: '扫描失败' })
return
}
if (result) {
try {
console.info('Scan result:', JSON.stringify(result))
let scanText = ''
if (typeof result === 'object') {
scanText = result['originalValue'] || result['result'] || result['value'] || ''
}
if (scanText) {
this.scanContent = scanText
hilog.info(0x0001, 'QRCodeTool', `Scan success: ${scanText}`)
promptAction.showToast({ message: '扫描成功' })
} else {
hilog.error(0x0001, 'QRCodeTool', 'Empty scan result')
promptAction.showToast({ message: '未获取到扫描内容' })
}
} catch (err) {
hilog.error(0x0001, 'QRCodeTool', `Parse result error: ${err}`)
promptAction.showToast({ message: '解析扫描结果失败' })
}
}
})
} catch (error) {
hilog.error(0x0001, 'QRCodeTool', `Failed to startScanForResult. Code: ${error.code}, message: ${error.message}`)
promptAction.showToast({ message: '启动扫码失败' })
}
}
- 保存功能实现
// 打包 PixelMap 为 jpg 格式
async packingPixelMap2Jpg(pixelMap: PixelMap): Promise<ArrayBuffer> {
const imagePackerApi = image.createImagePacker();
const packOpts: image.PackingOption = { format: "image/jpeg", quality: 98 };
let imageBuffer: ArrayBuffer = new ArrayBuffer(1);
try {
imageBuffer = await imagePackerApi.packing(pixelMap, packOpts);
} catch (err) {
console.error(`Invoke packingPixelMap2Jpg failed, err: ${JSON.stringify(err)}`);
}
return imageBuffer;
}
// 保存图片到相册
async savePhotoToGallery(context: common.UIAbilityContext) {
let helper = photoAccessHelper.getPhotoAccessHelper(context);
try {
const imageBuffer = await this.packingPixelMap2Jpg(this.pixmap as image.PixelMap)
let uri = await helper.createAsset(photoAccessHelper.PhotoType.IMAGE, 'jpg');
let file = await fileIo.open(uri, fileIo.OpenMode.READ_WRITE | fileIo.OpenMode.CREATE);
await fileIo.write(file.fd, imageBuffer);
await fileIo.close(file.fd);
promptAction.showToast({ message: '已保存至相册!' });
}
catch (error) {
const err: BusinessError = error as BusinessError;
console.error(`Failed to save photo. Code is ${err.code}, message is ${err.message}`);
}
}
- 复制功能实现
private async copyToClipboard(content: string) {
try {
const pasteData = pasteboard.createData(pasteboard.MIMETYPE_TEXT_PLAIN, content)
const systemPasteboard = pasteboard.getSystemPasteboard()
await systemPasteboard.setData(pasteData)
await promptAction.showToast({ message: '已复制到剪贴板' })
} catch (error) {
console.error('复制失败:', error)
await promptAction.showToast({ message: '复制失败' })
}
}
2.3 临时解决方案
-
扫描问题
- 临时方案:直接用扫描模块,简单粗暴
- 问题:格式错误就崩溃,用户体验差
- 最终方案:加try-catch,给出友好提示
-
保存问题
- 临时方案:直接保存,简单
- 问题:没有提示,体验差,权限处理不完善
- 最终方案:
- 加保存成功提示
- 完善权限申请流程
- 优化错误处理
- 确保文件正确关闭
- 添加详细的错误提示
-
大图片问题
- 临时方案:同步处理,简单
- 问题:大图片会卡死
- 最终方案:异步处理,加加载状态
-
复制问题
- 临时方案:直接复制,简单
- 问题:没有提示,体验差
- 最终方案:加复制成功提示
三、踩坑记录
3.1 遇到的问题
-
扫描问题
- 问题:格式错误就崩溃
- 解决:加try-catch
- 建议:给出友好提示
-
保存问题
- 问题:保存失败
- 解决:使用SaveButton和componentSnapshot
- 建议:注意权限申请
-
大图片问题
- 问题:会卡死
- 解决:异步处理
- 建议:加加载状态
-
复制问题
- 问题:没有提示
- 解决:加提示
- 建议:处理错误
3.2 优化建议
-
功能优化
- 支持更多二维码格式
- 加个历史记录
- 支持批量扫描
- 二维码美化、自定义样式、批量生成啥的都能加
-
性能优化
- 优化扫描速度
- 减少内存占用
- 及时释放资源
- 多线程、算法优化、结果缓存、异步处理都可以试试
-
用户体验
- 加个使用说明
- 支持快捷键
- 动画效果、主题、分享、收藏、导入、备份啥的都能加
四、总结
这个二维码工具,基本功能都齐了:
- 支持二维码生成
- 支持二维码扫描
- 支持保存到相册
- 支持复制结果
- 收藏常用设置
说实话,开发这个工具的过程真是让我又爱又恨。爱的是看着它从无到有,一步步变得更好用;恨的是各种坑踩得我头都大了。不过现在想想,这些坑踩得值!要不是这些坑,我还真发现不了这么多可以优化的地方。
有些边角问题其实还没完全搞定,比如有时候扫描还是会有点慢,保存大图片的时候偶尔会卡一下(有时候还会莫名其妙弹个错,真服了)。不过大部分场景都能用,用户反馈也还不错。后面有空再慢慢优化吧,毕竟好产品都是慢慢打磨出来的。
五、参考资料
欢迎体验
这个二维码工具已经集成到鸿蒙开发者工具箱里了,欢迎下载体验!如果遇到问题,欢迎随时反馈,我会继续改进的!
作者:在人间耕耘
邮箱:1743914721@qq.com
版权声明:本文为原创文章
如果你也遇到类似问题,欢迎留言交流。大家一起进步,一起成长!