第4.1篇:canIUse 系统能力检测——按需适配不同设备

系列:鸿蒙系统能力与设备协同篇
难度:⭐⭐ 进阶
前置知识:1.2 ArkUI 声明式 UI 基础
涉及源文件products/default/src/main/ets/pages/SystemCapabilitiesIndex.etsproducts/default/src/main/ets/pages/HarmonyFeaturesPage.etsproducts/default/src/main/ets/services/VoiceRecognitionService.ets


在这里插入图片描述

一、概述

HarmonyOS 生态覆盖了手机、平板、智慧屏、智能穿戴、车机等多种设备形态。不同设备在硬件配置和系统能力上存在天然差异——有的设备支持 GPS 定位,有的没有;有的设备内置了语音识别引擎,有的不支持;有的设备具备分布式数据管理能力,有的则没有。

如果开发者不加区分地在所有设备上调用相同 API,很可能在能力缺失的设备上引发运行时错误,导致应用闪退或功能异常。

canIUse 是 HarmonyOS 提供的运行时能力检测 API,它允许开发者在调用特定系统能力之前,先查询当前设备是否支持该能力。通过 canIUse,我们可以:

  • 按设备动态适配 UI:支持该能力时展示完整功能,不支持时展示降级界面
  • 提前规避运行时错误:在调用系统 API 之前先做检测,避免因能力缺失引发异常
  • 提供友好的用户反馈:不支持时告知用户原因,而不是让用户看到白屏或崩溃弹窗

本文将结合"画伴梦工厂"项目中的三个典型场景,深入讲解 canIUse 的完整用法。


二、API 语法与返回值

canIUse 是 HarmonyOS 提供的全局函数,无需 import 即可直接调用:

const supported: boolean = canIUse(capabilityString: string);

参数说明

参数 类型 说明
capabilityString string 要检测的系统能力字符串,格式为 SystemCapability.XXX.XXX
返回值 boolean true 表示当前设备支持该能力,false 表示不支持

返回值特性

  • 同步返回canIUse 是同步 API,不会产生异步等待,可直接在 aboutToAppearbuild() 或普通函数中使用
  • 运行时不抛异常:即使传入格式错误的能力字符串,也只会返回 false 而非抛出异常
  • 调用时机无关:可以在组件生命周期的任何阶段调用,包括 aboutToAppearbuild() 乃至普通成员函数

三、能力字符串格式

canIUse 的参数是一个层级命名空间字符串,遵循 SystemCapability.领域.子领域.具体能力 的格式。

项目中实际使用的能力字符串示例:

能力字符串 对应能力
SystemCapability.Location.Location.Core 位置服务核心能力(GPS/网络定位)
SystemCapability.Security.AccessToken 安全访问令牌(权限管理)
SystemCapability.DistributedDataManager.KVStore.Core 分布式 KV 数据库
SystemCapability.AI.Core AI 核心能力
SystemCapability.AI.SpeechRecognizer 语音识别能力

