《HarmonyOS技术精讲-Media Library Kit》之媒体库基础与权限配置

在这里插入图片描述

从"权限拒绝"说起

HarmonyOS NEXT 开发里,Media Library Kit(媒体文件管理服务)是处理图片、视频、音频的必备模块。但很多人在第一次接入时,会卡在权限配置这一步。

官方文档虽然描述了ohos.permission.READ_MEDIAohos.permission.WRITE_MEDIA两个权限,但实际配置时容易遇到几个问题:权限声明在module.json5里写错了类型、运行时请求弹窗没弹出来、或者弹窗授权后API还是返回权限不足。

更让人困惑的是,官方示例代码里既有user_grant(用户授权)类型,也有system_grant(系统授权)类型。文档中对于MEDIA权限的描述,有时写的是"user_grant",有时又写的是"system_grant"。这个矛盾导致不少人在API 12版本上踩了坑。

这篇文章一次性把Media Library Kit的权限机制讲清楚,后面实操才不会卡住。

Media Library Kit 到底在管什么

Media Library Kit 提供了一组统一的接口,用来管理设备上的媒体文件。它本质上是一个数据库层,直接操控的是媒体库里的元数据资源URI,而不是直接操作文件系统。

三个核心概念

概念 说明 举例
媒体库(MediaLibrary) 系统级的媒体数据库,所有媒体文件的元数据都存储在这里 类似SQLite数据库
媒体资源(FileAsset) 单张图片、单个视频、单个音频文件在库中的抽象 一张照片对应一个FileAsset实例
媒体类型(MediaType) 区分文件类别的枚举值:IMAGE、VIDEO、AUDIO 用于筛选查询

核心思路是:通过URI访问文件,通过元数据管理文件。不推荐直接使用文件路径操作,因为HarmonyOS对沙箱文件系统的访问限制比较严格。

// 获取媒体资源的典型流程(伪代码示意)
// 1. 获取上下文
// 2. 初始化MediaLibrary实例
// 3. 通过URI查询FileAsset
// 4. 读取文件内容或元数据

权限模型:两种授权模式

HarmonyOS NEXT采用了两种权限授权模型:

  1. system_grant(系统授权):系统在安装时自动授予,用户无感知。一般用于不涉及敏感数据的能力,如网络访问。
  2. user_grant(用户授权):需要运行时弹窗向用户申请,用户同意才生效。媒体库操作必须使用user_grant模式

MEDIA相关权限虽然敏感,但官方设计为两级控制:

  • ohos.permission.READ_MEDIA:读取媒体文件(user_grant)
  • ohos.permission.WRITE_MEDIA:写入/修改媒体文件(user_grant)

两者都必须弹窗申请,不能"静默授权"。这在API 12以后是强制的。

环境说明

DevEco Studio 版本:DevEco Studio 6.1.0 及以上
HarmonyOS SDK 版本:HarmonyOS 6.1.0(23) 及以上
目标设备:手机 / 平板(真机测试更好,模拟器对Media API的支持有限)

第一步:在module.json5中声明权限

打开项目根目录下的AppScope/entry/src/main/module.json5(或者entry级别的module.json5),在requestPermissions数组中添加权限声明:

{
  "module": {
    "name": "entry",
    "type": "entry",
    "description": "$string:module_desc",
    "mainElement": "EntryAbility",
    "deviceTypes": [
      "phone",
      "tablet",
      "2in1"
    ],
    "requestPermissions": [
      {
        "name": "ohos.permission.READ_MEDIA",
        "reason": "$string:permission_read_media_reason",
        "usedScene": {
          "abilities": [
            "EntryAbility"
          ],
          "when": "inuse"
        }
      },
      {
        "name": "ohos.permission.WRITE_MEDIA",
        "reason": "$string:permission_write_media_reason",
        "usedScene": {
          "abilities": [
            "EntryAbility"
          ],
          "when": "inuse"
        }
      }
    ]
  }
}

几点说明:

  • reason字段必须指向一个本地化字符串资源(在resources/base/element/string.json里定义),这块内容会在权限弹窗里向用户展示。
  • when字段可选inuse(使用时授权)或always(始终授权)。推荐用inuse,减少用户担忧。
  • usedScene.abilities数组里写上需要权限的Ability名称。如果遗漏了,可能会导致在某些场景下权限未生效。

