当前位置:首页 > 技术 > 正文内容

HarmonyOS 开发指南:系统API调用与兼容性处理实战

访客 技术 2026年8月15日 1

项目概述

本示例项目旨在演示 HarmonyOS 应用开发中如何高效集成各类系统级能力。通过具体的代码实现,展示了应用与系统服务的交互方式,涵盖了身份识别、意图跳转、多媒体调用、网络监控以及安全隐私保护等核心场景。

关键技术要点

1. 应用匿名标识符 (AAID) 的生命周期

在获取 AAID 时需注意其持久性特征。经测试验证,当用户完全卸载应用并重新安装后,系统生成的 AAID 值会发生变更。若业务逻辑需要标识符在重装后保持不变,建议引入如 harmony-utils 等第三方库或采用服务端存储方案来实现跨周期的用户唯一性识别。

2. 窗口隐私保护机制

启用隐私模式(防截屏/录屏)是保护敏感数据的重要手段。当该模式激活时,系统会拦截用户的截屏操作并弹出"当前页面涉及隐私内容"的提示。对于录屏行为,被保护页面的内容将显示为黑屏,从而有效防止敏感信息泄露。

3. API 兼容性检测

由于 HarmonyOS 设备形态多样,部分 API 并非在所有设备上通用。开发中若遇到 IDE 提示"The API is not supported on all devices",必须结合 canIUse 方法进行运行时能力检测。

实现步骤如下:

  1. 查阅文档:确认目标 API 是否存在设备限制。
  2. 定位系统能力:找到对应的 SystemCapability(例如 SystemCapability.Telephony.Call)。
  3. 条件执行:在调用逻辑前增加判断分支。

以下代码演示了在拨打电话前进行能力检测的安全做法:

import { call } from '@kit.TelephonyKit';
import { promptAction } from '@kit.ArkUI';
import { BusinessError } from '@kit.BasicServicesKit';

// 定义系统能力常量
const CAP_TELEPHONY_CALL = 'SystemCapability.Telephony.Call';

function attemptMakeCall phoneNumber: string) {
  if (canIUse(CAP_TELEPHONY_CALL)) {
    call.makeCall(phoneNumber, (err: BusinessError) => {
      if (err) {
        console.error(`拨号失败: ${JSON.stringify(err)}`);
      } else {
        console.log('拨号成功');
      }
    });
  } else {
    promptAction.showToast({
      message: '当前设备不支持电话功能',
      duration: 2000
    });
  }
}

完整代码实现

以下代码重构了原有逻辑,优化了变量命名与结构,并封装了各类功能的调用入口。

import { bundleManager, common, Want } from '@kit.AbilityKit';
import { BusinessError, deviceInfo } from '@kit.BasicServicesKit';
import { call } from '@kit.TelephonyKit';
import { scanBarcode, scanCore } from '@kit.ScanKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { promptAction, window } from '@kit.ArkUI';
import { connection } from '@kit.NetworkKit';
import { camera, cameraPicker } from '@kit.CameraKit';
import { photoAccessHelper } from '@kit.MediaLibraryKit';
import { resourceManager } from '@kit.LocalizationKit';
import { AAID } from '@kit.PushKit';

// 定义功能菜单项的数据结构
interface FeatureMenu {
  label: string;
  action: Want | (ctx: common.UIAbilityContext) => void;
}

// --- 意图构造器 ---

function buildSmsIntent(): Want {
  return {
    bundleName: 'com.ohos.mms',
    abilityName: 'com.ohos.mms.MainAbility',
    parameters: {
      contactObjects: JSON.stringify([{ contactsName: 'LiSi', telephone: '13900001111' }]),
      content: ' HarmonyOS 测试短信',
      pageFlag: 'conversation'
    }
  };
}

function buildWebIntent(): Want {
  return {
    action: 'ohos.want.action.viewData',
    entities: ['entity.system.browsable'],
    uri: 'https://developer.huawei.com'
  };
}

function buildAppStoreIntent(): Want {
  const targetBundle = "com.example.app";
  return {
    action: 'ohos.want.action.appdetail',
    uri: `store://appgallery.huawei.com/app/detail?id=${targetBundle}`,
  };
}

function buildSettingsIntent(uri: string): Want {
  return {
    bundleName: 'com.huawei.hmos.settings',
    abilityName: 'com.huawei.hmos.settings.MainAbility',
    uri: uri
  };
}

// --- 业务逻辑函数 ---

function handleCall() {
  const CAPABILITY = 'SystemCapability.Telephony.Call';
  if (canIUse(CAPABILITY)) {
    call.makeCall("13800138000", (err: BusinessError) => {
      if (err) {
        console.error(`Call failed: ${JSON.stringify(err)}`);
      } else {
        console.log('Call initiated');
      }
    });
  } else {
    promptAction.showToast({ message: '设备不支持通话功能', duration: 2000 });
  }
}

