让你的APP吃上鸿蒙PC的红利

54 阅读17分钟

鸿蒙电脑 Harmony OS 6了,再不入局就晚了

尊贵的鸿蒙电脑用户,付费能力可以说是全球最强,遥遥...

Harmony OS 5的时候不入局是对的,装机少,系统bug多,适配的app也少。

自从升级Harmony OS 6,各方各面都完善起来,市场风向也从

下着玩玩-到了-用户刚需。

很多手机端优秀app的开发者发现,一旦兼容2IN1,(2IN1是华为对PC电脑设备的统称)

运营统计中曾经占比渺小的2IN1设备,

开始节节攀升。

摆在我们开发者面前有几个问题需要解决:

1.不重开发,如何兼容2IN1

2.有2IN1的用户通常有华为手机,甚至多数有全家桶,应该为APP加入哪些功能增强竞争力

3.买不起昂贵的PC,该怎么开发、测试

关注我,将在接下来的系列中,用实战中的经验,解决以上三个问题。


第一问:不重开发,如何兼容2IN1?

答案:一次开发,多端部署。

层级解决问题核心技术
界面级一多页面如何适配不同屏幕自适应布局 + 响应式布局 + 资源分设备配置
功能级一多功能如何兼容不同能力动态能力检测 + 权限/能力声明 + 条件执行
工程级一多代码如何组织与分包三层架构(Common/Feature/Product)+ HAR/HSP/HAP 分包

在我们开发手机端、平板端的APP时,可能没考虑到未来的2IN1端,会出现3个问题:

1.布局上用了固定布局;

2.数据库用了单机版;

3.工程上没搞清楚什么HSP HAR,反正deveco打开新建后就直接在page文件夹里开始搞了。

所以,着手解决:自适应布局、分布式数据库、三层架构改造,就能兼容2IN1。

最小改动法:自适应布局的改造

升级一个版本,将主要界面进行自适应布局改造。

要吃透以下两种布局中的一种,根据APP的特点,采用其中一种,或两种一起上。

1. 自适应布局(Adaptive Layout)

  • 元素随容器尺寸等比缩放、拉伸、隐藏或换行,但结构不变
  • 适用于小范围变化,如手机竖屏 ↔ 横屏。
  • 技术点:flexGrowlayoutWeightaspectRatiodisplayPriorityList 延伸等。

2. 响应式布局(Responsive Layout)

  • 当屏幕达到特定断点(breakpoint) 时,整体布局结构改变

  • 适用于大屏 vs 小屏,如手机单栏 → 平板双栏。

  • 技术点:

    • 媒体查询(Media Query) :监听窗口宽度、方向等。

    • 栅格系统(GridRow / GridCol) :按 xs/sm/md/lg 断点自动调整列数。

    • 官方断点标准:

      表格

      断点范围 (vp)典型设备
      xs[0, 320)手表
      sm[320, 600)手机
      md[600, 840)折叠屏、小平板
      lg[840, ∞)平板、PC、2in1

实战避坑干货

实际开发中发现,响应式布局,除了要考虑xs\sm\md\lg,还要考虑480vp(双折叠展开时)。

横竖屏监听,要注意,折叠屏展开时和不展开时,监听方向要区分,

折起来时为手机,不应该垂直翻转;

折成方块(双折屏展开、3折屏2折)应该4方向可翻转;

2IN1应监听设备为HUAWEI MATEBOOK FOLD | ULTIMATE DESIGN时默认可四方向翻转。因为这个没有实体键盘的折叠屏电脑是带有折叠态、展开态、悬停态三种状态的,不能将它视作MateBook Pro那样的实体键盘电脑。

重构布局法:Navigation级布局改造

根据你的APP,可选择使用导航容器+侧边栏容器组成左右结构,替代掉底部导航按钮组、顶部导航按钮组这种只适合窄屏的布局。

1.左右布局

先看不同类型设备的表现和用户体验:

设备类型侧边栏行为用户体验
手机(竖屏)默认隐藏,左滑或点击菜单按钮呼出(Overlay 模式)节省空间,聚焦内容
平板/2in1(横屏)常驻左侧,主内容区右侧(Split 模式)多任务并行,高效操作
PC常驻 + 支持鼠标悬停展开子菜单桌面级导航体验