第二步:运行时请求权限

只有在module.json5声明之后,代码里才能发起权限请求。请求权限使用的是abilityAccessCtrl模块。

// PermissionHelper.ets
import { abilityAccessCtrl, bundleManager, common } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';

const NEEDED_PERMISSIONS: Array<string> = [
  'ohos.permission.READ_MEDIA',
  'ohos.permission.WRITE_MEDIA'
];

export async function requestMediaPermissions(context: common.UIAbilityContext): Promise<boolean> {
  const atManager = abilityAccessCtrl.createAtManager();

  // 1. 检查是否已有权限
  // bundleManager.getBundleInfoForSelfSync获取当前应用信息
  // 但更推荐用atManager.checkAccessTokenSync检查(更直接)
  for (const perm of NEEDED_PERMISSIONS) {
    // checkAccessTokenSync返回的是一个PermissionGrantInfo对象
    let grantInfo: abilityAccessCtrl.GrantStatus =
      atManager.checkAccessTokenSync(context.abilityInfo.accessTokenId, perm);
    if (grantInfo === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED) {
      continue;
    }

    // 2. 请求没有获得的权限
    // requestPermissionsFromUser会弹窗
    try {
      let result: Array<abilityAccessCtrl.GrantResult> =
        await atManager.requestPermissionsFromUser(context, NEEDED_PERMISSIONS);
      // result是一个数组,每个元素包含permission和grantResult
      // grantResult为0表示授予,-1表示拒绝
      for (let i = 0; i < result.length; i++) {
        if (result[i].grantResult !== 0) {
          console.error(`Permission ${result[i].permission} denied`);
          return false;
        }
      }
      return true;
    } catch (err) {
      let error = err as BusinessError;
      console.error(`requestPermissionsFromUser failed, code: ${error.code}, msg: ${error.message}`);
      return false;
    }
  }

  return true;
}

使用方式:

// 在EntryAbility的onWindowStageCreate或UI页面里
import { requestMediaPermissions } from './PermissionHelper';

@Entry
@Component
struct MediaPage {
  async aboutToAppear() {
    let context = getContext(this) as common.UIAbilityContext;
    let granted = await requestMediaPermissions(context);
    if (!granted) {
      // 权限被拒绝,提示用户或引导去设置
      AlertDialog.show({ message: '需要媒体访问权限才能使用此功能' });
    } else {
      // 开始媒体库操作
    }
  }

  build() {
    // UI...
  }
}

注意点:

  • requestPermissionsFromUser必须在UIAbility的生命周期内调用,不能在Worker线程或后台服务里主动弹窗。
  • 如果用户拒绝授权,应用不能再次自动弹窗。需要引导用户去系统设置中手动开启。可以用canRequestPermissionFromUser判断用户是否勾选“不再询问”。

踩坑记录

坑1:在API 12上,权限名称变了

现象:明明声明了ohos.permission.READ_MEDIA,也请求了,但运行时一直返回-1(拒绝)。

原因:API 12以后,媒体权限拆分成更细的粒度。具体表现为ohos.permission.READ_MEDIA在某些设备上不再支持,需要改用ohos.permission.READ_IMAGEohos.permission.READ_VIDEO等子权限。

解决方案:根据API版本动态选择权限名称。最好在请求前通过canIUse('SystemCapability.Multimedia.MediaLibrary')判断系统能力是否支持。

// 实际开发中这样处理
const TARGET_API_VERSION = bundleManager.getBundleInfoForSelfSync(
  bundleManager.BundleFlag.GET_BUNDLE_INFO_DEFAULT
).targetVersion;

if (TARGET_API_VERSION >= 12) {
  NEEDED_PERMISSIONS = [
    'ohos.permission.READ_IMAGE',
    'ohos.permission.READ_VIDEO',
    'ohos.permission.READ_AUDIO',
    'ohos.permission.WRITE_IMAGE',
    'ohos.permission.WRITE_VIDEO',
    'ohos.permission.WRITE_AUDIO'
  ];
} else {
  NEEDED_PERMISSIONS = [
    'ohos.permission.READ_MEDIA',
    'ohos.permission.WRITE_MEDIA'
  ];
}

