HarmonyOS实战:GIF图下载突破5M限制保存到相册
前言
最近在鸿蒙项目开发过程中,测试提出图片预览时无法下载的 bug,这么简单的功能怎么会有问题。一开始还以为是手机的问题或者网络不好,拿到测试机复现问题的时候发现下载的 Gif 图确实无法下载成功,报了个2300023 错误码,查看鸿蒙文档才发现图片下载时使用的是 http 请求,但是 http 请求限制了流的大小为 5M,而 Gif 的大小超过了限制,本篇文章将详细带你一步步解决这个问题,同时解决图片链接为 png 但是下载后是 Gif 图的问题,建议点赞收藏!
实现效果
需求分析
- 图片下载突破最大 5M 限制。
- 图片链接以 jpg结尾但是张 GIF。
技术实现
下载突破 5M 限制
官方的 http 请求对数据流大小有限制,最大为 5M,所以需要采用其他方案实现下载功能,这里采用 HttpRequest的requestInStream 对数据流进行分段下载。
- 首先创建 HttpRequest,这里使用 http 创建。
let httpRequest = http.createHttp();
- 调用 http.requestInstream 方法,根据返回码来判断链接是否请求成功,同时需要配置一些网络请求参数。可根据需要配置。
let streamInfo: http.HttpRequestOptions = { method: http.RequestMethod.GET, // 可选,默认为http.RequestMethod.GET // 开发者根据自身业务需要添加header字段 header: { 'Content-Type': 'application/json' }, expectDataType: http.HttpDataType.ARRAY_BUFFER, // 可选,指定返回数据的类型 usingCache: true, // 可选,默认为true priority: 1, // 可选,默认为1 connectTimeout: 60000, // 可选,默认为60000ms readTimeout: 60000, // 可选,默认为60000ms。若传输的数据较大,需要较长的时间,建议增大该参数以保证数据传输正常终止 } httpRequest.requestInStream(imageUrl, streamInfo).then((data: number) => { if (data == 200) { } else { fail() } })
- 订阅数据接收方法 dataReceive,由于数据流是分段下载所有需要额外定义一个 ArrayBuffer 用来接收完整的数据流。
let res = new ArrayBuffer(0); httpRequest.on('dataReceive', (data: ArrayBuffer) => { const newRes = new ArrayBuffer(res.byteLength + data.byteLength); const resView = new Uint8Array(newRes); resView.set(new Uint8Array(res)); resView.set(new Uint8Array(data), res.byteLength); res = newRes; });
- 当图片分段下载时,需要根据订阅dataEnd 方法来判断所有的数据流接收完成,同时将完整的数据流回调出去。
httpRequest.on('dataEnd', () => { success(res) })
- 当网络请求结束时,需要取消所有订阅的事件。
httpRequest.off('headerReceive') httpRequest.off('dataEnd')
判断图片类型
一般图片链接是以具体图片类型结尾,如 .png,.jep 等场景类型,但是也有没有直接给出图片类型的链接,甚至还有图片链接以 jep 结尾,但是图片确实 GIF 格式。这时如果仅仅根据链接判断容易判断失误。
1.先需要创建一个PixelMap,这里根据网络请求返回的 ArrayBuffer 来创建 PixelMap。
let imageSource: image.ImageSource = image.createImageSource(buffer); class tmp { height: number = 100 width: number = 100 }; let options: Record<string, number | boolean | tmp> = { 'alphaType': 0, // 透明度 'editable': false, // 是否可编辑 'pixelFormat': 3, // 像素格式 'scaleMode': 1, // 缩略值 'size': { height: 100, width: 100 } }; // 创建图片大小 imageSource.createPixelMap(options).then((pixelMap: PixelMap) => { })
2. 根据返回的 pixelMap 获取图片的ImageInfo。
pixelMap.getImageInfo().then(async (info: image.ImageInfo) => { }).catch((err: BusinessError) => { })
3. 根据返回的 image.ImageInfo 获取图片的 mimeType,如果是 GIF 图,则为 image/gif,如果时 png 则为 image/png。然后再将图片保存至相册时,指定图片的正确类型即可。
const context = getContext() as common.UIAbilityContext; // 获取getPhotoAccessHelper需要的context const helper = photoAccessHelper.getPhotoAccessHelper(context); // 获取相册管理模块的实例 const uri = await helper.createAsset(photoAccessHelper.PhotoType.IMAGE, info.mimeType == "image/gif"?'gif':'jpg'); // 指定待创建的文件类型、后缀和创建选项,创建图片或视频资源 const file = await fs.open(uri, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE); let r = await fs.write(file.fd, buffer); await fs.close(file.fd);
这里为了方便处理,仅处理 GIF 的类型,其他类型都是 png。
总结
日常使用 http 请求能满足大部分的场景,但是遇到 GIF 图体积较大时,就必须使用分段式下载。将图片保存时必须获取图片信息的真实类型才能保存成功,否则很容易将 GIF 图保存成普通的图片,这一点在日常开发中特别重要,已经学会了的小伙伴,赶快动手试试吧。