Navigation(导航容器) + SideBarContainer(侧边栏容器) 是鸿蒙官方推荐的“一多”导航解决方案

  • 自动响应式:无需手动判断设备类型;
  • 工程简洁:一套代码覆盖多端;
  • 体验一致:符合各端用户习惯;
  • 扩展性强:可结合媒体查询做精细化控制。

2.左中右三栏布局

导航容器分栏模式 + 侧边栏容器,终极绝杀布局,再也不用管布局了。

层级组件作用
导航容器Navigation提供 Stack/Split 自动切换
侧边入口SideBarContainer提供跨页面的全局导航菜单
页面结构NavDestination / 普通页面支持分栏渲染或全屏跳转
设备/窗口宽度Navigation 模式侧边栏状态用户交互
手机(<600 vp)Stack默认隐藏(可呼出)点击列表 → 全屏跳转详情页
平板(≥600 vp)Split常驻显示点击列表 → 右侧直接加载详情,不跳转全屏
折叠屏(展开)Split常驻同平板体验
PC/2in1Split常驻 + 可调整宽度支持鼠标拖拽分栏边界

实战避坑干货

1.动态控制是否显示侧边栏。

// 根据设备类型或窗口大小决定
@State showSidebar: boolean = true;

// 在 windowSizeChange 中更新
if (windowWidth < 600) {
  this.showSidebar = false; // 小屏默认隐藏
} else {
  this.showSidebar = true;
}

2. 详情页空状态处理(Split 模式下)

在平板、2IN1首次打开时,右侧可能为空。可默认加载一个占位页。

  1. 一定一定,精细化控制NavPathStack

使用AppStorage、Cosume等方式,将NavPathStack交给所有子页面。

吃透pushPath、replacePath、moveToTop、popToIndex,掌握什么时候更换右侧子页面,什么时候更换左侧主页面。

吃透嵌套Navigation,分栏模式下,左侧子页面中使用嵌套的Navigaion单独控制NavPathStack。

补丁法:功能级一多能力兼容

不同设备硬件/系统能力不同(如手表无摄像头、智慧屏无 GPS、电脑有鼠标有触控板),不能硬编码调用。

记住这两个解决方案,丝滑打补丁:

  • 动态能力检测:使用 @ohos:ability 或 deviceCapability API 判断当前设备是否支持某功能。
import deviceManager from '@ohos.distributedHardware.deviceManager';
// 或使用 canIUse 等机制判断 API 是否可用
if (canIUse('sensor.onHeartRate')) {
  // 启用心率监测
}
  • 模块化功能开关:在 module.json5 中声明可选权限设备能力依赖,避免强制要求所有设备都具备某能力。

逆子分家:这个家没法儿呆了,分家分家!

----工程级一多(代码组织与分包)

鸿蒙推荐采用 三层架构 + 模块化分包 实现工程级一多:

1. Common 层(公共能力层)

  • 存放所有设备共用的代码:工具类、网络请求、基础组件、通用逻辑。
  • 编译为 HAR(Harmony Archive) 或 HSP(Shared Package) 包。

2. Feature 层(基础特性层)

  • 按功能模块拆分,如“音乐播放”、“地图导航”、“用户中心”。
  • 每个 Feature 可独立打包为 HAR/HSP,被多个产品复用
  • 支持按需加载(动态 import)。

3. Product 层(产品定制层)

  • 针对具体设备类型创建不同 Entry 模块:

    • product_phone
    • product_tablet
    • product_wearable
    • product_2in1
  • 每个 Product 模块:

    • 引用所需的 Common 和 Feature 模块;
    • 定制 UI 入口、交互逻辑、设备专属功能
    • 在 module.json5 中声明目标设备类型deviceTypes);
    • 最终打包为独立的 HAP(Harmony Ability Package)

工程级分包算不算重新开发呢?这取决于你原有代码的MVVM做得是否到位:

如果你是逍遥派,想到哪写到哪,那么工程级分包前,你得还风流债,整理出Common和Feature。

