OpenHarmony API8 升级到 API9 权限与接口修改详解

问题解构

OpenHarmony 从 API8 升级到 API9 是一个重要的版本跨越,涉及到底层运行机制和开发框架的调整。对于初学者而言,核心的痛点主要集中在以下三个维度的变更:

  1. Context(上下文)获取方式的变更:权限申请等系统级操作不再依赖旧的 Ability 获取方式。
  2. 系统权限申请参数的调整:申请权限时的接口调用方法发生了改变。
  3. 媒体库初始化接口的迭代:图片、视频等多媒体资源的初始化逻辑需要重写。
  4. 全局变量存储机制的演进:单例模式在特定场景下失效,需要引入新的全局存储方案。

以下将结合具体代码和场景,详细解析这些变更。

方案推演与代码详解

1. Context 获取方式的变更

在 API8 中,开发者习惯使用 featureAbility 模块来获取 Context,进而申请权限。但在 API9 中,推荐直接在 EntryAbility(主入口)中获取 Context,并将其传递或存储起来供全局使用。

API8 写法(旧版):
通常通过导入 featureAbility 直接获取上下文。

// API8 示例代码
import featureAbility from '@ohos.ability.featureAbility';

// 获取当前 Ability 的 Context
let context = featureAbility.getContext();

API9 写法(新版):
MainAbilityEntryAbilityonCreate 生命周期中,直接使用 this.context

// API9 示例代码 (EntryAbility.ets)
import UIAbility from '@ohos.app.ability.UIAbility';
import window from '@ohos.window';

export default class EntryAbility extends UIAbility {
    onCreate(want, launchParam) {
        // 1. 直接获取 Context
        let context = this.context;
        
        // 2. 建议将此 context 存入 GlobalThis 以便在其他页面访问
        // 关于 GlobalThis 的使用详见下文第 3 点
        globalThis.context = context; 
        
        console.info('[EntryAbility] onCreate Context obtained.');
    }
    // ... 其他生命周期方法
}

2. 系统权限申请接口修改

权限申请是应用开发中的高频操作。在升级过程中,不仅获取 Context 的方式变了,调用权限验证的接口逻辑也有所调整。

场景分析:
假设我们需要申请读取存储的权限(例如 ohos.permission.READ_IMAGEVIDEO,具体权限名需根据实际配置文件 module.json5 确定)。

API8 写法(旧版):
使用 verifyAccessToken 接口,通常结合 featureAbility 获取的 Context 使用。

// API8 权限验证示例
import featureAbility from '@ohos.ability.featureAbility';
import abilityAccessCtrl from '@ohos.abilityAccessCtrl';

let context = featureAbility.getContext();
let manager = abilityAccessCtrl.createAtContext();

// 验证权限
manager.verifyAccessToken(context.applicationInfo.accessTokenId, "ohos.permission.READ_IMAGEVIDEO")
    .then((data) => {
        if (data === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED) {
            console.log('API8: 权限已授予');
        }
    });

API9 写法(新版):
API9 中,abilityAccessCtrl 的使用方式更加规范,通常通过实例来管理权限状态,且 Context 的传递更加明确。

// API9 权限验证示例
import abilityAccessCtrl from '@ohos.abilityAccessCtrl';
import { BusinessError } from '@ohos.base';

// 假设我们已经通过 globalThis.context 获取了上下文
let context = globalThis.context as common.UIAbilityContext;
let atManager = abilityAccessCtrl.createAtContext();

// 验证权限
atManager.verifyAccessToken(context.applicationInfo.accessTokenId, 'ohos.permission.READ_IMAGEVIDEO')
    .then((grantStatus: number) => {
        if (grantStatus === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED) {
            console.info('API9: 权限已授予');
        } else {
            console.error('API9: 权限未授予');
        }
    })
    .catch((err: BusinessError) => {
        console.error(`API9: 验证权限失败, code: ${err.code}, message: ${err.message}`);
    });