坑2:授权成功后再次调用API,仍提示无权限

现象:用户弹窗点了“允许”,UI也显示授权成功,但一调用getMediaLibrary()就报错201: permission denied

原因:权限授权是异步完成的,但Media Library API在首次初始化时需要身份校验。如果校验时权限还未同步到进程级缓存,就会返回错误。这种概率在第一次安装启动时最高。

解决方案:增加一段500ms-1s的延迟,或者循环检查权限状态再初始化。推荐方案是在aboutToAppear里请求权限,在onPageShow里再进行Media Library初始化,确保权限已刷入上下文。

@Entry
@Component
struct MediaPage {
  private mediaLibrary: mediaLibrary.MediaLibrary | null = null;

  async aboutToAppear() {
    // 只请求权限
    let granted = await requestMediaPermissions(getContext(this) as common.UIAbilityContext);
    // 不在这里初始化MediaLibrary
  }

  async pageShow() {
    // 页面显示时初始化
    let context = getContext(this);
    try {
      this.mediaLibrary = mediaLibrary.getMediaLibrary(context);
      console.info('MediaLibrary initialized');
    } catch (err) {
      console.error('Init failed:', err);
    }
  }
}

最佳实践

  1. 尽量申请最小权限:如果只读图片,只申请ohos.permission.READ_IMAGE,不要申请完全读写。减少用户的隐私顾虑。

  2. 权限请求文案写清楚reason字段必须向用户解释清楚“为什么需要这个权限”,不要用通用话术“为了更好的体验”。具体清晰的文案能有效提高授权率。例如:“用于读取您相册中的图片来设置头像”。

  3. 防止权限弹窗被阻断requestPermissionsFromUser必须在Ability的UI线程调用。如果在MainThread里直接调用而不await,可能导致弹窗被系统拦截。始终使用async/await处理。

  4. 权限状态缓存:每次进页面都弹窗肯定不行。建议在全局的AppStoragePersistentStorage里记录权限状态,避免重复请求。

示例代码入口文件

// entry/src/main/ets/pages/Index.ets
@Entry
@Component
struct Index {
  @State mediaGranted: boolean = false;

  async aboutToAppear() {
    let context = getContext(this) as common.UIAbilityContext;
    let granted = await requestMediaPermissions(context);
    this.mediaGranted = granted;
  }

  build() {
    Column() {
      if (this.mediaGranted) {
        Text('权限已获批准,可以操作媒体库')
          .fontSize(20)
          .margin({ top: 100 })
      } else {
        Button('申请媒体权限')
          .onClick(async () => {
            let context = getContext(this) as common.UIAbilityContext;
            let granted = await requestMediaPermissions(context);
            this.mediaGranted = granted;
          })
      }
    }
    .width('100%')
    .height('100%')
  }
}

FAQ

Q:为什么在模拟器上运行,MediaLibrary返回null?

A:模拟器对Media Library Kit的支持有限,特别是图片、视频相关的API可能返回空。建议在真机上进行测试。如果必须模拟,可以检查canIUse('SystemCapability.Multimedia.MediaLibrary')是否返回true。

Q:申请了READ_MEDIA权限,系统弹窗却显示“允许访问所有文件”?

A:这与HarmonyOS的权限设计有关。“所有文件访问”是指ohos.permission.WRITE_MEDIA导致的。如果不需要写入权限,只申请READ_MEDIA(或细粒度子权限),弹窗文案就不会出现“所有文件”。

Q:用户拒绝后,如何判断是否可以再次弹窗?

A:可以在弹窗前用canRequestPermissionFromUser(tokenId, permissionName)判断。如果返回false,说明用户勾选了“不再询问”,此时需要引导用户去系统“设置 > 应用 > 权限管理”手动开启。


如果你也遇到类似问题,可以重点检查权限名称是否匹配API版本、权限请求时机是否在Ability生命周期内、以及是否忽略pageShow延迟初始化。官方文档对user_grantsystem_grant的区分描述得比较模糊,建议以实际运行效果为准进行验证。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