如果你一开始就做好了,这时候只需要新创建Product层Product_2in1即可,当然,这样做的代码量依然是所有解决方案中最多的,但是这样做,摒弃掉兼容布局,完全针对电脑的特点,用户得到的体验将是前所未有的。(个人开发者慎重考虑工作量。)

其余两个问题,我将会在如何让你的APP吃上鸿蒙PC端红利的二、三中继续回答。 ​

第二问:有2IN1的用户通常是全家桶用户,应该如何增强竞争力?

回答:跨设备协同体验

鸿蒙(HarmonyOS)的“一多开发”(一次开发,多端部署)不仅关注单设备内的多形态适配(如手机、平板、2in1),更核心的是支撑跨设备协同体验,包括:

  • 分布式能力
  • 应用接续(Continuity)
  • 碰一碰(NFC/蓝牙快速发现与连接)
  • 鼠标/键盘联动(外设协同)

一、分布式能力:鸿蒙一多的底层基石

划重点,下表一定要逐字看完,欲练神功,必先死记。

技术作用开发接口
分布式软总线(SoftBus)设备自动发现、安全连接、低延时通信@ohos.distributedHardware deviceManager
分布式数据管理(DistributedDataManager)多设备间数据同步(如剪贴板、配置)@ohos.data.relationalStore createDistributedTable()
分布式任务调度(DTSEngine)跨设备拉起服务、迁移任务@ohos.ability.featureAbility startAbility() + want 中指定设备

使用场景:

分布式让2IN1设备(2IN1是华为对各种PC设备的统称)能够和手机、平板、手表手环、智慧屏、车机、耳机等一切具有harmony OS 设备进行数据同步。

无论如何,都要让APP具备一些分布式能力,即便APP在之前没有考虑分布式,没关系,升级到分布式没有想象中那么困难。

分布式的三要素:权限、设备、分布式数据

要实现分布式就三步走:

1.获取权限,通过结构主动向用户索要“发现设备”的权限。

2.获取设备表。

3.对每一台设备进行分布式数据的拉取和推送。

权限代码片段:

  /**
   * 1.判断是否已授权,未授权则拉起授权
   * @param context
   * @returns
   */
  async checkPermissions(context:common.UIAbilityContext): Promise<void> {
try {
      let grantStatus1: boolean = await this.checkPermissionGrant('ohos.permission.DISTRIBUTED_DATASYNC') === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED;// 获取同步权限状态。
      if (!grantStatus1) {
        // 申请权限。
          this.reqPermissionsFromUser(['ohos.permission.DISTRIBUTED_DATASYNC'], context);
      } else {
        // 已经授权,可以继续访问目标操作。
        emitter.emit('distributedSuccessFulEvt');
      }
    } catch (e) {
      // 获取权限失败
      emitter.emit('distributedErrorEvt');
      throw e as SubError;
    }
}

  /**
   * 1.1权限判断
   * @param permission
   * @returns
   */
  private async checkPermissionGrant(permission: Permissions): Promise<abilityAccessCtrl.GrantStatus> {
    let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
    let grantStatus: abilityAccessCtrl.GrantStatus = abilityAccessCtrl.GrantStatus.PERMISSION_DENIED;

    // 获取应用程序的accessTokenID。
    let tokenId: number = 0;
    try {
      let bundleInfo: bundleManager.BundleInfo = await bundleManager.getBundleInfoForSelf(bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION);
      let appInfo: bundleManager.ApplicationInfo = bundleInfo.appInfo;
      tokenId = appInfo.accessTokenId;
    } catch (error) {
      const err: BusinessError = error as BusinessError;
      console.error(`Failed to get bundle info for self, code: ${err.code}, message: ${err.message}`);
    }

    // 校验应用是否被授予权限。
    try {
      grantStatus = await atManager.checkAccessToken(tokenId, permission);
    } catch (error) {
      const err: BusinessError = error as BusinessError;
      console.error(`Failed to check access token, code: ${err.code}, message: ${err.message}`);
    }

    return grantStatus;
  }

