HarmonyOS NEXT 权限管理体系
HarmonyOS NEXT 的权限体系是业界最细粒度之一,将权限按风险程度划分为多个等级,确保用户的隐私和安全得到精确保护。
Normal 权限
普通权限,风险极低。在 module.json5 中声明后即自动授权,用户感知不到。例如:INTERNET(网络访问)、KEEP_BACKGROUND_RUNNING(后台运行)等。
User_grant 权限
用户授权权限,涉及用户隐私敏感数据。必须在运行时通过系统弹框向用户申请,用户可以拒绝。例如:CAMERA、MICROPHONE、READ_CONTACTS、LOCATION 等。
System_grant 权限
系统授权权限,只有预置应用或具有特定签名的应用才能申请。普通开发者无法获取这类权限。例如:MANAGE_WIFI、INSTALL_BUNDLE 等系统级能力。
权限组
相关权限被分组管理,同一组内只要有一个权限被授予,同组其他权限申请时无需再次弹框。例如:CAMERA 和 MICROPHONE 同属媒体权限组。
完整的权限申请流程
import abilityAccessCtrl, { Permissions } from '@ohos.abilityAccessCtrl'
import { common } from '@kit.AbilityKit'
/**
* 完整权限申请工具类
* 处理:已授权 / 未授权 / 已拒绝 / 永久拒绝 四种情况
*/
class PermissionHelper {
static async requestPermissions(
context: common.UIAbilityContext,
permissions: Permissions[]
): Promise<'granted' | 'denied' | 'permanently_denied'> {
const atManager = abilityAccessCtrl.createAtManager()
// 1. 先检查当前授权状态
const tokenId = await this.getTokenId(context)
const allGranted = await Promise.all(permissions.map(async (perm) => {
const status = await atManager.checkAccessToken(tokenId, perm)
return status === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED
}))
if (allGranted.every(v => v)) {
return 'granted' // 全部已授权
}
// 2. 请求授权
const result = await atManager.requestPermissionsFromUser(context, permissions)
// 3. 检查结果
const allGrantedNow = result.authResults.every(
r => r === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED
)
if (allGrantedNow) return 'granted'
// 4. 检查是否被永久拒绝(不再弹框)
const permanentlyDenied = result.dialogShownResults.some(shown => !shown)
return permanentlyDenied ? 'permanently_denied' : 'denied'
}
// 永久拒绝后,引导用户去设置页手动开启
static async openPermissionSettings(context: common.UIAbilityContext) {
const want: Want = {
bundleName: 'com.huawei.hmos.settings',
abilityName: 'com.huawei.hmos.settings.MainAbility',
uri: 'application_info_entry',
parameters: {
pushParams: context.applicationInfo.name
}
}
await context.startAbility(want)
}
}
// 在 UI 组件中使用
@Entry
@Component
struct CameraEntrance {
build() {
Button('打开相机')
.onClick(async () => {
const result = await PermissionHelper.requestPermissions(
getContext(this) as common.UIAbilityContext,
['ohos.permission.CAMERA']
)
switch (result) {
case 'granted':
// 进入相机页面
break
case 'denied':
promptAction.showToast({ message: '需要相机权限才能使用此功能' })
break
case 'permanently_denied':
// 弹框引导用户去设置页
await PermissionHelper.openPermissionSettings(
getContext(this) as common.UIAbilityContext
)
break
}
})
}
}
系统能力 SysCap
SystemCapability(SysCap)是 HarmonyOS NEXT 描述设备能力的体系。由于鸿蒙生态覆盖手机、平板、手表、电视、车机等众多设备,不同设备支持的能力不同(手表没有相机,电视没有触摸屏)。SysCap 机制让开发者在代码层面声明"此功能需要某种系统能力",AGC 审核和设备过滤时据此判断兼容性:
// 检查设备是否支持特定系统能力
import deviceInfo from '@ohos.deviceInfo'
// 判断设备类型
function getDeviceType(): string {
return deviceInfo.deviceType // 'phone' | 'tablet' | 'tv' | 'wearable' | '2in1'
}
// 根据设备类型展示不同 UI
@Entry
@Component
struct AdaptiveLayout {
@State deviceType: string = deviceInfo.deviceType
build() {
if (this.deviceType === 'tablet' || this.deviceType === '2in1') {
// 平板/折叠屏:双栏布局
Row() {
SideBar().width(280)
MainContent().layoutWeight(1)
}
} else {
// 手机:单栏布局
Column() {
MainContent().width('100%')
}
}
}
}
// 原子化服务(卡片、快捷方式)需要声明 SysCap
// module.json5 中:
// "syscap": "SystemCapability.Window.SessionManager"
加密与安全存储
HarmonyOS NEXT 提供了完整的加密工具包(@ohos.security.cryptoFramework),以及受硬件保护的安全存储(Huks,Hardware Universal Key Store):
import huks from '@ohos.security.huks'
// HUKS 是华为统一密钥库服务
// 密钥存储在 TEE(可信执行环境)中,不可导出
// 即使设备被 root,密钥也无法被提取
class SecureKeyManager {
private static KEY_ALIAS = 'app_master_key'
// 生成 AES-256-GCM 密钥(存储在 TEE 中)
static async generateKey() {
const keyAlias = SecureKeyManager.KEY_ALIAS
const options: huks.HuksOptions = {
properties: [
{ tag: huks.HuksTag.HUKS_TAG_ALGORITHM, value: huks.HuksKeyAlg.HUKS_ALG_AES },
{ tag: huks.HuksTag.HUKS_TAG_KEY_SIZE, value: huks.HuksKeySize.HUKS_AES_KEY_SIZE_256 },
{ tag: huks.HuksTag.HUKS_TAG_PURPOSE, value: huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_ENCRYPT | huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_DECRYPT },
{ tag: huks.HuksTag.HUKS_TAG_BLOCK_MODE, value: huks.HuksCipherMode.HUKS_MODE_GCM },
{ tag: huks.HuksTag.HUKS_TAG_PADDING, value: huks.HuksKeyPadding.HUKS_PADDING_NONE },
]
}
await huks.generateKeyItem(keyAlias, options)
console.log('主密钥已在 TEE 中生成')
}
// 加密数据
static async encrypt(plainText: string): Promise<Uint8Array> {
const textEncoder = new TextEncoder()
const plainBuffer = textEncoder.encode(plainText)
// ... 使用 HUKS session 执行加密
return plainBuffer // 简化示例
}
}
// 使用 cryptoFramework 进行哈希和 HMAC
import cryptoFramework from '@ohos.security.cryptoFramework'
async function hashPassword(password: string): Promise<string> {
const mdAlgo = cryptoFramework.createMd('SHA256')
const textEncoder = new TextEncoder()
const encoded = textEncoder.encode(password)
await mdAlgo.update({ data: encoded })
const digestData = await mdAlgo.digest()
// 转为十六进制字符串
return Array.from(digestData.data)
.map(b => b.toString(16).padStart(2, '0'))
.join('')
}
服务卡片(Form Widget)
服务卡片是 HarmonyOS NEXT 的核心差异化功能之一。用户可以将应用的信息摘要以"卡片"形式固定在桌面或服务中心,无需打开应用即可查看实时数据、执行快捷操作。
FormExtensionAbility
服务卡片的提供者,继承自 ExtensionAbility。负责响应卡片生命周期(创建、更新、销毁),向桌面提供卡片数据。
ArkUI 卡片 UI
卡片的界面同样用 ArkTS + ArkUI 编写,但有严格限制:不支持自定义组件、不能使用网络请求(只读卡片数据)、不能访问 AppStorage。UI 由主应用进程预渲染后发送给桌面展示。
// module.json5 — 声明服务卡片
{
"extensionAbilities": [{
"name": "WeatherFormAbility",
"srcEntry": "./ets/formability/WeatherFormAbility.ets",
"type": "form",
"metadata": [{
"name": "ohos.extension.form",
"resource": "$profile:form_config"
}]
}]
}
// WeatherFormAbility.ets — 卡片提供者
import { FormExtensionAbility, formBindingData, formInfo, formProvider } from '@kit.FormKit'
export default class WeatherFormAbility extends FormExtensionAbility {
// 卡片首次创建时调用,返回初始数据
onAddForm(want: Want): formBindingData.FormBindingData {
const data = {
temperature: '24°C',
weather: '晴天',
city: '深圳',
icon: '☀️'
}
return formBindingData.createFormBindingData(data)
}
// 卡片事件处理(用户点击卡片上的按钮)
onFormEvent(formId: string, message: string) {
console.log(`卡片 ${formId} 收到事件: ${message}`)
if (message === 'refresh') {
this.updateWeather(formId)
}
}
async updateWeather(formId: string) {
// 从服务端获取最新天气(在服务层,而非卡片 UI 层)
const weather = await fetchWeatherData()
const newData = {
temperature: weather.temp,
weather: weather.description,
city: weather.city,
icon: this.getWeatherIcon(weather.code)
}
// 推送新数据给桌面卡片
await formProvider.updateForm(formId, formBindingData.createFormBindingData(newData))
}
getWeatherIcon(code: number): string {
const icons: Record<number, string> = {
100: '☀️', 101: '⛅', 300: '🌧️', 400: '❄️'
}
return icons[code] ?? '🌤️'
}
}
// widget/pages/WeatherCard.ets — 卡片 UI(ArkUI 受限版)
let storageWidgetData = new LocalStorage()
@Entry(storageWidgetData)
@Component
struct WeatherCard {
@LocalStorageProp('temperature') temperature: string = '--°C'
@LocalStorageProp('weather') weather: string = '--'
@LocalStorageProp('city') city: string = '--'
@LocalStorageProp('icon') icon: string = '🌤️'
build() {
Column({ space: 4 }) {
Text(this.city).fontSize(14).fontColor('#adbac7')
Text(this.icon).fontSize(36)
Text(this.temperature)
.fontSize(28)
.fontWeight(FontWeight.Bold)
.fontColor('#e6edf3')
Text(this.weather).fontSize(12).fontColor('#768390')
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
.backgroundColor('#161b22')
.borderRadius(16)
}
}
服务卡片最佳实践
- 卡片 UI 必须极简,避免复杂布局(桌面渲染资源有限)
- 卡片数据更新频率不要太高(系统会限制,建议 30 分钟以上)
- 卡片点击事件只能通过 FormExtensionAbility 处理,不能直接启动其他 Ability
- 提供 2×2、2×4、4×4 多种尺寸的卡片,满足用户不同的桌面布局需求