鸿蒙开发实战:【文件管理】

介绍

本示例主要展示了文件管理相关的功能,使用[@ohos.multimedia.medialibrary]、[@ohos.filemanagement.userFileManager] 、[@ohos.fileio] 、[@ohos.file.fs]、[@ohos.app.ability.contextConstant]

等接口,实现了增添文件、删除文件、查找指定类型文件文件、复制并移动文件、切换加密分区和预览图片、监听文件的功能;

效果预览

首页图片列表图片预览文档删除加密分区
imageimageimageimageimage

使用说明

  1. 在主界面,可以点击图片、视频、文档、音频等按钮进入对应目录的文件列表浏览界面;

  2. 在文档列表浏览界面,点击“+”按钮,可以添加文件;

  3. 在文档列表浏览界面,长按列表项会出现删除图片,点击删除图标可以删除文件;

  4. 在图片文件列表界面,点击图片可以进入图片预览界面。

  5. 进入“我的手机”页面前应先安装[MyPhoneFilePage],在主页点击“我的手机”,进入应用目录下。

    1. 列表的上方是默认的EL2加密分区的应用根目录下文件列表,点击下方两个按钮“data/app/el3”和“data/app/el4”分别进入EL3和EL4加密分区应用根目录,进入后对文件或文件夹操作与EL2加密分区相同。
    2. 点击左下角“新建文件夹”按钮,在弹窗中输入文件夹名称,点击弹窗中的“确定”按钮,完成创建。
    3. 点击新建的文件夹,进入目录,在新目录中点击左下角的“新建文件”,在弹窗的窗口中填写文件名称,然后点击确定,完成创建。
    4. 点击右上角多选按钮,选择需要重命名的文件(仅选中一个文件时可用),点击重命名,在弹窗中修改文件名称,点击“确定”,完成修改。
    5. 点击右上角多选按钮,选择需要复制和移动的文件(可多选,并且不可移动到本身的子目录下),选中后点击左下角“复制和移动”按钮,在页面中点击目标目录会进入该目录,在目标目录下点击“移动到这”按钮,完成文件复制和移动。
    6. 点击右上角多选按钮,选择需要删除的文件,选中后点击右下角“更多”按钮,弹出的菜单中选择“删除”,在弹窗中点击“删除”,即可删除文件。
    7. 点击右上角多选按钮,选择一项需要修改时间的文件,选中后点击右下角“更多”按钮,弹出的菜单中选择“修改文件(夹)时间”,在弹窗的文本框中输入要修改的时间,点击“确定”,即可修改文件(夹)时间。
    8. 点击单个文件,可进入文件内容页面,点击右上角编辑按钮,进入编辑模式编辑、修改文件内容,然后点击右上角的保存按钮保存对文件的修改,点击左上角"X"按钮退出编辑模式,点击返回按钮返回上一页。
  6. 在主页点击“监听文件”,进入文件监听页面。

    1. 点击添加监听按钮,选择IN_CREATE监听,然后点击确定按钮,成功添加IN_CREATE监听。
    2. 点击添加按钮,成功添加一个文件,触发事件后日志显示为相应日志:event:256,fileName为新增文件的路径。
    3. 点击停止监听按钮,选择IN_CREATE监听,然后点击确定按钮,成功停止IN_CREATE监听。
    4. 点击添加按钮,成功添加一个文件,触发事件后日志无变化。
    5. 点击添加监听按钮,选择IN_DELETE监听,然后点击确定按钮,成功添加IN_DELETE监听。
    6. 选择要删除的文件item,左滑后点击删除图标,成功删除一个文件,触发事件后日志显示为相应日志:event:512,fileName为删除文件的路径。
    7. 点击停止监听按钮,选择IN_DELETE监听,然后点击确定按钮,成功停止IN_CREATE监听。
    8. 选择要删除的文件item,左滑后点击删除图标,成功删除一个文件,触发事件后日志无变化。
    9. 点击添加监听按钮,选择IN_MODIFY监听,然后点击确定按钮,成功添加IN_MODIFY监听。
    10. 选择要编辑的文件item,左滑后点击编辑图标,进入文件编辑界面,修改文件名和文件内容,修改之后点击保存图标,页面显示的文件文件大小发生变化,然后点击返回图标后返回文件监听界面,查看触发事件后日志显示为相应日志:event:2,fileName为修改后文件的路径。IN_MODIFY监听只监听文件内容是否发生变化,若单独修改文件名,则不会更新监听日志。
    11. 点击停止监听按钮,选择IN_MODIFY监听,然后点击确定按钮,成功停止IN_MODIFY监听。
    12. 选择要编辑的文件item,左滑后点击编辑图标,进入文件编辑界面,修改文件名和文件内容,修改之后点击保存图标,页面显示的文件文件大小发生变化,然后点击返回图标后返回文件监听界面,查看触发事件后日志无变化。

