错误示例和如何规避
任务一:支持能力集,联想能力集与要求能力集解析
这三个“能力集”是理解和解决 HarmonyOS 跨设备兼容性问题的基石。它们共同决定了一个应用能开发什么功能、能在哪些设备上运行。
能力集 (Capability Set) | 中文名称 | 角色与作用 | 通俗比喻 |
---|---|---|---|
Support Capability Set | 支持能力集 | 设备方:定义了一台设备实际具备哪些系统能力。这是设备的“能力清单”,在设备出厂时就已确定。 | 一台电脑的硬件配置单:有摄像头、有蓝牙、有指纹识别。 |
Required Capability Set | 要求能力集 | 应用方:定义了你的应用运行所必需的最低能力。如果设备不满足这个列表,应用就不能被分发和安装。 | 游戏A的最低配置要求:必须有独立显卡、至少8GB内存。 |
Suggestion Capability Set | 联想能力集 | 开发工具 (IDE):定义了你在 DevEco Studio 中写代码时能获得哪些 API 提示和联想。它通常是所有目标设备“支持能力集”的并集。 | 你买了一堆电脑配件(即使有些还不能用),工具书会把所有配件的用法都告诉你。 |
核心关系与规则:
-
安装规则:应用能否在设备上安装的唯一标准是:应用的“要求能力集”必须是设备“支持能力集”的子集。
- 示例:你的应用要求
NFC.Core
能力,那么它就无法安装在一台没有NFC芯片的手表上。
- 示例:你的应用要求
-
开发时的陷阱:“联想能力集”是为了方便开发,它会展示所有目标设备(如手机+手表)的能力总和。这意味着,你在手机项目中可能会看到并使用仅手表才有的API(比如心率传感器),反之亦然。这就导致了运行时兼容性风险。
-
开发者要做的事:
- 谨慎定义“要求能力集”:只将应用绝对核心、不可或缺的能力放入。例如,一个支付应用可能强依赖NFC,就必须加入。
- 动态判断非必要能力:对于那些“有则更好,无则隐藏”的功能(如心率监测、NFC读卡),不要放入“要求能力集”,而是在代码中进行运行时动态判断。
任务二:系统能力相关 API 收集整理
在 ArkTS 中,进行运行时动态判断主要有两种官方推荐的方法。
1. canIUse()
:官方推荐的显式判断方法
这是最直接、最清晰的方法,用于检查当前设备是否支持某个具体的 SystemCapability
(SysCap)。
- API 签名:
canIUse(syscap: string): boolean;
- 参数:
syscap
- 一个表示系统能力的字符串,例如'SystemCapability.Communication.NFC.Core'
。 - 返回值:
boolean
-true
表示支持,false
表示不支持。 - 优点: 意图明确,代码可读性高,是处理兼容性问题的首选方案。
整理示例:
import hilog from '@ohos.hilog';// 检查NFC基础能力
if (canIUse('SystemCapability.Communication.NFC.Core')) {hilog.info(0x0000, 'SysCapCheck', '设备支持NFC基础能力');
} else {hilog.warn(0x0000, 'SysCapCheck', '设备不支持NFC基础能力');
}// 检查相机基础能力
if (canIUse('SystemCapability.Multimedia.Camera.Core')) {hilog.info(0x0000, 'SysCapCheck', '设备支持相机');
} else {hilog.warn(0x0000, 'SysCapCheck', '设备不支持相机');
}// 检查位置服务能力
if (canIUse('SystemCapability.Location.Location.Core')) {hilog.info(0x0000, 'SysCapCheck', '设备支持位置服务');
} else {hilog.warn(0x0000, 'SysCapCheck', '设备不支持位置服务');
}
2. import
模块判断 & try...catch
异常捕获
当一个设备不支持某个 SysCap 时,其对应的 API 模块在 import
时可能为 undefined
,或者在调用时直接抛出错误。因此可以通过检查模块或捕获异常来判断。
- 方法:
import
模块后,在使用前判断其是否为undefined
。- 将 API 调用放在
try...catch
块中,如果设备不支持,调用会抛出一个错误(通常是BusinessError
,错误码801
表示服务或能力不可用)。
- 优点: 可以在调用失败时直接进入异常处理流程,适合处理那些调用本身就可能失败的复杂场景(如生物认证)。
整理示例:
import nfcController from '@kit.ConnectivityKit'; // 假设新版SDK的导入方式
import userAuth from '@ohos.userIAM.userAuth';
import { BusinessError } from '@ohos.base';// 方式一:检查导入的模块
function checkNfcByImport() {if (nfcController) {console.info('NFC模块已成功导入,设备可能支持NFC。');// 进一步操作} else {console.error('NFC模块导入失败,设备不支持NFC。');}
}// 方式二:使用 try...catch 捕获异常
async function checkBiometrics() {const authParam: userAuth.AuthParam = {challenge: new Uint8Array([1, 2, 3, 4]),// 尝试使用人脸识别authType: [userAuth.UserAuthType.FACE],authTrustLevel: userAuth.AuthTrustLevel.ATL1,};try {const authInstance = userAuth.getUserAuthInstance(authParam, { title: '请验证' });await authInstance.start();console.info('人脸识别认证成功。');} catch (error) {const err = error as BusinessError;// 801 错误码通常表示能力不支持if (err.code === 801) {console.error('设备不支持人脸识别,错误信息: ' + err.message);// 在这里可以引导用户使用密码等其他方式} else {console.error('认证时发生其他错误: ' + JSON.stringify(err));}}
}
任务三:编写跨设备兼容性问题用例与纠错
以下是几个典型的跨设备问题用例,每个都包含“有问题的代码”和“纠错后的代码”。
用例 1:NFC 门禁卡模拟
- 场景: 一个智慧社区应用,希望提供NFC模拟门禁卡功能。在有NFC的旗舰手机上运行良好,但在平板或没有NFC的手机上,点击“添加门卡”按钮时应用崩溃。
- 相关 SysCap:
SystemCapability.Communication.NFC.Core
有问题的 ArkTS 代码:
// 错误示例:未做任何检查,直接调用NFC API
import nfcController from '@kit.ConnectivityKit';@Entry
@Component
struct Index {build() {Column() {Button('添加我的门卡').onClick(() => {// 在不支持NFC的设备上,nfcController为undefined,调用.isNfcAvailable()会直接导致应用崩溃const isNfcAvailable = nfcController.isNfcAvailable();console.log(`NFC可用状态: ${isNfcAvailable}`);// ...后续添加门卡的逻辑})}}
}
问题分析: 在没有NFC能力的设备上,nfcController
导入后是 undefined
。执行 undefined.isNfcAvailable()
会立即抛出 TypeError
,导致应用闪退。
纠错后的 ArkTS 代码:
// 正确示例:使用 canIUse 进行前置判断
import nfcController from '@kit.ConnectivityKit';
import promptAction from '@ohos.promptAction';@Entry
@Component
struct Index {build() {Column() {Button('添加我的门卡').onClick(() => {// 步骤1:先用 canIUse 判断设备是否具备NFC能力if (canIUse('SystemCapability.Communication.NFC.Core')) {// 步骤2:在确保能力存在后,再安全地调用APIconst isNfcAvailable = nfcController.isNfcAvailable();if (isNfcAvailable) {promptAction.showToast({ message: '请将门卡贴近手机NFC区域' });// ...执行后续添加逻辑} else {promptAction.showToast({ message: '请在系统设置中开启NFC功能' });}} else {// 步骤3:在不具备能力的设备上,给出友好提示promptAction.showToast({ message: '抱歉,您的设备不支持NFC功能' });}})}.width('100%').height('100%').justifyContent(FlexAlign.Center)}
}
用例 2:地理位置打卡
- 场景: 企业办公应用,需要员工到达公司后进行地理位置打卡。手机上正常,但在仅有WLAN的平板或智慧屏上,无法获取精确位置,可能导致应用逻辑异常或返回空数据。
- 相关 SysCap:
SystemCapability.Location.Location.Core
,SystemCapability.Location.Location.Gnss
有问题的 ArkTS 代码:
// 错误示例:假设设备一定能获取到高精度位置
import geoLocationManager from '@ohos.geoLocationManager';@Entry
@Component
struct Index {async checkIn() {try {// 直接请求高精度位置,在室内或无GPS模块的设备上可能长时间无返回或失败const location = await geoLocationManager.getCurrentLocation();console.log(`打卡成功,位置: ${location.latitude}, ${location.longitude}`);} catch (err) {console.error('获取位置失败: ' + JSON.stringify(err));}}build() {Button('上班打卡').onClick(() => this.checkIn())}
}
问题分析: getCurrentLocation
在没有GPS模块(如仅WLAN的平板)或信号弱的设备上可能会超时或抛出异常。直接依赖其成功返回,会使应用在这些设备上打卡功能失效。
纠错后的 ArkTS 代码:
// 正确示例:检查能力并提供备选方案
import geoLocationManager from '@ohos.geoLocationManager';
import promptAction from '@ohos.promptAction';@Entry
@Component
struct Index {async checkIn() {// 判断1:设备是否支持位置服务if (!canIUse('SystemCapability.Location.Location.Core')) {promptAction.showToast({ message: '设备不支持定位功能' });return;}// 判断2:设备是否开启了位置服务if (!geoLocationManager.isLocationEnabled()) {promptAction.showToast({ message: '请在系统设置中开启位置服务' });return;}try {// 优先尝试获取缓存位置,速度快const lastLocation = await geoLocationManager.getLastLocation();if (lastLocation) {console.log(`通过缓存位置打卡成功: ${lastLocation.latitude}, ${lastLocation.longitude}`);return;}// 缓存没有再实时获取const location = await geoLocationManager.getCurrentLocation();console.log(`实时定位打卡成功: ${location.latitude}, ${location.longitude}`);} catch (err) {promptAction.showToast({ message: '获取位置失败,请稍后重试' });console.error('获取位置失败: ' + JSON.stringify(err));// 此处可以引导用户使用其他打卡方式,如连接公司WIFI打卡}}build() {Button('上班打卡').onClick(() => this.checkIn())}
}