/**
   * 1.2拉起授权窗口
   * @param permissions
   * @param context
   */
  private reqPermissionsFromUser(permissions: Array<Permissions>, context: common.UIAbilityContext): void {
    let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
    // requestPermissionsFromUser会判断权限的授权状态来决定是否唤起弹窗。
    atManager.requestPermissionsFromUser(context, permissions).then((data) => {
      let grantStatus: Array<number> = data.authResults;
      let length: number = grantStatus.length;
      for (let i = 0; i < length; i++) {
        if (grantStatus[i] === 0) {
          // 用户授权,等待其他权限。
        } else {
          // 用户拒绝授权,提示用户必须授权才能访问当前页面的功能,并引导用户到系统设置中打开相应的权限。
          let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
          atManager.requestPermissionOnSetting(context, ['ohos.permission.DISTRIBUTED_DATASYNC']).then((data: Array<abilityAccessCtrl.GrantStatus>) => {
            if(data[0] === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED){
              console.info(`2次同步授权完成, result: ${data}`);
              AppStorage.setOrCreate(IS_DATA_SYNC,true);
              emitter.emit('distributedSuccessFulEvt');
            } else {
              emitter.emit('distributedErrorEvt');
              emitter.emit('distributedReject');  //拒绝了权限申请。
              console.info(`2次同步授权被拒绝, result: ${data}`);
            }
          }).catch((err: BusinessError) => {
            emitter.emit('distributedErrorEvt');
            throw new SubError({code:err.code,message:err.message});
            console.error(`requestPermissionOnSetting fail, code: ${err.code}, message: ${err.message}`);
          });
          return;
        }
      }
      // 授权成功。
      console.info(`同步授权完成, result: ${data}`);
      emitter.emit('distributedSuccessFulEvt');
    }).catch((err: BusinessError) => {
      emitter.emit('distributedErrorEvt');
      console.error(`Failed to request permissions from user. Code is ${err.code}, message is ${err.message}`);
    })
  }

获取了权限后,先把数据库设置为分布式。

const tables: string[] = [
      'table1',  //表1
      'table2', //表2
    ];
    try {
      //设置表们为分布式。
      await store.setDistributedTables(tables);
    } catch (error) {
      console.error(`Set distributed tables failed: ${error.code}, ${error.message}`);
    }

查找设备的代码片段:

  searchDevices(): distributedDeviceManager.DeviceBasicInfo[] {
    // 查询组网内的设备列表 这里要写包名
    const deviceManager = distributedDeviceManager.createDeviceManager('com.xx.xx');
    try {
      const deviceList = deviceManager.getAvailableDeviceListSync();
      if (!deviceList) {
        throw new SubError({ code: 998404, message: '没有设备' });
      }
      return deviceList;    //设备列表。
    } catch (error) {
      const e = error as SubError;
      console.error('d2d sync with all devices',e.code,e.message)
      throw error as SubError;    //这个SubError是我自定义的一个错误类,可以直接使用Error
    }
  }

推送本地数据到其他设备。这个操作一定是你使用insert、update、delete等数据库写操作后进行。

// 构造用于同步分布式表的谓词对象
    const predicates = new relationalStore.RdbPredicates(DB_CONSTANTS.TABLE1);
    predicates.inDevices(deviceList);
    try {
      const result = await store.sync(relationalStore.SyncMode.SYNC_MODE_PUSH, predicates);
      // 获取同步结果
      for (let i = 0; i < result.length; i++) {
        const deviceId = result[i][0];
        const syncResult = result[i][1];
        if (syncResult === 0) {
          console.info('rdbDataSync', `device:${deviceId} table:${table} sync success`);
        } else {
          // 有些设备没有你的APP,它会报错。
          console.error('rdbDataSync', `device:${deviceId} table:${table} sync failed, status:${syncResult}`);  
        }
      }
    } catch (e){
      console.error('pushToAllDevice同步失败');
    }

关于分布式数据库(键值对、关系型、用户首选项)的不同:键值对分布式可以各设备之间相互读写,关系型则只能读其他设备,不能写。这个特性一定搞明白。

二、应用接续(Continuity):任务无缝流转

场景示例

  • 手机上写邮件 → 点击“接续到2IN1” → 2IN1继续编辑
  • 车机导航 → 到家后自动接续到手表步行导航

注意的是,这里要用到一个叫“分布式数据对象”的东西,它和分布式数据库中读出来的内容不是同一个东西,它是个独立的东西。