具体实现:

  • 增添文件、删除文件、查找指定类型文件文件和预览图片的功能接口封装在MediaLibraryManager,源码参考:[MediaLibraryManager.ts]
/** Copyright (c) 2022-2023 Huawei Device 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 image from '@ohos.multimedia.image'import mediaLibrary from '@ohos.multimedia.mediaLibrary'import Logger from '../../utils/Logger'import abilityAccessCtrl from '@ohos.abilityAccessCtrl';import type { Permissions } from '@ohos.abilityAccessCtrl';/*** 主要封装了mediaLibrary库相关的接口*/class MediaLibraryManager {requestPermission(context): void {let permissions: Array<Permissions> = ['ohos.permission.READ_MEDIA','ohos.permission.WRITE_MEDIA']let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();atManager.requestPermissionsFromUser(context, permissions, (code, result) => {Logger.debug('permissionRequest ' + JSON.stringify(code) + ' Result: ' + JSON.stringify(result))})}async getPixelMapByFileAsset(fileAsset: mediaLibrary.FileAsset): Promise<image.PixelMap> {if (fileAsset == undefined) {Logger.error('fileAsset undefined')// 异常情况下统一返回undefined,不建议使用nullreturn undefined}Logger.debug('begin getPixelMapByFileAsset:' + fileAsset.displayName)let fd: number = undefinedlet pixelMap = undefinedtry {fd = await fileAsset.open('rw')Logger.debug('getPixelMapByFileAsset fd: ' + fd)let imageSource = image.createImageSource(fd)Logger.debug('imageSource: ' + JSON.stringify(imageSource))let decodingOptions = {sampleSize: 1,editable: true,desiredSize: { width: 3000, height: 4000 },rotate: 0,desiredPixelFormat: 3,desiredRegion: { size: { height: 6000, width: 8000 }, x: 0, y: 0 },index: 0}pixelMap = await imageSource.createPixelMap(decodingOptions)Logger.debug('pixel size: ' + pixelMap.getPixelBytesNumber())fileAsset.close(fd)} catch (err) {Logger.debug('err: ' + JSON.stringify(err))}return pixelMap}getMediaLibrary(context): mediaLibrary.MediaLibrary {return mediaLibrary.getMediaLibrary(context)}async getFileAssets(context, fileType: mediaLibrary.MediaType): Promise<mediaLibrary.FetchFileResult> {Logger.debug('begin getFileAssets, fileType:' + fileType)let fileKeyObj = mediaLibrary.FileKeylet imagesFetchOption = {selections: fileKeyObj.MEDIA_TYPE + '= ?',selectionArgs: [fileType.toString()],}let fetchFileResult: mediaLibrary.FetchFileResult = undefinedtry {fetchFileResult = await this.getMediaLibrary(context).getFileAssets(imagesFetchOption)Logger.debug('fetchFileResult count:' + fetchFileResult.getCount())} catch (error) {Logger.error('fetchFileResult Error: ' + JSON.stringify(error))}return fetchFileResult}async getFileAssetsByName(context, name: string): Promise<mediaLibrary.FileAsset> {Logger.debug('begin getFileAssetsByName: ' + name)let fileKeyObj = mediaLibrary.FileKeylet imagesFetchOption = {selections: fileKeyObj.DISPLAY_NAME + '= ?',selectionArgs: [name.toString()],}let fetchFileResult: mediaLibrary.FetchFileResult = undefinedlet file: mediaLibrary.FileAsset = undefinedtry {fetchFileResult = await this.getMediaLibrary(context).getFileAssets(imagesFetchOption)Logger.debug('fetchFileResult count:' + fetchFileResult.getCount())file = await fetchFileResult.getFirstObject()} catch (error) {Logger.error('fetchFileResult Error: ' + JSON.stringify(error))}return file}async getThumbnail(fileAsset: mediaLibrary.FileAsset): Promise<image.PixelMap> {let thumbnail = undefinedtry {thumbnail = await fileAsset.getThumbnail()Logger.debug('PixelMap size: ' + thumbnail.getPixelBytesNumber())} catch (error) {Logger.error('getThumbnail Error: ' + JSON.stringify(error))}return thumbnail}async createFileAsset(context, mediaType: mediaLibrary.MediaType,dir: mediaLibrary.DirectoryType, fileName: string): Promise<mediaLibrary.FileAsset> {Logger.debug('createFileAsset: ' + fileName)let media = this.getMediaLibrary(context)let path = await media.getPublicDirectory(dir)return await media.createAsset(mediaType, fileName, path)}async deleteFileAsset(fileAsset: mediaLibrary.FileAsset): Promise<void> {Logger.debug('deleteFileAsset:' + fileAsset.displayName);await fileAsset.trash(true);}}export default new MediaLibraryManager()
  • 使用mediaLibrary.getMediaLibrary来获取MediaLibrary对象;

  • 读取每个文件的数据:使用MediaLibrary.getFileAssets读取满足条件的文件集合FetchFileResult,然后调用FetchFileResult.getFirstObject();

  • 创建模拟文件:使用MediaLibrary.getPublicDirectory()获取系统预定的目录,然后使用MediaLibrary.createAsset();

  • 删除指定路径的文件:使用MediaLibrary.deleteAsset();

  • 获取预览图:使用image.createImageSource()创建指定的文件资源ImageSource,然后调用ImageSource.createPixelMap(),接口参考:[@ohos.multimedia.image] 。

  • MyPhone模块中的文件增删、复制移动、查找功能封装在FileSystem,源码参考:[FileIoManager.ets]。

/** Copyright (c) 2023 Huawei Device 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 storageStatistics from '@ohos.file.storageStatistics';import fileio from '@ohos.fileio';import prompt from '@ohos.promptAction';import { BusinessError } from '@ohos.base';import Logger from '../../utils/Logger';import { FileType, SubDirectoryType } from '../../mock/local/FileData';// 大小和单位const GB_MAGNITUDE: number = 1024 * 1024 * 1024;const MB_MAGNITUDE: number = 1024 * 1024;const KB_MAGNITUDE: number = 1024;const GB_SYMBOL: string = 'GB';const MB_SYMBOL: string = 'MB';const KB_SYMBOL: string = 'KB';const BYTE_SYMBOL: string = 'B';const TAG: string = 'FileIoManager';class FileSystem {// 获取文件大小getFileSize(filePath: string): string {try {let fileSize = fileio.statSync(filePath).size;if (fileSize / GB_MAGNITUDE > 1) {return `${(fileSize / GB_MAGNITUDE).toFixed(2)}${GB_SYMBOL}`;} else if (fileSize / MB_MAGNITUDE > 1) {return `${(fileSize / MB_MAGNITUDE).toFixed(2)}${MB_SYMBOL}`;} else if (fileSize / KB_MAGNITUDE > 1) {return `${(fileSize / KB_MAGNITUDE).toFixed(2)}${KB_SYMBOL}`;} else {return `${fileSize}${BYTE_SYMBOL}`;}} catch (err) {Logger.error(TAG, `getFileSize failed, code is ${err.code}, message is ${err.message}`);throw new Error(`getFileSize failed, code is ${err.code}, message is ${err.message}`);}}/*// 总空间---默认GBasync getTotalSize(): Promise<string> {let totalSize: number;try {totalSize = await storageStatistics.getTotalSize();} catch (err) {let error: BusinessError = err as BusinessError;Logger.error(TAG, `getTotalSize failed, code is ${error.code}, message is ${error.message}`);throw new Error(`getTotalSize failed, code is ${error.code}, message is ${error.message}`);}return `${(totalSize / GB_MAGNITUDE).toFixed(2)}${GB_SYMBOL}`;}// 剩余空间async getFreeSize(): Promise<string> {let freeSize: number;try {freeSize = await storageStatistics.getFreeSize();} catch (err) {let error: BusinessError = err as BusinessError;Logger.error(TAG, `getFreeSize failed, code is ${error.code}, message is ${error.message}`);throw new Error(`getFreeSize failed, code is ${error.code}, message is ${error.message}`);}if (freeSize / GB_MAGNITUDE > 1) {return `${(freeSize / GB_MAGNITUDE).toFixed(2)}${GB_SYMBOL}`;} else if (freeSize / MB_MAGNITUDE > 1) {return `${(freeSize / MB_MAGNITUDE).toFixed(2)}${MB_SYMBOL}`;} else if (freeSize / KB_MAGNITUDE > 1) {return `${(freeSize / KB_MAGNITUDE).toFixed(2)}${KB_SYMBOL}`;} else {return `${freeSize}${BYTE_SYMBOL}`;}}*/// 根据沙箱路径打开目录getSubdirectory(filePath: string): Array<SubDirectoryType> {// 获取目录let dir: fileio.Dir;try {dir = fileio.opendirSync(filePath);} catch (err) {let error: BusinessError = err as BusinessError;Logger.error(TAG, `Open dir of path ${filePath} failed. error code is ${error.code}, message is ${error.message}`);throw new Error(`Open dir of path ${filePath} failed, code is ${error.code}, message is ${error.message}`);}// 读取的结果let dirent: fileio.Dirent;// 结果数组class SubDirectory {name: string = '';type: number = 0;time: Date;childrenNum: number = 0;fileSize: string = '';constructor(time: Date) {this.time = time;}}let subdirectory: Array<SubDirectory> = []do {dirent = dir.readSync();if (dirent) {let subdirectoryNum: number = 0;let fileSize: string = '';let time: Date = new Date();// 如果是文件夹,就读取文件夹中文件的数量if (dirent.isDirectory()) {subdirectoryNum = this.getSubdirectoryNum(filePath + `${dirent.name}`);time = this.getFileTime(filePath + `${dirent.name}`);} else {// 如果不是文件夹,就读取文件大小和时间fileSize = this.getFileSize(filePath + `${dirent.name}`);time = this.getFileTime(filePath + `${dirent.name}`);}let item = new SubDirectory(time);item.name = dirent.name;item.type = dirent.isDirectory() ? 1 : 2;item.childrenNum = subdirectoryNum;item.fileSize = fileSize;subdirectory.push(item);}} while (dirent);return subdirectory;}// 获取目录中的子目录个数getSubdirectoryNum(filePath: string): number {let dir: fileio.Dir;try {dir = fileio.opendirSync(filePath);} catch (err) {let error: BusinessError = err as BusinessError;Logger.error(TAG, `Open dir of path ${filePath} failed. error code is ${error.code}, message is ${error.message}`);throw new Error(`Open dir of path ${filePath} failed, code is ${error.code}, message is ${error.message}`);}// 读取的结果let dirent: fileio.Dirent;// 记录子目录的个数let subdirectoryNum = 0;do {dirent = dir.readSync();if (dirent) {subdirectoryNum++;}} while (dirent);return subdirectoryNum;}// 获取文件修改时间getFileTime(filePath: string): Date {try {let fileTime = fileio.statSync(filePath).mtime;return new Date(fileTime * 1000);} catch (err) {Logger.error(TAG, `getFileTime failed, code is ${err.code}, message is ${err.message}`);throw new Error(`getFileTime failed, code is ${err.code}, message is ${err.message}`);}}// 创建目录createDirectory(filePath: string): void {try {fileio.mkdirSync(filePath);} catch (err) {Logger.error(TAG, `create directory failed, code is ${err.code}, message is ${err.message}`);}}// 创建文件createFile(filePath: string): void {try {fileio.openSync(filePath, 0o100, 0o666);} catch (err) {Logger.error(TAG, `create file failed, code is ${err.code}, message is ${err.message}`);}}// 删除目录和文件---选中项deleteSelected(dataArray: Map<string, number>): void {try {dataArray.forEach((value, data) => {if (value === 1) {fileio.rmdirSync(data);} else {fileio.unlinkSync(data);}})prompt.showToast({ message: $r('app.string.label_delete_success') });} catch (err) {Logger.error(TAG, `delete failed, code is ${err.code}, message is ${err.message}`);}}// 复制文件copyFile(filePath: string, newFilePath: string): void {try {// 遍历数据直接copy所有项目fileio.copyFileSync(filePath, newFilePath);} catch (err) {Logger.error(TAG, `copy file failed, code is ${err.code}, message is ${err.message}`);}}// 重命名文件renameFile(filePath: string, newFilePath: string): void {try {fileio.renameSync(filePath, newFilePath);} catch (err) {Logger.error(TAG, `rename file failed, code is ${err.code}, message is ${err.message}`);}}// 开始移动文件startMoveFile(needMoveFiles: Array<FileType>, newFilePath: string): void {// 遍历数据needMoveFiles.forEach((file: FileType): void => {// 如果是目录if (file.type === 1) {// 先创建这个目录this.createDirectory(`${newFilePath}/${file.fileName}`);// 获取当前文件夹下的所有文件目录let subdirectory = this.getSubdirectory(`${file.filePath}/`);// 处理为我们想要的格式let needMoveFiles: Array<FileType> = [];// 遍历子目录数据subdirectory.forEach((subdirectoryData: SubDirectoryType) => {let data: FileType = {filePath: `${file.filePath}/${subdirectoryData.name}`,fileName: subdirectoryData.name,type: subdirectoryData.type};// 逐一添加进去needMoveFiles.push(data);})// 使用我们的数据递归this.startMoveFile(needMoveFiles, `${newFilePath}/${file.fileName}`);} else {this.copyFile(file.filePath, `${newFilePath}/${file.fileName}`);}})}}export default new FileSystem();
  • 读取文件列表:使用fileio.opendirSync()打开指定目录dir,然后使用dir.readSync()读取文件内容dirent,在调用dirent中相关api获取想要的文件参数;

  • 创建目录:使用fileio.mkdirSync()创建文件夹;

  • 创建文件:使用fileio.openSync()创建文件;

  • 删除选中内容:使用fileio.rmdirSync()删除文件夹,使用fileio.unlinkSync()删除文件;

  • 复制文件:使用fileio.copyFileSync()复制目标文件;

  • 移动文件:使用fileio.mkdirSync()创建指定目录,再递归选中目录中的文件,将内部的文件创建到指定的位置。

  • 修改加密分区:修改应用上下文Context的area,实现当前加密分区的修改。

  • 在Library模块中通过封装FileManager向外提供功能接口,如MediaLibraryManager.getPixelMapByFileAsset(),源码参考:[FileManager.ts]

/** Copyright (c) 2022 Huawei Device 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 mediaLibrary from '@ohos.multimedia.mediaLibrary'import image from '@ohos.multimedia.image'import MediaLibraryManager from './medialibrary/MediaLibraryManager'import LocalMockData from '../mock/local/LocalMockData'/*** 文件管理接口,统一封装了各模块对外提供的功能接口*/class FileManager {/*** 申请文件管理权限* @param context 上下文对象*/requestPermission(context): void {MediaLibraryManager.requestPermission(context)}/*** 通过传入文件对象FileAsset,获取到文件中的图片PixelMap对象* @param fileAsset 文件对象* @return Promise<image.PixelMap> 返回PixelMap对象*/async getPixelMapByFileAsset(fileAsset: mediaLibrary.FileAsset): Promise<image.PixelMap> {return await MediaLibraryManager.getPixelMapByFileAsset(fileAsset)}/*** 通过传入文件类型,获取到不同的文件列表信息* @param context 上下文对象* @param fileType 文件类型* @return Promise<mediaLibrary.FetchFileResult> 返回文件列表信息*/async getFileAssets(context, fileType: mediaLibrary.MediaType): Promise<mediaLibrary.FetchFileResult> {return await MediaLibraryManager.getFileAssets(context, fileType)}/*** 通过文件名称获取文件对象* @param context 上下文对象* @param name 文件名称* @return Promise<mediaLibrary.FileAsset> 返回文件对象信息*/async getFileAssetsByName(context, name: string): Promise<mediaLibrary.FileAsset> {return await MediaLibraryManager.getFileAssetsByName(context, name)}/*** 获取文件缩略图* @param fileAsset 文件对象* @return Promise<image.PixelMap> 返回缩略图信息*/async getThumbnail(fileAsset: mediaLibrary.FileAsset): Promise<image.PixelMap> {return await MediaLibraryManager.getThumbnail(fileAsset)}/*** 创建文件* @param context 上下文对象* @param mediaType 文件类型* @param dir 文件路径* @param fileName 文件名称* @return Promise<mediaLibrary.FileAsset> 返回匹配的文件信息*/async createFileAsset(context, mediaType: mediaLibrary.MediaType,dir: mediaLibrary.DirectoryType, fileName: string): Promise<mediaLibrary.FileAsset> {return await MediaLibraryManager.createFileAsset(context, mediaType, dir, fileName)}/*** 删除文件* @param fileAsset 文件对象*/async deleteFileAsset(fileAsset: mediaLibrary.FileAsset): Promise<void> {await MediaLibraryManager.deleteFileAsset(fileAsset);}/*** 创建模拟文件* @param context 上下文对象* @param mediaType 文件类型* @return Promise<mediaLibrary.FileAsset> 返回文件对象*/async createTxtFileAsset(context): Promise<mediaLibrary.FileAsset> {return await LocalMockData.createFileAsset(context)}/*** 该文件是否支持预览* @param fileName 文件名* @return boolean ture表示支持,false表示不支持*/isSupportPreview(fileName: string): boolean {return LocalMockData.isSupportPreview(fileName)}}export default new FileManager()
  • 如效果预览中的图片列表,读取指定类型的文件:在[FileList.ets]中调用FileManager.getFileAssets();

  • 创建模拟文件:在[FileList.ets] 中调用FileManager.createTxtFileAsset();

  • 删除指定路径的文件:在[FileList.ets] 中调用FileManager.deleteFileAsset();

  • 获取缩略图:在[ThumbnailImage.ets] 中调用FileManager.getThumbnail();

  • 如效果预览中的图片预览,获取预览图:在[ImagePreview.ets] 中调用FileManager.getPixelMapByFileAsset()。

  • 监听文件模块中的文件增删、查找、修改、监听功能封装在MyWatcher。

  • 增加文件、删除文件、监听文件、停止监听文件:在[WatcherFile.ets] 中调用MyWathcer.addFileToWatcher()、MyWathcer.deleteFileToWatcher()、MyWathcer.startWatcher(watcherName)、MyWathcer.stopWatcher();

  • 修改文件:在[EditFile.ets]

中调用MyWatcher.modifyFileToWatcher()。

鸿蒙Next技术知识已更新gitee.com/li-shizhen-skin/harmony-os/blob/master/README.md请前往参考。

QQ浏览器截图20240311144813.png

约束与限制

  1. 本示例仅支持标准系统上运行,支持设备:RK3568;
  2. 本示例为Stage模型,仅支持API11版本SDK,SDK版本号(API Version 11 Beta),镜像版本号(4.1Beta)。
  3. 本示例需要使用DevEco Studio 版本号(4.0Release)及以上版本才可编译运行。
  4. 本示例涉及调用系统权限的接口,需要配置允许权限列表,在配置文件中的“allowed-acls”字段中增加"ohos.permission.READ_MEDIA", “ohos.permission.WRITE_MEDIA”, “ohos.permission.FILE_ACCESS_MANAGER”, "ohos.permission.STORAGE_MANAGER"四个权限。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/758622.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

WRF高精度气象模拟技术及在地学领域中的应用

随着生态文明建设和“碳中和”战略的持续推进&#xff0c;我国及全球气候变化及应对是政府、科学界及商业界关注的焦点。气候是多个领域&#xff08;生态、水资源、风资源及碳中和等问题&#xff09;的主要驱动因素&#xff0c;合理认知气候变化有利于解释生态环境变化机理及过…

(56)删除每行中的最大值

文章目录 1. 每日一言2. 题目3. 解题思路4. 代码5. 结语 1. 每日一言 抱怨过去发生的一切&#xff0c;就等于丧失了力量&#xff0c;白白浪费了往事要带给我们的成长。 2. 题目 题目链接&#xff1a;删除每行中的最大值 给你一个 m x n 大小的矩阵 grid &#xff0c;由若干正…

【动态规划】算法例题

目录 一维动态规划&#xff1a; 137. 爬楼梯 ① 138. 打家劫舍 ② 139. 单词拆分 ② 140. 零钱兑换 ② 141. 最长递增子序列 ② 多维动态规划&#xff1a; 142. 三角形最小路径和 ② 143. 最小路径和 ② 144. 不同路径 II ② 145. 最长回文子串 ② 146. 交错字符串…

SpringCloud-深度理解ElasticSearch

一、Elasticsearch概述 1、Elasticsearch介绍 Elasticsearch&#xff08;简称ES&#xff09;是一个开源的分布式搜索和分析引擎&#xff0c;构建在Apache Lucene基础上。它提供了一个强大而灵活的工具&#xff0c;用于全文搜索、结构化搜索、分析以及数据可视化。ES最初设计用…

​selenium+python做web端自动化测试框架与实例详解教程

最近受到万点暴击&#xff0c;由于公司业务出现问题&#xff0c;工作任务没那么繁重&#xff0c;有时间摸索seleniumpython自动化测试&#xff0c;结合网上查到的资料自己编写出适合web自动化测试的框架&#xff0c;由于本人也是刚刚开始学习python&#xff0c;这套自动化框架目…

【NLP笔记】RNN总结

文章目录 经典RNN单向RNN双向RNNDeep RNNRNN特性总结 变体RNNLSTMGRU 参考及转载内容&#xff1a; 循环神经网络&#xff08;RNN&#xff09;深度学习05-RNN循环神经网络完全理解RNN&#xff08;循环神经网络&#xff09; 传统的CNN&#xff08;Covolutional Neural Network&am…

Redis6.0多线程的疑惑解答

1.Redis6.0之前的版本真的是单线程吗&#xff1f; Redis在处理客户端的请求是&#xff0c;包括获取(socket读)、解析、执行、内容返回(socket 写)等都有一个 顺序串行的主线程处理&#xff0c;这就是所谓的"单线程"。但如果严格来讲并不是单线程&#xff0c;除了主线…

浏览量这么低,还要不要继续坚持?

哈喽&#xff0c;你好啊&#xff0c;我是雷工&#xff01; 曾经在一个群里聊天&#xff0c;有群友看到我两位数的浏览量&#xff0c;说到&#xff1a;浏览量这么低还坚持什么&#xff1f; 浏览量低是事实&#xff0c;大多数是十几二十的&#xff0c;上百的都是少数&#xff0c…

python社区垃圾分类管理平台的设计与实现flask-django-php-nodejs

近些年来&#xff0c;随着科技的飞速发展&#xff0c;互联网的普及逐渐延伸到各行各业中&#xff0c;给人们生活带来了十分的便利&#xff0c;社区垃圾分类管理平台利用计算机网络实现信息化管理&#xff0c;使整个社区垃圾分类管理的发展和服务水平有显著提升。 语言&#xf…

Swift 从获取所有 NSObject 对象聊起:ObjC、汇编语言以及底层方法调用链(一)

概览 Swift 语言给我们的印象是&#xff1a;简洁、现代化和可以“心安神泰”的完全信赖。不过&#xff0c;在一些特殊情况下我们唯有进入 Swift 底层的动态世界方能真正地“随遇而安”。 保安局“刘局长”曾语重心长的教导过我们&#xff1a;“非常时期&#xff0c;用非常方法…

有了std::thread,为什么还需要引入std::jthread?

C进阶专栏&#xff1a;http://t.csdnimg.cn/HGkeZ 目录 1.前言 2.std::is_invocable_v 3.std::jthread 3.1.构造函数 3.2.std::jthread无需join/detach使用实例 3.3.std::jthread处理外部请求中断实 3.4.处理中断请求示例代码 4.特性 5.总结 1.前言 C11以来提供了C原…

Redis实现高可用方案

文章目录 前言一、主从模式1.1 复制流程1.2 优缺点 二、哨兵模式2.1 介绍2.2 哨兵的工作模式 三、集群模式3.1 Cluster集群节点的通讯3.2 Hash Slot插槽算法3.3 Redis Cluster集群3.4 故障转移 前言 如果单机部署Redis服务的话&#xff0c;一旦Reids宕机&#xff0c;那么整个服…

英伟达 V100、A100/800、H100/800 GPU 对比

近期&#xff0c;不论是国外的 ChatGPT&#xff0c;还是国内诸多的大模型&#xff0c;让 AIGC 的市场一片爆火。而在 AIGC 的种种智能表现背后&#xff0c;均来自于堪称天文数字的算力支持。以 ChatGPT 为例&#xff0c;据微软高管透露&#xff0c;为 ChatGPT 提供算力支持的 A…

centos 环境部署

一、安装redis 1. 升级 GCC 最直接的解决方式是升级你的 GCC 编译器到支持 C11 标准的版本。CentOS 7 默认的 GCC 版本较旧&#xff0c;可能不支持 _Atomic。你可以通过以下步骤升级 GCC&#xff1a; 启用 CentOS 的 Software Collections (SCL) 仓库&#xff0c;该仓库提供了…

王老吉药业开拓数字经济“新蓝海”,成立数字经济研究所,科技赋能新品压片糖

3月12日&#xff0c;广州王老吉药业股份有限公司&#xff08;以下简称“王老吉药业”&#xff09;召开第十一届312感恩活动新闻发布会&#xff0c;宣告王老吉数字经济研究所成立&#xff0c;并发布王老吉压片糖新品。一系列重要重要举措&#xff0c;无一不标志着王老吉药业正以…

Java SE入门及基础(44)

目录 I / O流(上) 1. 什么是I / O流 过程分析 I / O的来源 Java 中的 I / O流 2. 字节流 OutputStream 常用方法 文件输出流 FileOutputStream 构造方法 示例 InputStream 常用方法 文件输入流 FileInputStream 构造方法 示例 综合练习 字节流应用场景 Java SE文…

自动化测试报告生成(Allure)

&#x1f345; 视频学习&#xff1a;文末有免费的配套视频可观看 &#x1f345; 关注公众号【互联网杂货铺】&#xff0c;回复 1 &#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 之前尝试使用过testNG自带的测试报告、优化过reportNG的测试报告…

算法·动态规划Dynamic Programming

很多人听到动态规划或者什么dp数组了&#xff0c;或者是做到一道关于动态规划的题目时&#xff0c;就会有一种他很难且不好解决的恐惧心理&#xff0c;但是如果我们从基础的题目开始深入挖掘动规思想&#xff0c;在后边遇到动态规划的难题时就迎难而解了。  其实不然&#xff…

linux:线程互斥

个人主页 &#xff1a; 个人主页 个人专栏 &#xff1a; 《数据结构》 《C语言》《C》《Linux》 文章目录 前言一、线程互斥问题解释互斥量的接口 二、加锁的原理三、 死锁死锁四个必要条件避免死锁 总结 前言 本文是对于线程互斥的知识总结 一、线程互斥 问题 我们先看下面…

财报解读:出海“窗口期”再现,汇量科技保驾护航的底气源于什么

大数据时代&#xff0c;每个人的喜好都被精准捕捉。购物APP、购物网站们&#xff0c;都仿佛一位贴心的时尚顾问。而这源于个性化广告经过深度学习和智能算法得来的结果。 随着广告市场的竞争愈演愈烈&#xff0c;广告主们需要更为精准、高效的个性化投放。近日&#xff0c;深耕…