3. 全局变量存储与单例失效问题

在 API8 升级到 API9 的过程中,很多开发者反馈原有的单例模式失效,或者在非 UI 页面(如公共工具类)中无法获取到 Context。这是因为模块加载机制或实例化时机发生了变化。

解决方案:利用 GlobalThis
GlobalThis 是一个全局对象,其特性是在应用生命周期内属性保持不变。利用它可以存储 Context 或其他全局单例对象,从而解决跨模块访问的问题 。

代码示例:

首先,在 EntryAbility.ets 中进行初始化存储:

// EntryAbility.ets
import UIAbility from '@ohos.app.ability.UIAbility';

export default class EntryAbility extends UIAbility {
    onCreate(want, launchParam) {
        // 将 Context 存入 GlobalThis
        globalThis.abilityContext = this.context;
        
        // 也可以存储其他全局单例对象
        globalThis.globalDataManager = new GlobalDataManager(); 
    }
}

然后,在任意页面(例如 Index.ets)或工具类中读取:

// Index.ets 或其他工具类文件
import abilityAccessCtrl from '@ohos.abilityAccessCtrl';

// 定义一个类来测试全局访问
class GlobalDataManager {
    data: string = "我是全局单例数据";
}

export struct Index {
    aboutToAppear() {
        // 1. 从 GlobalThis 获取 Context
        let context = globalThis.abilityContext;
        
        if (context) {
            console.info('成功获取到全局 Context');
            // 在这里可以使用 context 进行权限申请等操作
        } else {
            console.error('获取 Context 失败,请检查 EntryAbility 中的赋值');
        }

        // 2. 从 GlobalThis 获取业务单例对象
        let manager = globalThis.globalDataManager as GlobalDataManager;
        console.info(manager.data);
    }

    build() {
        // UI 渲染代码
    }
}

4. 媒体库接口初始化修改

如果应用涉及图片或视频处理,媒体库的初始化接口在 API9 中也有显著变化,通常需要传入正确的 Context。

API8 -> API9 变更要点:

  • API8 中可能使用旧的 media 库接口。
  • API9 中推荐使用 @ohos.file.photoAccessHelper 等新接口,且初始化时必须传入 Context。

代码示例:

// API9 媒体库辅助类初始化示例
import photoAccessHelper from '@ohos.file.photoAccessHelper';

class MediaManager {
    // 初始化方法
    static init() {
        // 必须传入从 GlobalThis 获取的 Context
        let context = globalThis.abilityContext;
        
        if (!context) {
            console.error("MediaManager init failed: Context is null");
            return;
        }

        try {
            // 使用 Context 获取媒体资源示例(此处仅展示接口依赖 Context)
            let helper = photoAccessHelper.getPhotoAccessHelper(context);
            
            // 后续可以使用 helper 进行图片查询等操作
            console.info('媒体库初始化成功');
        } catch (err) {
            console.error(`媒体库初始化失败: ${JSON.stringify(err)}`);
        }
    }
}

总结对比表

下表总结了从 API8 迁移到 API9 的关键差异点:

功能模块 API8 (旧版) API9 (新版) 核心变更点
Context 获取 featureAbility.getContext() this.context (在 Ability 内部) 摆脱对 featureAbility 的依赖,使用原生生命周期属性 。
权限管理 结合 featureAbility 使用 结合 abilityAccessCtrl 实例使用 接口调用更加面向对象,参数传递更规范。
全局变量 依赖模块单例 推荐使用 GlobalThis 解决跨模块 Context 丢失和单例失效问题 。
媒体库 旧版接口 photoAccessHelper 接口重构,初始化强依赖 Context。

通过以上步骤和代码示例,即使是初学者也能清晰地理解 API8 升级到 API9 的核心逻辑。在升级过程中,务必检查官方接口文档,重点关注所有需要传入 Context 的地方,并将其替换为从 GlobalThis 或生命周期中获取的新 Context 。

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