function handleScan(context: common.UIAbilityContext) {
  const SCAN_CAP = 'SystemCapability.Multimedia.Scan.ScanBarcode';
  const CORE_CAP = 'SystemCapability.Multimedia.Scan.Core';

  if (canIUse(SCAN_CAP) && canIUse(CORE_CAP)) {
    const scanOptions: scanBarcode.ScanOptions = {
      scanTypes: [scanCore.ScanType.ALL],
      enableMultiMode: true,
      enableAlbum: true
    };

    scanBarcode.startScanForResult(context, scanOptions, (error: BusinessError, result: scanBarcode.ScanResult) => {
      if (error) {
        hilog.error(0x0001, 'ScanDemo', `Scan error: ${error.message}`);
        return;
      }
      hilog.info(0x0001, 'ScanDemo', `Result: ${JSON.stringify(result)}`);
      promptAction.showToast({ message: `扫码结果: ${result.originalValue}`, duration: 2000 });
    });
  } else {
    promptAction.showToast({ message: '设备不支持扫码', duration: 2000 });
  }
}

function checkAppInstallation() {
  try {
    // 测试高德地图链接
    const testLink = "amapuri://";
    const isInstalled = bundleManager.canOpenLink(testLink);
    const msg = isInstalled ? "应用已安装" : "应用未安装";
    promptAction.showToast({ message: msg, duration: 2000 });
  } catch (err) {
    const error = err as BusinessError;
    if (error.code === 17700056) {
      promptAction.showToast({ message: '请在 module.json5 中配置 querySchemes', duration: 2000 });
    }
  }
}

function showAppInfo() {
  const flags = bundleManager.BundleFlag.GET_BUNDLE_INFO_DEFAULT | 
                bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION;
  const info = bundleManager.getBundleInfoForSelfSync(flags);
  
  // 简化展示,仅打印日志
  console.info('App Info:', JSON.stringify(info));
  promptAction.showToast({ message: `包名: ${info.name}`, duration: 2000 });
}

function showDeviceInfo() {
  const infoStr = `品牌: ${deviceInfo.brand}\n型号: ${deviceInfo.productSeries}\nAPI: ${deviceInfo.sdkApiVersion}`;
  promptAction.showToast({ message: infoStr, duration: 3000 });
}

async function togglePrivacyMode(enable: boolean, context: common.UIAbilityContext) {
  try {
    const windowStage = await window.getLastWindow(context);
    windowStage.setWindowPrivacyMode(enable, (err: BusinessError) => {
      if (err.code) {
        console.error(`Set privacy mode failed: ${JSON.stringify(err)}`);
        if (err.code === 201) {
           promptAction.showToast({ message: '缺少权限: ohos.permission.PRIVACY_WINDOW', duration: 2000 });
        }
        return;
      }
      const status = enable ? "开启" : "关闭";
      promptAction.showToast({ message: `已${status}防截屏`, duration: 2000 });
    });
  } catch (e) {
    console.error(`Exception: ${JSON.stringify(e)}`);
  }
}

function monitorNetworkStatus() {
  const netConn = connection.createNetConnection();
  netConn.register((err: BusinessError) => {
    if (err) {
       console.error(`Network register fail: ${err.code}`);
       return;
    }
    promptAction.showToast({ message: '开始监听网络', duration: 2000 });
  });

  netConn.on('netLost', () => {
    promptAction.showToast({ message: '网络已断开', duration: 2000 });
  });

  netConn.on('netConnectionPropertiesChange', () => {
    promptAction.showToast({ message: '网络状态变更', duration: 2000 });
  });
}

async function capturePhoto(context: common.UIAbilityContext) {
  try {
    const profile: cameraPicker.PickerProfile = {
      cameraPosition: camera.CameraPosition.CAMERA_POSITION_BACK
    };
    const result = await cameraPicker.pick(context, [cameraPicker.PickerMediaType.PHOTO], profile);
    if (result.resultCode === 0) {
      context.eventHub.emit("imageUpdate", result.resultUri);
    }
  } catch (err) {
    console.error('Camera error', JSON.stringify(err));
  }
}

function pickGalleryImage(context: common.UIAbilityContext) {
  try {
    const options = new photoAccessHelper.PhotoSelectOptions();
    options.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE;
    options.maxSelectNumber = 1;
    
    const picker = new photoAccessHelper.PhotoViewPicker();
    picker.select(options).then((res: photoAccessHelper.PhotoSelectResult) => {
      if (res.photoUris && res.photoUris.length > 0) {
        context.eventHub.emit("imageUpdate", res.photoUris[0]);
      }
    }).catch((err: BusinessError) => {
      console.error('Gallery error', err.message);
    });
  } catch (e) {
    console.error('Pick exception', JSON.stringify(e));
  }
}