这个叫分布式数据对象的,主要作用是,将现在内存中进行了一半的内容记下来,另一台设备从这个分布式数据对象中把内容读出,继续进行操作。

除了分布式数据对象,还有个重要的东西叫SessionId,本端将生成的sessionId通过want传递到远端,供远端激活同步使用。

三、碰一碰(Tap to Share / Tap to Connect)

技术原理

  • 基于 NFC + 蓝牙/WiFi P2P 快速建立连接;
  • 触发系统级 原子化服务(Atomic Service) 或 FA 拉起

优势:无需用户打开 App,符合鸿蒙“服务找人”理念。

实战理解

将分享、截图、应用接续打包成碰一碰,主打让用户在人前装*,人后实用。这个功能很能提升用户使用率,有条件一定上一个。

四、鼠标联动(外设协同)

适用场景

  • 电脑连接蓝牙鼠标 → 应用需显示 hover 效果、右键菜单;
  • PC 模式下支持快捷键(Ctrl+C/V);
  • 多屏联动(鼠标能在手机、平板、电脑三者的屏幕上无缝移动)。

实战理解

只需要把图片、文本、列表等设置成能拖入拖出的方式复制、拷贝,就能让你的APP具备鼠标联动功能。这是全家桶用户高频使用的功能,性价比极高的功能。

五、整体架构建议:如何在一多工程中集成这些能力?

1Project/
2├── common/                 # 公共逻辑(含分布式工具类)
3│   └── DistributedHelper.ts
4├── features/
5│   ├── continuity/         # 应用接续模块
6│   ├── nfc-share/          # 碰一碰分享
7│   └── input-enhance/      # 鼠标/键盘增强
8├── products/
9│   ├── phone/              # 手机端:简化接续入口
10│   ├── tablet/             # 平板端:常驻侧边栏 + 分栏 + 鼠标支持
11│   └── pc/                 # PC端:快捷键 + 右键菜单
12└── module.json5            # 声明分布式权限、NFC、外设能力

六、总结:鸿蒙一多 × 分布式 = 全场景体验

表格

特色能力技术方案一多开发要点
分布式软总线 + 分布式数据/任务一套逻辑,多设备协同执行
应用接续ContinuationRegisterManager状态序列化 + 目标设备UI适配
碰一碰NFC + 原子化服务/卡片无感触发,服务直达
鼠标联动PointerEvent + 快捷键大屏设备增强交互,小屏忽略

🔥 核心思想
“一多”不仅是适配不同屏幕,更是构建“以人为中心的超级终端”
你的应用不再属于某一台设备,而是运行在用户所有的鸿蒙设备组成的“分布式网络”中。

这正是鸿蒙生态对开发者的最大红利。 ​

第三问:买不起昂贵的PC,该怎么开发、测试。

回到:Deveco + 模拟器 + 云调试 + 云测试

开发方面

目前体验过的所有IDE,只有原生IDE Deveco studio体验最完美。

但是,原生的AI还不太能打,(这是基于202601的版本)基于IDE是实时保存的,所以,同一个文件,可以在Deveco中手动编辑,同时,将该项目在Trae、Claude等AI IDE打开用AI辅助。

某些AI对arkTS的认识不太全面,在规则上需要给AI反复强调ArkTS语法。不然容易给出一些ts代码。

调试、测试

在没有真机的情况下,有一些功能是测不出来的。

那么,模拟器、云调试,有哪些限制,这务必要清楚:

一、DevEco Studio 中 2in1 模拟器的限制

✅ 支持的能力
  • 可创建并运行面向 HarmonyOS API 17+ 的 2in1 设备项目。
  • 模拟器支持 窗口管理(主窗尺寸记忆、子窗阴影、跨屏拖拽等)。
  • 支持 ArkUI 针对 PC/2in1 的增强特性
  • 可通过 StartOptions 控制新窗口大小(min/max width/height)。
  • 支持 File Manager Service Kit 基础功能(如文件图标获取、回收站操作)。
  • WebView 调试支持自动端口转发(无需手动查进程 ID)。
