大家好,我是青蓝逐码 的云杰。在鸿蒙应用开发中,多媒体文件的交互能力是提升用户体验的关键一环。无论是实现社交 APP 的图片分享,还是视频类应用的离线缓存,掌握网络图片/视频下载、本地图片转像素进行图片处理、截图功能集成、图片/视频/不同格式文件的保存等核心技能都至关重要。本文将带你解锁鸿蒙开发中多媒体文件处理的全流程技巧,附完整代码示例和避坑指南,让你的应用轻松玩转图片、视频与文件存储。
一、网络资源下载:图片 / 视频获取的两种核心方案
以下载视频为例,下载图片也是一样的道理
request.agent.create
相关链接:developer.huawei.com/consumer/cn... 具体步骤如下:
- 获取上下文对象和缓存目录路径。
ts
let context = getContext(this) as common.UIAbilityContext;
let filesDir = context.cacheDir;
- 定义保存到沙箱路径的视频文件名,使用当前时间戳作为文件名的一部分。
ts
let filePath = `${filesDir}/${Date.now()}.mp4`;
this.filePath = filePath;
- 配置下载任务的相关参数,包括操作类型、URL、保存路径、进度通知、覆盖现有文件和网络类型。
ts
let config: request.agent.Config = {
action: request.agent.Action.DOWNLOAD,
url: 'https://download.blender.org/durian/trailer/sintel_trailer-1080p.mp4',
saveas: filePath,
gauge: true,
overwrite: true,
network: request.agent.Network.WIFI
};
- 使用
request.agent.create
方法创建下载任务。
ts
request.agent.create(context, config)
.then(async (task: request.agent.Task) => {
// 创建成功之后,开始下载任务
task.start((err: BusinessError) => {
if (err) {
request.agent.remove(task.tid)
promptAction.showToast({ message: '下载失败' })
} else {
promptAction.showToast({ message: '开始下载任务!!' })
}
})
task.on('completed', async () => {
// 下载完成--->保存到相册
})
})
在这个函数中,首先获取上下文对象和缓存目录路径,然后定义保存到沙箱路径的视频文件名。接着配置下载任务的相关参数,包括操作类型、URL、保存路径、进度通知、覆盖现有文件和网络类型。最后使用request.agent.create
方法创建下载任务,并在任务创建成功后开始下载。如果下载过程中出现错误,将删除任务并显示提示信息;如果下载成功,将显示开始下载的提示信息。当下载任务完成时,可以在task.on('completed', ...)
回调中进行后续操作,例如将视频保存到相册。
request.downloadFile
相关链接:developer.huawei.com/consumer/cn...
- 获取应用上下文和文件目录:
ts
let context = getContext(this);
let filesDir: string = context.filesDir;
这里,我们首先获取应用的上下文(context
),然后从上下文中获取文件目录(filesDir
)。5
- 定义下载文件的路径:
ts
let filePath = `${filesDir}/${Date.now()}.mp4`;
我们创建一个唯一的文件路径,其中包含当前时间戳(Date.now()
),以确保每次下载的文件名都是唯一的。
- 下载文件:
ts
request.downloadFile(getContext(this), {
url: 'https://download.blender.org/durian/trailer/sintel_trailer-1080p.mp4',
filePath: filePath
}).then((downloadTask: request.DownloadTask) => {
downloadTask.on('complete', () => {
// 保存至相册
})
})
我们使用request.downloadFile
方法下载一个视频文件。这个方法接受两个参数:应用上下文(getContext(this)
)和一个配置对象。配置对象包含以下属性:
url
:要下载的文件的URL。filePath
:下载文件的保存路径。
request.downloadFile
方法返回一个Promise
,当下载任务创建成功时,Promise
会解析为一个DownloadTask
对象。我们可以使用then
方法处理这个对象。
在then
方法的回调中,我们监听DownloadTask
对象的complete
事件。当下载任务完成时,这个事件会被触发。
二、Media 文档图片操作
如何将app.media.图片,转换为PixelMap,再对图片做一系列的操作呢;
相关链接:developer.huawei.com/consumer/cn...
- 从媒体资源中获取图片内容,将其转换为PixelMap首先,通过
resourceManager.getMediaContent
方法获取媒体内容的ArrayBuffer,然后创建一个ImageSource实例。接着,使用getImageInfo
法获取图片的尺寸信息,并通过createPixelMap
方法将ImageSource转换为PixelMap对象。最后,再次调用getImageInfo
方法获取PixelMap的宽度和高度,并将其存储在组件的状态变量中。
ts
async DoPixelMap() {
/**
* 转像素
*/
getContext(this).resourceManager.getMediaContent($r("app.media.img01")).then((data) => {
let arrayBuffer = data.buffer.slice(data.byteOffset, data.byteLength + data.byteOffset)
const imagePacker = image.createImagePacker()
let imageSource: image.ImageSource = image.createImageSource(arrayBuffer);
imageSource.getImageInfo((err, value) => {
//获取图片资源的尺寸
console.log(`图片的尺寸为:width:${value.size.width}height:${value.size.height}`)
if (err) {
return;
}
//转PixelMap
let opts: image.DecodingOptions =
{ editable: true, desiredSize: { height: value.size.height, width: value.size.width } };
imageSource.createPixelMap(opts, (err,
pixelMap) => {
this.imagePixelMap = pixelMap
this.imagePixelMap.getImageInfo().then((value) => {
this.imageWidth = value.size.width
this.imageHeight = value.size.height
})
})
})
})
}
- 图片操作
注意: 对图片操作之前要确保图片的editable属性为True
- 图片旋转
图片旋转的角度取值范围:0-360。超出取值范围时,根据圆周360度自动矫正。例如,-100度与260度效果相同。
如果图片旋转的角度不是90的整数倍,旋转后图片的尺寸会发生改变。
ts
pixelMap.rotateSync(angle);
- 图片裁剪
参数名 | 类型 | 必填 | 说明 |
---|---|---|---|
region | Region | 是 | 裁剪的尺寸。 |
ts
import { BusinessError } from '@kit.BasicServicesKit';
async function CropSync() {
let region : image.Region = { x: 0, y: 0, size: { height: 100, width: 100 } };
if (pixelMap != undefined) {
pixelMap.cropSync(region);
}
}
- 图片缩放
- 建议宽高的缩放倍数取非负数,否则会产生翻转效果。
- 宽高的缩放倍数 = 缩放后的图片宽高 / 缩放前的图片宽高。
参数名 | 类型 | 必填 | 说明 |
---|---|---|---|
x | number | 是 | 宽度的缩放倍数。 |
y | number | 是 | 高度的缩放倍数。 |
level | AntiAliasingLevel | 否 | 采用的缩放算法。 |
ts
import { BusinessError } from '@kit.BasicServicesKit';
async function ScaleSync() {
let scaleX: number = 2.0;
let scaleY: number = 1.0;
if (pixelMap != undefined) {
pixelMap.scaleSync(scaleX, scaleY, image.AntiAliasingLevel.LOW);
}
}
- 图片翻转
参数名 | 类型 | 必填 | 说明 |
---|---|---|---|
horizontal | boolean | 是 | true表示进行水平翻转,false表示不进行水平翻转。 |
vertical | boolean | 是 | true表示进行垂直翻转,false表示不进行垂直翻转。 |
ts
import { BusinessError } from '@kit.BasicServicesKit';
async function FlipSync() {
let horizontal : boolean = true;
let vertical : boolean = false;
if (pixelMap != undefined) {
pixelMap.flipSync(horizontal, vertical);
}
}
三、截图功能开发
组件截图
componentSnapshot
直接使用componentSnapshot可能导致实例不明确的问题,建议使用getUIContext获取UIContext实例,并使用getComponentSnapshot获取绑定实例的componentSnapshot。
ts
this.getUIContext().getComponentSnapshot().get('id')
视频流截图(Xcomponent组件)
参数名 | 类型 | 必填 | 说明 |
---|---|---|---|
surfaceId | string | 是 | 对应Surface的ID,可通过预览组件获取,如XComponent组件。 |
region | Region | 是 | 区域信息。Region.size的宽高需和设置的预览流大小保持一致。 |
ts
import { BusinessError } from '@kit.BasicServicesKit';
async function CreatePixelMapFromSurface(surfaceId: string) {
let region: image.Region = { x: 0, y: 0, size: { height: 100, width: 100 } };
image.createPixelMapFromSurface(surfaceId, region).then(() => {
console.info('Succeeded in creating pixelmap from Surface');
}).catch((error: BusinessError) => {
console.error(`Failed to create pixelmap. code is ${error.code}, message is ${error.message}`);
});
}
屏幕截图
screenshot.pick
获取屏幕截图。此接口仅可在2in1设备上使用。
ts
import { BusinessError } from '@kit.BasicServicesKit';
try {
let promise = screenshot.pick();
promise.then((pickInfo: screenshot.PickInfo) => {
pickInfo.pixelMap.release(); // PixelMap使用完后及时释放内存
}).catch((err: BusinessError) => {
console.log('Failed to pick. Code: ' + JSON.stringify(err));
});
} catch (exception) {
console.error('Failed to pick Code: ' + JSON.stringify(exception));
};
screenshot.capture
获取屏幕全屏截图,此接口仅支持在平板和2in1设备上使用。与pick接口不同之处是可以通过设置不同的displayId截取不同屏幕的截图。
参数名 | 类型 | 必填 | 说明 |
---|---|---|---|
options | CaptureOption | 否 | 截取图像的相关信息。可包含设备ID,即displayId。 此参数不填时,默认截取displayId为0的屏幕截图。 |
ts
import { BusinessError } from '@kit.BasicServicesKit';
import { image } from '@kit.ImageKit';
let captureOption: screenshot.CaptureOption = {
"displayId": 0
};
try {
let promise = screenshot.capture(captureOption);
promise.then((pixelMap: image.PixelMap) => {
pixelMap.release(); // PixelMap使用完后及时释放内存
}).catch((err: BusinessError) => {
console.log('Failed to save screenshot. Code: ' + JSON.stringify(err));
});
} catch (exception) {
console.error('Failed to save screenshot. Code: ' + JSON.stringify(exception));
};
四.文件保存至本地
使用安全控件保存图片/视频
下列以保存图片为案例
- 设置安全控件按钮属性。
- 创建安全控件按钮。
- 调用
MediaAssetChangeRequest.createImageAssetRequest
和PhotoAccessHelper.applyChanges
接口创建图片资源。
ts
import { photoAccessHelper } from '@kit.MediaLibraryKit';
@Entry
@Component
struct Index {
saveButtonOptions: SaveButtonOptions = {
icon: SaveIconStyle.FULL_FILLED,
text: SaveDescription.SAVE_IMAGE,
buttonType: ButtonType.Capsule
} // 设置安全控件按钮属性。
build() {
Row() {
Column() {
SaveButton(this.saveButtonOptions) // 创建安全控件按钮。
.onClick(async (event, result: SaveButtonOnClickResult) => {
if (result == SaveButtonOnClickResult.SUCCESS) {
try {
let context = getContext();
let phAccessHelper = photoAccessHelper.getPhotoAccessHelper(context);
// 需要确保fileUri对应的资源存在。
let fileUri = 'file://com.example.temptest/data/storage/el2/base/haps/entry/files/test.jpg';
// 创建一个图片资源变更请求(申请书)
let assetChangeRequest: photoAccessHelper.MediaAssetChangeRequest = photoAccessHelper.MediaAssetChangeRequest.createImageAssetRequest(context, fileUri);
//创建的申请书提交,执行申请书
await phAccessHelper.applyChanges(assetChangeRequest);
console.info('createAsset successfully, uri: ' + assetChangeRequest.getAsset().uri);
} catch (err) {
console.error(`create asset failed with error: ${err.code}, ${err.message}`);
}
} else {
console.error('SaveButtonOnClickResult create asset failed');
}
})
}
.width('100%')
}
.height('100%')
}
}
弹窗授权保存图片/视频
下列以保存视频为案例
- 指定待保存到媒体库的位于应用沙箱的应用文件图片uri。
- 指定待保存照片的创建选项,包括文件后缀和照片类型,标题和照片子类型可选。
- 调用showAssetsCreationDialog,基于弹窗授权的方式获取的目标媒体文件uri。
- 将来源于应用沙箱的照片内容写入媒体库的目标uri。
ts
async save(srcFileUri: string) {
let context = getContext(this);
let phAccessHelper = photoAccessHelper.getPhotoAccessHelper(context);
try {
let srcFileUris: Array<string> = [
srcFileUri
];
// 指定待保存照片的创建选项,包括文件后缀和照片类型,标题和照片子类型可选
let photoCreationConfigs: Array<photoAccessHelper.PhotoCreationConfig> = [
{
title: `${Date.now()}`, // 可选
fileNameExtension: 'mp4',
photoType: photoAccessHelper.PhotoType.VIDEO,
subtype: photoAccessHelper.PhotoSubtype.DEFAULT, // 可选
}
];
// 基于弹窗授权的方式获取媒体库的目标uri
let desFileUris: Array<string> = await phAccessHelper.showAssetsCreationDialog(srcFileUris, photoCreationConfigs);
// 将来源于应用沙箱的内容写入媒体库的目标uri
let desFile: fileIo.File = await fileIo.open(desFileUris[0], fileIo.OpenMode.WRITE_ONLY);
let srcFile: fileIo.File = await fileIo.open(srcFileUri, fileIo.OpenMode.READ_ONLY);
await fileIo.copyFile(srcFile.fd, desFile.fd);
fileIo.closeSync(srcFile);
fileIo.closeSync(desFile);
promptAction.showToast({ message: '下载成功' })
} catch (err) {
console.error(`failed to create asset by dialog successfully errCode is: ${err.code}, ${err.message}`);
}
}
保存文件
- 选择文件保存位置
- 将数据导出至我们选择的位置
以保存txt文件为案例
ts
import { common } from '@kit.AbilityKit'
import { BusinessError } from '@kit.BasicServicesKit'
import { picker } from '@kit.CoreFileKit'
import { fileIo as fs } from '@kit.CoreFileKit';
@Entry
@Component
struct KeyEventExample {
@State Text: string = '你好啊'
@State uris: Array<string> = [];
build() {
Column() {
Text(this.Text)
Button('创建保存文件')
.onClick(() => {
let documentSaveOptions = new picker.DocumentSaveOptions();
documentSaveOptions.newFileNames = ['DocumentViewPicker01.txt'];
// 请确保 getContext(this) 返回结果为 UIAbilityContext
let context = getContext(this) as common.Context;
// 创建文件选择器实例。
const documentViewPicker = new picker.DocumentViewPicker(context);
//用户选择目标文件夹,用户选择与文件类型相对应的文件夹,即可完成文件保存操作。保存成功后,返回保存文档的URI。
documentViewPicker.save(documentSaveOptions).then((documentSaveResult: Array<string>) => {
this.uris = documentSaveResult;
})
.catch((err: BusinessError) => {
AlertDialog.show({
message: JSON.stringify(`Invoke documentViewPicker.save failed, code is ${err.code}, message is ${err.message}`,
null, 2)
})
})
})
Button('写入保存文件')
.onClick(() => {
//这里需要注意接口权限参数是fs.OpenMode.READ_WRITE。
let file = fs.openSync(this.uris[0], fs.OpenMode.READ_WRITE);
let writeLen: number = fs.writeSync(file.fd, '吾乃天下第一');
AlertDialog.show({ message: JSON.stringify('write data to file succeed and size is:' + writeLen, null, 2) })
fs.closeSync(file);
})
}.height(300).width('100%').padding(35)
}
}
封装
ts
/**
* 保存到本地
*/
import { common } from '@kit.AbilityKit';
import { fileIo as fs, picker } from '@kit.CoreFileKit';
import { CountType } from '../pages/FileTextPage';
import { promptAction } from '@kit.ArkUI';
class SaveFile {
uris: Array<string> = [];
async getAdreessTxt() {
let documentSaveOptions = new picker.DocumentSaveOptions();
documentSaveOptions.newFileNames = [`数据${Date.now()}.txt`];
// 请确保 getContext(this) 返回结果为 UIAbilityContext
let context = getContext(this) as common.Context;
// 创建文件选择器实例。
const documentViewPicker = new picker.DocumentViewPicker(context);
this.uris = await documentViewPicker.save(documentSaveOptions)
}
async getAdreessJson() {
let documentSaveOptions = new picker.DocumentSaveOptions();
documentSaveOptions.newFileNames = [`数据${Date.now()}.json`];
// 请确保 getContext(this) 返回结果为 UIAbilityContext
let context = getContext(this) as common.Context;
// 创建文件选择器实例。
const documentViewPicker = new picker.DocumentViewPicker(context);
this.uris = await documentViewPicker.save(documentSaveOptions)
}
async writeFile(数据) {
//这里需要注意接口权限参数是fs.OpenMode.READ_WRITE。
let file = fs.openSync(this.uris[0], fs.OpenMode.READ_WRITE);
let writeLen: number = fs.writeSync(file.fd, JSON.stringify(数据));
promptAction.showToast({ message: '导出成功' })
fs.closeSync(file);
}
}
export const savefile = new SaveFile()
总结
以上我们详细拆解了鸿蒙开发中多媒体文件处理的全流程核心技术,涵盖网络图片/视频下载、本地像素处理、截图集成及多格式文件存储等关键场景,附可直接复用的代码示例与避坑经验。期待你将这些技术应用于实际项目,提升应用交互体验,若有任何实践疑问或创新想法,欢迎随时留言探讨,后续将带来更多鸿蒙开发实战干货!