function setScreenOrientation(orientation: window.Orientation) {
  window.getLastWindow(getContext()).then((win) => {
    win.setPreferredOrientation(orientation);
  });
}

function getCurrentOrientation() {
  const config = getContext().resourceManager.getConfigurationSync();
  const isPortrait = config.direction === resourceManager.Direction.DIRECTION_VERTICAL;
  promptAction.showToast({ message: isPortrait ? "当前为竖屏" : "当前为横屏", duration: 2000 });
}

function showDeviceType() {
  const type = getContext().resourceManager.getDeviceCapabilitySync().deviceType;
  let typeStr = "未知设备";
  switch (type) {
    case resourceManager.DeviceType.DEVICE_TYPE_PHONE: typeStr = "手机"; break;
    case resourceManager.DeviceType.DEVICE_TYPE_TABLET: typeStr = "平板"; break;
    case resourceManager.DeviceType.DEVICE_TYPE_TV: typeStr = "电视"; break;
    // ... 其他类型
  }
  promptAction.showToast({ message: typeStr, duration: 2000 });
}

function getLocalIp() {
  connection.getDefaultNet().then((handle: connection.NetHandle) => {
    connection.getConnectionProperties(handle, (err, data) => {
      if (!err && data.linkAddresses.length > 0) {
        const ip = data.linkAddresses[0].address.address;
        promptAction.showToast({ message: `IP地址: ${ip}`, duration: 2000 });
      }
    });
  });
}

function fetchAAID() {
  AAID.getAAID().then((id: string) => {
    promptAction.showToast({ message: `AAID: ${id}`, duration: 3000 });
  });
}

// --- UI 入口 ---

@Entry
@Component
struct SystemApiDemo {
  @State menuList: FeatureMenu[] = [];
  @State previewUri: string = "";

  aboutToAppear(): void {
    // 使用 EventHub 接收图片选择结果
    getContext(this).eventHub.on('imageUpdate', (uri: string) => {
      this.previewUri = uri;
    });

    // 初始化功能列表
    this.menuList = [
      { label: '获取设备 AAID', action: fetchAAID },
      { label: '发送短信', action: buildSmsIntent() },
      { label: '打开浏览器', action: buildWebIntent() },
      { label: '跳转应用市场', action: buildAppStoreIntent() },
      { label: 'WLAN 设置', action: buildSettingsIntent('wifi_entry') },
      { label: '输入法设置', action: buildSettingsIntent('set_input') },
      { label: '拨打电话', action: handleCall },
      { label: '扫一扫', action: handleScan },
      { label: '检测应用安装', action: checkAppInstallation },
      { label: '应用信息', action: showAppInfo },
      { label: '设备信息', action: showDeviceInfo },
      { label: '开启防截屏', action: (ctx) => togglePrivacyMode(true, ctx) },
      { label: '关闭防截屏', action: (ctx) => togglePrivacyMode(false, ctx) },
      { label: '监听网络', action: monitorNetworkStatus },
      { label: '获取本机IP', action: getLocalIp },
      { label: '拍照', action: capturePhoto },
      { label: '选择图片', action: pickGalleryImage },
      { label: '设为竖屏', action: () => setScreenOrientation(window.Orientation.PORTRAIT) },
      { label: '设为横屏', action: () => setScreenOrientation(window.Orientation.LANDSCAPE) },
      { label: '屏幕方向检测', action: getCurrentOrientation },
      { label: '设备类型', action: showDeviceType }
    ];
  }

  build() {
    Stack() {
      List() {
        ForEach(this.menuList, (item: FeatureMenu, index: number) => {
          ListItem() {
            Button(`${index + 1}. ${item.label}`)
              .width('100%')
              .onClick(() => {
                if (typeof item.action === 'function') {
                  (item.action as Function)(getContext(this) as common.UIAbilityContext);
                } else {
                  const want = item.action as Want;
                  const ctx = getContext(this) as common.UIAbilityContext;
                  ctx.startAbility(want).catch((err: BusinessError) => {
                    console.error(`Start Ability failed: ${err.code}`);
                  });
                }
              })
          }
        })
      }
      .width('100%')
      .height('100%')
      .alignListItem(ListItemAlign.Center)

      // 图片预览浮层
      if (this.previewUri) {
        Column() {
          Image(this.previewUri)
            .width('80%')
            .objectFit(ImageFit.Contain)
            .border({ width: 1, color: Color.White })
        }
        .width('100%')
        .height('100%')
        .backgroundColor('rgba(0,0,0,0.8)')
        .onClick(() => {
          this.previewUri = "";
        })
      }
    }
  }
}

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。