❌ 主要限制
  1. ACL 权限受限

    • 虽然 DevEco 模拟器支持部分 ACL 权限(如 READ_IMAGEVIDEOSYSTEM_FLOAT_WINDOW),但 高危内核权限(如 kernel.ALLOW_WRITABLE_CODE_MEMORY)或涉及硬件抽象层(如 ACCESS_DDK_USB)的权限 无法生效,即使通过自动签名也无法真正启用。
    • 某些权限(如 MANAGE_APN_SETTING)仅在真机上可调用。
  2. AR Engine 功能不完整

    • 虽然 API 层面支持深度估计,但 模拟器无真实摄像头/传感器,无法生成有效深度图,仅能返回模拟数据或空值。
  3. 应用市场访问受限

    • 在 x86_64 架构的 2in1 模拟器中,华为应用市场默认不可见。(mac arm版、所有matebook纯血鸿蒙版都支持。但是,都买matebook了,就没必要用模拟器了。)

    • 虽可通过“使用其他应用打开”触发 Intent 回退机制间接启动应用市场(见 2025 年 12 月 CSDN 博客),但:

      • 无法下载/安装 HAP 应用(错误码如 9568420)。
      • 仅能查看搜索结果,不能完成完整分发流程。
  4. App Linking 不支持模拟器调试

    • 官方明确说明:App Linking(含直达应用市场、延迟链接)不支持在 DevEco 模拟器中运行调试,必须使用真机或云调试(但云调试也有局限,见下文)。
  5. 性能与渲染差异

    • 模拟器基于 x86_64 虚拟化,与 ARM 真机(如 MateBook Pro 实际可能为 ARM 架构)存在 指令集、GPU 渲染路径、内存管理策略差异,可能导致 UI 卡顿、动画异常或崩溃无法复现。

二、AGC 云调试(MateBook Pro 设备)的限制

✅ 支持
  • 真机环境下的 完整权限模型(包括部分 ACL 权限,前提是应用已通过审核)。
  • 可测试 窗口管理、多任务、外接显示器 等 2in1 特性。
  • 支持 App Linking 跳转
  • 2026 年初,AGC 云调试 正式提供 MateBook Pro 或同类 2in1 PC 设备
❌ 明确限制
**会话时长与资源限制**
-   即使未来上线,云调试通常限制单次会话 **≤ 2 小时**,且 **不支持长时间后台运行、系统级设置修改**(如网络代理、开发者选项深度配置)。
  1. 无法调试系统级服务

    • 云调试设备为多租户共享,禁止 root、禁止修改系统分区、禁止安装非 HAP 格式应用
  2. 权限申请仍需审核

    • 即使使用云真机,ACL 权限仍需提前在 AGC 提交审核,否则运行时会被拒绝。

三、必须使用真机才能完成的场景(总结)

场景是否必须真机说明
ACL 高危权限调用(如内核、USB DDK、APN 设置)模拟器和云调试均无法绕过权限审核与硬件依赖
AR/VR 深度感知、摄像头实时处理需真实传感器和 GPU 加速
App Linking 全链路测试(含跳转市场、延迟链接)模拟器不支持,云调试可能不提供 2in1 设备
HAP 应用在应用市场的安装/更新流程模拟器无法安装,云调试若无该设备也无法测试
系统级性能压测、功耗分析、热管理模拟器无真实电源/温控模型
外设连接测试(USB-C 扩展坞、触控笔、蓝牙键鼠高级功能)模拟器无物理接口
多屏协同、超级终端联动、碰一碰、隔空投送、鼠标联动需多台真机组成生态

建议

  • 开发阶段:使用 DevEco 2in1 模拟器进行 UI 布局、基础逻辑、窗口管理 快速迭代。

  • 集成测试阶段, 进行 权限、外设、市场分发 验证:

    • 申请 MateBook Pro 真机(如果有机会,通过华为开发者联盟设备借用计划)
    • 买真机
    • 提交公测版本,让人工帮你测,备注里写清楚测试流程,比如碰一碰,步骤1步骤2...写清楚。如果被打回,人工会给你说明怎么出问题的,基于此来改。(这个反馈可能会比较慢,有时候3、4天才打回也是常有的。)

能给付费意愿高的客群做产品,一定要抓住机会。宁卖一块玉,不卖百斤枣。