能力字符串的命名规则:

  1. 固定前缀:所有系统能力均以 SystemCapability. 开头
  2. 领域分层:第二段为一级领域(如 LocationSecurityAIDistributedDataManager
  3. 子领域细分:第三段为二级子领域(如 Location 下的 LocationAI 下的 SpeechRecognizer
  4. 能力节点:第四段为具体能力名称(如 CoreAccessToken

完整的系统能力列表可以参考 HarmonyOS 官方文档中的 SystemCapability 枚举。

在项目中,能力字符串被统一抽离到 CommonConstants.ets 常量文件中,方便管理和复用:

// CommonConstants.ets 中的能力常量定义
interface CommonConstants {
  // ...
  locationCapability: string;
  // ...
}

const commonConstants: CommonConstants = {
  // ...
  locationCapability: 'SystemCapability.Location.Location.Core',
  // ...
};

四、场景一:SystemCapabilitiesIndex.ets——条件 UI 渲染

SystemCapabilitiesIndex 是项目中专门演示 canIUse 用法的页面,它检测设备是否支持位置服务核心能力,并根据检测结果展示不同的图片和文字。

核心代码

@Entry
@Component
struct SystemCapabilitiesIndex {
  @StorageLink('mainBreakpoint') currentBreakpoint: string = commonConstants.breakpointsInitializeName;
  private readonly breakpointSystem: BreakpointSystem = new BreakpointSystem(commonConstants.breakpointSystemName);
  private locationCapability: boolean = false;

  aboutToAppear() {
    this.locationCapability = canIUse(commonConstants.locationCapability);
    this.breakpointSystem.register(this.getUIContext());
  }

  build() {
    Column() {
      // 根据 locationCapability 选择不同的图片资源
      Image(this.locationCapability ? $r('app.media.ic_location_yes') : $r('app.media.ic_location_no'))
        // 响应式宽度/高度省略...

      // 根据 locationCapability 显示不同的提示文本
      Text(this.locationCapability ? $r('app.string.location_cap_valid') : $r('app.string.location_cap_invalid'))
        .fontColor(this.locationCapability ?
          $r('sys.color.ohos_id_color_text_tertiary') : $r('sys.color.ohos_id_color_text_secondary'))
    }
  }
}

设计要点

1. 检测时机——aboutToAppear

canIUse 的调用放在了 aboutToAppear() 中。这是组件生命周期的初始化阶段,在 aboutToAppear 中检测并保存结果,后续 build() 直接使用缓存值,避免每次渲染都重复调用。

aboutToAppear() {
  this.locationCapability = canIUse(commonConstants.locationCapability);
}

2. 结果存储——普通成员变量

这里有一个值得注意的设计:locationCapability 使用了普通 private 成员变量而非 @State 装饰器。这是因为该值在组件生命周期内不会变化(设备能力不会在运行中动态改变),无需响应式追踪。用普通变量存储可以避免不必要的框架开销。

3. UI 条件渲染——三元表达式

build() 方法中通过三元表达式,根据 locationCapability 的值选择不同的资源:

元素 true(支持) false(不支持)
图片 ic_location_yes(绿色勾选图标) ic_location_no(灰色叉号图标)
文字 location_cap_valid(“当前设备支持位置能力”) location_cap_invalid(“当前设备不支持位置能力”)
文字颜色 三级文本色(柔和灰) 二级文本色(更浅灰色)

这种设计让用户一目了然地感知到设备能力的有无,而不是在功能缺失时看到错误提示。


五、场景二:HarmonyFeaturesPage.ets——功能卡片徽标

HarmonyFeaturesPage 是"鸿蒙能力中心"页面,展示了"画伴梦工厂"项目接入的三个鸿蒙创新方向。每个方向使用 canIUse 在卡片右上角显示"已支持"或"待适配"徽标。

能力数据模型

interface CapabilityFeature {
  title: string;      // 功能标题
  direction: string;  // 创新方向
  capability: string; // 能力字符串
  desc: string;       // 功能描述
  color: string;      // 卡片背景色
}

const HARMONY_FEATURES: CapabilityFeature[] = [
  {
    title: '权限最小化与隐私保护',
    direction: '安全隐私保护',
    capability: 'SystemCapability.Security.AccessToken',
    desc: '拍照仅在识别页申请,分享、下载和跨设备流转前需要家长确认。',
    color: '#EAF8F0'
  },
  {
    title: '作品跨设备流转',
    direction: '全场景一体化协同',
    capability: 'SystemCapability.DistributedDataManager.KVStore.Core',
    desc: '将画作草稿、动画结果和成长记录作为可协同的家庭内容。',
    color: '#EEF0FF'
  },
  {
    title: '图片元素智能识别',
    direction: 'AI 智能化体验',
    capability: 'SystemCapability.AI.Core',
    desc: '基于画面元素、颜色和故事线生成标签、动作和动画脚本。',
    color: '#FFF0DD'
  }
];

FeatureCard Builder 中的动态徽标

@Builder
private FeatureCard(item: CapabilityFeature) {
  Column() {
    Row() {
      // 左侧标题区域
      Column() {
        Text(item.direction).fontSize(11).fontColor(this.brandPurple)
        Text(item.title).fontSize(15).fontWeight(FontWeight.Bold).fontColor(this.ink)
      }
      .alignItems(HorizontalAlign.Start)
      .layoutWeight(1)

      // 右侧能力徽标——关键代码
      Text(canIUse(item.capability) ? '已支持' : '待适配')
        .fontSize(11)
        .fontColor(canIUse(item.capability) ? '#2F9E44' : '#A06A00')
        .padding({ left: 10, right: 10, top: 5, bottom: 5 })
        .backgroundColor(canIUse(item.capability) ? '#EAF8F0' : '#FFF0DD')
        .borderRadius(12)
    }
    Text(item.desc).fontSize(12).fontColor('#68708A').width('100%').margin({ top: 8 })
    Text(item.capability).fontSize(10).fontColor('#8A8FA4').width('100%').margin({ top: 8 })
  }
  .padding(14).width('100%').backgroundColor(item.color).borderRadius(16).margin({ top: 10 })
}

设计要点

1. 实时检测与静态数据解耦

注意 canIUse(item.capability) 是在 @Builder每次渲染时实时调用的。这与场景一在 aboutToAppear 中提前缓存的方式不同。这里的考虑是:

  • FeatureCard 是 @Builder 构建函数,不是独立组件,没有 aboutToAppear 生命周期
  • HARMONY_FEATURES 是静态数据数组,将检测结果预先计算会增加数据模型的复杂度
  • 在卡片渲染时实时检测更简洁,且 canIUse 是轻量同步 API,性能开销极小

2. 两态样式设计

状态 徽标文字 文字颜色 背景色
已支持 “已支持” #2F9E44(绿色) #EAF8F0(浅绿)
待适配 “待适配” #A06A00(橙色) #FFF0DD(浅橙)

绿色表示该能力在当前设备上可用,橙色表示该能力待适配——这是一种非阻断式的 UI 反馈。用户即使看到"待适配"也不会收到错误弹窗,只是知道该功能暂时不可用。

3. 能力字符串展示

卡片底部还展示了完整的能力字符串(如 SystemCapability.Security.AccessToken),这对开发者调试和用户反馈都有帮助——当用户反馈"某个功能不可用"时,可以直接查看对应的能力字符串是否被支持。


六、场景三:VoiceRecognitionService.ets——运行时安全检测

与前两个 UI 展示场景不同,VoiceRecognitionService服务层使用 canIUse,在创建语音识别引擎之前先检测设备是否支持语音识别能力。

const SPEECH_RECOGNIZER_CAPABILITY: string = 'SystemCapability.AI.SpeechRecognizer';

export class VoiceRecognitionService {
  private engine: speechRecognizer.SpeechRecognitionEngine | null = null;
  private capturer: audio.AudioCapturer | null = null;

  async start(callbacks: VoiceRecognitionCallbacks): Promise<void> {
    // ...
    try {
      // 先检测能力,不支持则直接抛出错误
      if (!canIUse(SPEECH_RECOGNIZER_CAPABILITY)) {
        throw new Error('当前设备不支持系统语音识别能力');
      }

      // 创建引擎——只有确认支持后才执行
      this.engine = await this.createSpeechEngine(callbacks);
      this.engine.setListener(this.createListener(callbacks));
      this.capturer = await audio.createAudioCapturer(this.createCapturerOptions());
      // ... 后续操作
    } catch (error) {
      callbacks.onError(this.formatError(error as BusinessError, '语音识别启动失败,请稍后重试'));
      callbacks.onListeningChange(false);
      await this.destroy();
    }
  }
}

设计要点

1. 防御式编程——先检测后执行

这是一个典型的防御式编程模式:

canIUse 检测 → 不通过 → throw Error → catch 中统一处理错误回调
                ↓ 通过
        创建引擎 → 启动采集 → 开始识别

在调用 speechRecognizer.createEngine() 之前先做 canIUse 检测,可以避免在能力缺失的设备上执行复杂的引擎创建和资源分配操作,既提升了性能也避免了潜在的系统级崩溃。

2. 错误统一处理

通过 try-catchcanIUse 检测失败与引擎创建失败统一处理为相同的错误回调流程——调用 callbacks.onError() 通知上层 UI 显示错误提示,并执行清理操作。这种设计让服务调用方无需关心具体的失败原因,统一通过回调处理即可。

3. 能力字符串作为模块级常量

const SPEECH_RECOGNIZER_CAPABILITY: string = 'SystemCapability.AI.SpeechRecognizer';

能力字符串被定义为模块级常量,这在项目中有几个好处:

  • 避免魔法字符串:如果能力字符串在代码中多次出现,定义成常量可以避免拼写错误
  • 便于维护:如果 SDK 升级后能力路径发生变化,只需修改一处
  • 自我文档化:常量命名清晰说明了检测的能力是什么

七、三种使用模式对比

综合项目中的三个场景,canIUse 的使用可以归纳为三种模式:

模式 使用场景 调用时机 检测结果处理
初始化缓存模式 SystemCapabilitiesIndex aboutToAppear 存入成员变量,build() 中条件渲染
实时检测模式 HarmonyFeaturesPage FeatureCard @Builder 渲染时 直接用于 UI 属性(文字、颜色、背景)
防御式编程模式 VoiceRecognitionService 创建引擎之前 失败时 throw Error,走统一错误处理

选择建议

  • UI 条件渲染场景(如页面级别的功能开关):推荐使用初始化缓存模式,在 aboutToAppear 中检测一次,后续直接使用缓存值,避免重复调用
  • 列表/卡片渲染场景(如多个能力项的徽标展示):可以使用实时检测模式,因为 canIUse 是轻量同步 API,在 @Builder 中直接调用代码更简洁
  • 服务层/工具类场景(如在创建系统资源之前):必须使用防御式编程模式,因为涉及系统资源分配和可能的内存泄漏,提前检测可以避免不必要的开销

八、能力不可用时的处理策略

canIUse 返回 false 时,开发者需要设计合理的降级策略。项目中展示了三种不同的处理方式:

8.1 UI 层降级——展示替代信息

对应于 SystemCapabilitiesIndex 的做法:

// 显示"不支持"状态的图片和文字
Image(this.locationCapability ? $r('app.media.ic_location_yes') : $r('app.media.ic_location_no'))
Text(this.locationCapability ? $r('app.string.location_cap_valid') : $r('app.string.location_cap_invalid'))

适用于:展示型页面。用户只是查看某个能力是否存在,不涉及后续操作。降级策略是展示不同的视觉反馈,让用户了解设备能力状态。

8.2 UI 层降级——标记不可用

对应于 HarmonyFeaturesPage 的做法:

Text(canIUse(item.capability) ? '已支持' : '待适配')

适用于:功能列表/目录页。多个能力并列展示,部分能力在当前设备不可用。降级策略是标记"待适配",但仍然展示该功能项,让用户知道该功能的存在。

8.3 服务层降级——提前失败并通知

对应于 VoiceRecognitionService 的做法:

if (!canIUse(SPEECH_RECOGNIZER_CAPABILITY)) {
  throw new Error('当前设备不支持系统语音识别能力');
}

适用于:调用系统资源。如果不检测直接调用,可能引发系统级异常。降级策略是提前抛出可捕获的错误,由上层决定如何提示用户。

8.4 通用设计原则

  1. 不要假设能力存在:即使当前开发机支持某项能力,也不能假设所有目标设备都支持
  2. 不要只检测一次:如果能力检测结果影响后续的业务逻辑,建议在每个关键路径上都做检测
  3. 降级要友好:能力不可用时不应该是崩溃或白屏,而应该是清晰的提示信息
  4. 区分"不可用"与"未实现"canIUse 返回 false 表示设备不支持,而不是开发者没有实现该功能,UI 上应使用"待适配"而非"开发中"

九、最佳实践总结

结合"画伴梦工厂"项目中的实际代码,总结 canIUse 的最佳实践如下:

1. 能力字符串统一管理

将项目中使用的所有能力字符串统一放置在常量模块中:

// CommonConstants.ets
const commonConstants: CommonConstants = {
  locationCapability: 'SystemCapability.Location.Location.Core',
  // 可以在这里持续添加其他能力字符串
};

2. 选择合适的检测时机

// ✅ 推荐:组件初始化时检测
aboutToAppear() {
  this.locationCapability = canIUse(commonConstants.locationCapability);
}

// ✅ 推荐:服务方法开始时检测
async start(callbacks: VoiceRecognitionCallbacks): Promise<void> {
  if (!canIUse(SPEECH_RECOGNIZER_CAPABILITY)) {
    throw new Error('...');
  }
  // ...
}

3. 检测结果与 UI 状态分离

对于 UI 层的条件渲染,将 canIUse 的结果存储在普通成员变量而非 @State 中:

// ✅ 推荐:普通变量(能力在运行时不会变化)
private locationCapability: boolean = false;

// ❌ 不推荐:@State 装饰器(徒增响应式追踪开销)
@State private locationCapability: boolean = false;

4. 服务层检测 + 错误回调

在 Service 类中使用 canIUse 做前置检测,并通过回调将错误信息传递到 UI 层:

// VoiceRecognitionService.ets 中的错误传递模式
callbacks.onError('当前设备不支持系统语音识别能力');

5. 文档化能力依赖

在功能模块的注释中明确标注依赖的系统能力,方便其他开发者理解和排查:

/**
 * VoiceRecognitionService
 * 依赖系统能力:SystemCapability.AI.SpeechRecognizer
 * 在 start() 方法中使用 canIUse 做运行时检测
 */

总结

本文通过"画伴梦工厂"项目中的三个实际场景,全面讲解了 canIUse API 的使用方法和设计模式:

场景 使用文件 检测的能力 设计模式
位置能力检测页 SystemCapabilitiesIndex.ets SystemCapability.Location.Location.Core 初始化缓存 + 条件 UI 渲染
鸿蒙能力中心 HarmonyFeaturesPage.ets 安全/分布式/AI 三大能力 实时检测 + 功能卡片徽标
语音识别服务 VoiceRecognitionService.ets SystemCapability.AI.SpeechRecognizer 防御式编程 + 统一错误处理

canIUse 是鸿蒙多设备适配中最基础也最实用的 API 之一。它虽小,却承载着保证应用在不同设备上稳定运行优雅降级的重要职责。在开发鸿蒙应用时,养成"先检测、后调用"的习惯,能够让应用在面对鸿蒙日益丰富的设备生态时更加健壮。

下一篇: 第 4.2 篇将介绍 abilityAccessCtrl 权限管理——如何在运行时安全地申请和管理系统权限,让应用在权限合规的同时提供流畅的用户体验。


参考源码

本文所有代码均来自项目文件:

  • products/default/src/main/ets/pages/SystemCapabilitiesIndex.ets — canIUse 能力检测演示页面,展示条件 UI 渲染
  • products/default/src/main/ets/pages/HarmonyFeaturesPage.ets — 鸿蒙能力中心页面,展示 FeatureCard 徽标
  • products/default/src/main/ets/services/VoiceRecognitionService.ets — 语音识别服务,展示运行时安全检测模式
  • products/default/src/main/ets/constants/CommonConstants.ets — 常量定义,管理能力字符串
Logo

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

更多推荐