一次开发,多端部署

简介

一次开发多端部署,对于开发者来说,这意味着巨大的便利。以往开发不同平台的应用,要针对每个平台编写大量的特定代码,而 HarmonyOS 改变了这一局面。通过统一的开发语言和框架,如 ArkTS 等,开发者可以只写一次代码。无论是开发智能手表、平板电脑还是智能手机的应用,都能基于同一套代码逻辑。

这种特性可以节省大量的开发时间和成本。不用再为每个终端设备组建专门的开发团队,减少了代码维护的复杂性。而且,由于代码的复用性高,出现问题时也更容易排查和修复。

在实际应用场景中,这种特性大放异彩。以一款智能家居控制应用为例,开发者利用 HarmonyOS 的一次开发多端部署功能,开发出的应用在用户的手机上,可以呈现出丰富详细的控制界面,方便用户远程操控家中的各种智能设备,如调节灯光亮度、查看智能摄像头画面等。当这个应用部署在家庭中控大屏上时,由于屏幕尺寸变大,它会自动适配大屏的显示,展现出简洁明了且操作便捷的交互界面,方便家庭成员在室内近距离操作。而在智能手表这样的小屏幕设备上,应用则会突出关键功能,如快速开启或关闭常用的智能设备,实现了便捷的单手操作。

再比如一款运动健康类应用,在智能手机端,它可以详细记录用户的运动轨迹、心率变化、运动时长等大量数据,并通过精美的图表展示给用户。当部署到智能手表上时,它能实时显示用户的心率、运动步数等关键信息,还能接收来自手机端同步过来的运动计划和提醒,方便用户在运动过程中快速查看和调整。在平板电脑上,应用则可以更好地展示历史运动数据的分析报告,利用大屏幕优势为用户呈现更全面的健康洞察。这些案例都充分展现出 HarmonyOS 在多端协同开发方面的卓越优势。

既然一次开发,多端部署这一特性优势居多,接下来就具体来看看如何实现吧!

关键问题

为了实现“一多”的目标,需要解决如下三个基础问题:

问题1: 页面如何适配

不同设备间的屏幕尺寸、色彩风格等存在差异,页面如何适配。

问题2: 功能如何兼容

不同设备的系统能力有差异,如智能穿戴设备是否具备定位能力、智慧屏是否具备摄像头等,功能如何兼容。

问题3: 工程如何组织

如何实现一套代码同时能部署到多种不同设备上,代码工程如何组织。

关键问题的解决思路

针对“一多”提出的三个基础问题,可以从界面级、功能级、工程级三个维度给出相关问题的解决思路:

在这里插入图片描述

界面级一多

页面级一多需要考虑不同设备间的屏幕尺寸、色彩风格等存在差异,页面如何适配。可以从布局能力、资源使用、交互归一几个方面去考虑。

布局能力

布局决定了页面中的元素按照何种方式排布及显示,是页面设计及开发过程中首先需要考虑的问题。一般情况下,可以通过页面(或自定义组件)内的组件结构(组件个数、组件的父子/兄弟关系、组件类型、组件的相对位置)来判断使用何种布局能力。

  • 对于随尺寸变化组件结构相同的场景,可以在开发过程中灵活使用自适应布局能力来达到目标效果。
  • 对于随尺寸变化组件结构不同的场景,更适合使用响应式布局能力来实现不同尺寸下的不同显示的效果。

布局可以分为自适应布局和响应式布局,二者的介绍如下表所示:

名称 简介
自适应布局 当外部容器大小发生变化时,元素可以根据相对关系自动变化以适应外部容器变化的布局能力。相对关系如占比、固定宽高比、显示优先级等。当前自适应布局能力有7种:拉伸能力、均分能力、占比能力、缩放能力、延伸能力、隐藏能力、折行能力。自适应布局能力可以实现界面显示随外部容器大小连续变化。
响应式布局 当外部容器大小发生变化时,元素可以根据断点、栅格或特定的特征(如屏幕方向、窗口宽高等)自动变化以适应外部容器变化的布局能力。当前响应式布局能力有3种:断点、媒体查询、栅格布局。响应式布局可以实现界面随外部容器大小有级不连续变化,通常不同特征下的界面显示会有较大的差异。
自适应布局

针对常见的开发场景,方舟开发框架提炼了七种自适应布局能力,这些布局可以独立使用,也可多种布局叠加使用。

自适应布局能力 使用场景 实现方式
拉伸能力 容器组件尺寸发生变化时,增加或减小的空间全部分配给容器组件内指定区域。 Flex布局的flexGrow和flexShrink属性
均分能力 容器组件尺寸发生变化时,增加或减小的空间均匀分配给容器组件内所有空白区域。 Row组件、Column组件或Flex组件的justifyContent属性设置为FlexAlign.SpaceEvenly
占比能力 子组件的宽或高按照预设的比例,随容器组件发生变化。 基于通用属性的两种实现方式:- 将子组件的宽高设置为父组件宽高的百分比- layoutWeight属性
缩放能力 子组件的宽高按照预设的比例,随容器组件发生变化,且变化过程中子组件的宽高比不变。 布局约束的aspectRatio属性
延伸能力 容器组件内的子组件,按照其在列表中的先后顺序,随容器组件尺寸变化显示或隐藏。 基于容器组件的两种实现方式:- 通过List组件实现- 通过Scroll组件配合Row组件或Column组件实现
隐藏能力 容器组件内的子组件,按照其预设的显示优先级,按照布局优先级大小,从小到大依次隐藏,直到容器能够完整显示剩余元素。相同显示优先级的子组件同时显示或隐藏。 布局约束的displayPriority属性
折行能力 容器组件尺寸发生变化时,如果布局方向尺寸不足以显示完整内容,自动换行。 Flex组件的wrap属性设置为FlexWrap.Wrap
响应式布局

响应式布局是指页面内的元素可以根据特定的特征(如窗口宽度、屏幕方向等)自动变化以适应外部容器变化的布局能力。响应式布局中最常使用的特征是窗口宽度,可以将窗口宽度划分为不同的范围(下文中称为断点)。当窗口宽度从一个断点变化到另一个断点时,改变页面布局(如将页面内容从单列排布调整为双列排布甚至三列排布等)以获得更好的显示效果。

响应式布局能力 简介
断点 将窗口宽度划分为不同的范围(即断点),监听窗口尺寸变化,当断点改变时同步调整页面布局。
媒体查询 媒体查询支持监听窗口宽度、横竖屏、深浅色、设备类型等多种媒体特征,当媒体特征发生改变时同步调整页面布局。
栅格布局 栅格组件将其所在的区域划分为有规律的多列,通过调整不同断点下的栅格组件的参数以及其子组件占据的列数等,实现不同的布局效果。

断点

断点以应用窗口宽度为切入点,将应用窗口在宽度维度上分成了几个不同的区间即不同的断点,在不同的区间下,开发者可根据需要实现不同的页面布局效果。具体的断点如下所示。

断点名称 取值范围(vp)
xs [0, 320)
sm [320, 600)
md [600, 840)
lg [840, +∞)

说明

  • 以设备屏幕宽度作为参照物,也可以实现类似的效果。考虑到应用可能以非全屏窗口的形式显示,以应用窗口宽度为参照物更为通用。
  • 开发者可以根据实际使用场景决定适配哪些断点。如xs断点对应的一般是智能穿戴类设备,如果确定某页面不会在智能穿戴设备上显示,则可以不适配xs断点。
  • 可以根据实际需要在lg断点后面新增xl、xxl等断点,但注意新增断点会同时增加UX设计师及应用开发者的工作量,除非必要否则不建议盲目新增断点。

系统提供了多种方法,判断应用当前处于何种断点,进而可以调整应用的布局。常见的监听断点变化的方法如下所示:

  • 获取窗口对象并监听窗口尺寸变化
  • 通过媒体查询监听应用窗口尺寸变化
  • 借助栅格组件能力监听不同断点的变化
栅格布局
  • 断点

    一)开发者可以修改断点的取值范围,支持启用最多6个断点。

    • 基于本文断点小节介绍的推荐值,栅格组件默认提供xs、sm、md、lg四个断点。
    • 栅格组件支持开发者修改断点的取值范围,除了默认的四个断点,还支持开发者启用xl和xxl两个额外的断点。

    说明

    断点并非越多越好,通常每个断点都需要开发者“精心适配”以达到最佳显示效果。

    二)栅格断点默认以窗口宽度为参照物,同时还允许开发者配置为以栅格组件本身的宽度为参照物。

    栅格既可以用于页面整体布局的场景,也可以用于页面局部布局的场景。考虑到在实际场景中,存在应用窗口尺寸不变但是局部区域尺寸发生了变化的情况,栅格组件支持以自身宽度为参照物响应断点变化具有更大的灵活性。

    三)栅格组件的断点发生变化时,会通过onBreakPointChange事件通知开发者。

  • 栅格组件的columns、gutter和margin

    栅格组件columns默认为12列,gutter默认为0,同时支持开发者根据实际需要定义不同断点下的columns数量以及gutter长度。特别的,在栅格组件实际使用过程中,常常会发生多个元素占据的列数相加超过总列数而折行的场景。栅格组件还允许开发者分别定义水平方向的gutter(相邻两列之间的间距)和垂直方向的gutter(折行时相邻两行之间的间距)。

    考虑到组件通用属性中已经有margin和padding,栅格组件不再单独提供额外的margin属性,直接使用通用属性即可。借助margin或者padding属性,均可以控制栅格组件与父容器左右边缘的距离,但是二者也存在一些差异:

    • margin区域在栅格组件的边界外,padding区域在栅格组件的边界内。
    • 栅格组件的backgroundColor会影响padding区域,但不会影响margin区域。

    总的来讲,margin在组件外而padding在组件内,开发者可以根据实际需要进行选择及实现目标效果。

  • 栅格组件的span、offset和order

    栅格组件(GridRow)的直接孩子节点只可以是栅格子组件(GridCol),GridCol组件支持配置span、offset和order三个参数。这三个参数的取值按照"xs -> sm -> md -> lg -> xl -> xxl"的向后方向具有继承性(不支持向前方向的继承性),例如将sm断点下span的值配置为3,不配置md断点下span的值,则md断点下span的取值也是3。

    参数名 类型 必填 默认值 说明
    span {xs?: number, sm?: number, md?: number, lg?: number, xl?: number, xxl?:number} - 在栅格中占据的列数。span为0,意味着该元素既不参与布局计算,也不会被渲染。
    offset {xs?: number, sm?: number, md?: number, lg?: number, xl?: number, xxl?:number} 0 相对于前一个栅格子组件偏移的列数。
    order {xs?: number, sm?: number, md?: number, lg?: number, xl?: number, xxl?:number} 0 元素的序号,根据栅格子组件的序号,从小到大对栅格子组件做排序。
资源使用

在页面开发过程中,经常需要用到颜色、字体、间距、图片等资源,在不同的设备或配置中,这些资源的值可能不同。有两种方式处理:

  • 应用资源:借助资源文件能力,开发者在应用中自定义资源,自行管理这些资源在不同的设备或配置中的表现。

    • 应用开发中使用的各类自定义资源文件,需要统一存放于应用的resources目录下,便于使用和维护。resources目录包括两大类目录,一类为base目录与限定词目录,另一类为rawfile目录,

    • base目录与限定词目录下面可以创建资源组目录(包括element、media等),用于存放特定类型的资源文件。

      资源组目录 目录说明 资源文件
      element 表示元素资源,以下每一类数据都采用相应的JSON文件来表征。- boolean,布尔型- color,颜色- float,浮点型- intarray,整型数组- integer,整型- pattern,样式- plural,复数形式- strarray,字符串数组- string,字符串 element目录中的文件名称建议与下面的文件名保持一致。每个文件中只能包含同一类型的数据。- boolean.json- color.json- float.json- intarray.json- integer.json- pattern.json- plural.json- strarray.json- string.json
      media 表示媒体资源,包括图片、音频、视频等非文本格式的文件。 文件名可自定义,例如:icon.png。
  • 系统资源:开发者直接使用系统预置的资源定义(即分层参数)。

    • 在工程中,通过 $r('app.type.name')的形式引用应用资源。app代表是应用内resources目录中定义的资源;type 代表资源类型(或资源的存放位置),可以取 color、float、string、plural和media,name代表资源命名,由开发者添加资源时确定。
交互归一

对于不同类型的智能设备,用户可能有不同的交互方式,如通过触摸屏、鼠标、触控板等。如果针对不同的交互方式单独做适配,会增加开发工作量同时产生大量重复代码。为解决这一问题,我们统一了各种交互方式的API,即实现了交互归一。

常见的基础输入方式及其在各输入设备上的表现如下图所示。

在这里插入图片描述

功能级一多

应用开发至少包含两部分工作: UI页面开发和底层功能开发(部分需要联网的应用还会涉及服务端开发)。前面介绍了如何解决页面适配的问题,本部分主要介绍应用如何解决设备系统能力差异的兼容问题。

系统能力

系统能力(即SystemCapability,缩写为SysCap)指操作系统中每一个相对独立的特性,如蓝牙,WIFI,NFC,摄像头等,都是系统能力之一。每个系统能力对应多个API,随着目标设备是否支持该系统能力共同存在或消失。

与系统能力相关的,有支持 能力集联想能力集要求能力集三个核心概念。

  • 支持能力集:设备具备的系统能力集合,在设备配置文件中配置。
  • 要求能力集:应用需要的系统能力集合,在应用配置文件中配置。
  • 联想能力集:开发应用时IDE可联想的API所在的系统能力集合,在应用配置文件中配置。

说明

  • 只有当应用要求能力集是设备支持能力集的子集的时候,应用才可以在该设备上分发、安装和运行。
  • 可以访问系统能力列表了解全量的系统能力。
动态逻辑判断

如果某个系统能力没有写入应用的要求能力集中,那么在使用前需要判断设备是否支持该系统能力。

  • 方法1:HarmonyOS定义了API canIUse帮助开发者来判断该设备是否支持某个特定的syscap。
if (canIUse("SystemCapability.Communication.NFC.Core")) { 
    console.log("该设备支持SystemCapability.Communication.NFC.Core");
} else {    
    console.log("该设备不支SystemCapability.Communication.NFC.Core");
}
  • 方法2:开发者可通过import的方式将模块导入,若当前设备不支持该模块,import的结果为undefined,开发者在使用其API时,需要判断其是否存在。
import controller from '@kit.ConnectivityKit';
   try {
    controller.enableNfc();
    console.log("controller enableNfc success");
} catch (busiError) {
    console.log("controller enableNfc busiError: " + busiError);
}

说明

  • 如果某系统能力是应用运行必须的,则要将其写入到应用的要求能力集中,以确保应用不会分发和安装到不符合要求的设备上。
  • 如果某系统能力不是应用运行必须的,则可以在运行时做动态判断,这样可以最大程度扩大应用的适用范围。
配置联想能力集和要求能力集
  • IDE会根据创建的工程所支持的设备自动配置联想能力集和要求能力集,同时也支持开发者修改。
// syscap.json
{
    "devices": {
        "general": [            // 每一个典型设备对应一个syscap支持能力集,可配置多个典型设备
            "default",
            "tablet"
        ],
        "custom": [             // 厂家自定义设备
            {
                "某自定义设备": [
                    "SystemCapability.Communication.SoftBus.Core"
                ]
            }
        ]
    },
    "development": {             // addedSysCaps内的sycap集合与devices中配置的各设备支持的syscap集合的并集共同构成联想能力集
        "addedSysCaps": [
            "SystemCapability.Communication.NFC.Core"
        ]
    },
    "production": {              // 用于生成rpcid,慎重添加,可能导致应用无法分发到目标设备上
        "addedSysCaps": [],      // devices中配置的各设备支持的syscap集合的交集,添加addedSysCaps集合再除去removedSysCaps集合,共同构成要求能力集
        "removedSysCaps": []     // 当该要求能力集为某设备的子集时,应用才可被分发到该设备上
    }
}

说明

  • 对于要求能力集,开发者修改时要十分慎重,修改不当会导致应用无法分发和安装到目标设备上。
  • 对于联想能力集,通过增加系统能力可以扩大IDE可联想的API范围。但要注意这些API可能在某些设备上不支持,使用前需要判断。

工程级一多

工程级一多需要考虑如何实现一套代码同时能部署到多种不同设备上,代码工程如何组织。

应用程序包结构

在进行应用开发时,一个应用通常包含一个或多个Module。Module是HarmonyOS应用/服务的基本功能单元,包含了源代码、资源文件、第三方库及应用/服务配置文件,每一个Module都可以独立进行编译和运行。

Module分为AbilityLibrary两种类型:

  • Ability类型的Module编译后生成HAP包。
  • Library类型的Module编译后生成HAR包。

HarmonyOS的应用以APP Pack形式发布,其包含一个或多个HAP包HAP是HarmonyOS应用安装的基本单位,HAP可以分为EntryFeature两种类型:

  • Entry类型的HAP:应用的主模块。在同一个应用中,同一设备类型只支持一个Entry类型的HAP,通常用于实现应用的入口界面、入口图标、主特性功能等。
  • Feature类型的HAP:应用的动态特性模块。Feature类型的HAP通常用于实现应用的特性功能,一个应用程序包可以包含一个或多个Feature类型的HAP,也可以不包含

部署模型

“一多”有两种部署模型:

  • 部署模型A:不同类型的设备上按照一定的工程结构组织方式,通过一次编译生成相同的HAP(或HAP组合)。
  • 部署模型B:不同类型的设备上按照一定的工程结构组织方式,通过一次编译生成不同的HAP(或HAP组合)。

开发者可以从应用UX设计及应用功能两个维度,结合具体的业务场景,考虑选择哪种部署模型。当然,也可以借助设备类型分类,快速做出判断。

从屏幕尺寸、输入方式及交互距离三个维度考虑,可以将常用类型的设备分为不同泛类:

  • 默认设备、平板
  • 车机、智慧屏
  • 智能穿戴
  • ……

对于相同泛类的设备,优先选择部署模型A,对于不同泛类设备,优先选择部署模型B

说明

  • 应用在不同泛类设备上的UX设计或功能相似时,可以使用部署模型A
  • 应用在同一泛类不同类型设备UX设计或功能差异非常大时,可以使用部署模型B,但同时也应审视应用的UX设计及功能规划是否合理。
  • 实际上在开发多设备应用时,如果目标设备类型较多,往往是部署模型A和部署模型B混合使用。
  • 不管采用哪种部署模型,都应该采用一次编译
工程结构

“一多”推荐在应用开发过程中使用如下的“三层工程结构”。

  • common(公共能力层):用于存放公共基础能力集合(如工具库、公共配置等)。

    common层不可分割,需编译成一个HAR包,其只可以被products和features依赖,不可以反向依赖。

  • features(基础特性层):用于存放基础特性集合(如应用中相对独立的各个功能的UI及业务逻辑实现等)。

    各个feature高内聚、低耦合、可定制,供产品灵活部署。不需要单独部署的feature通常编译为HAR包,供products或其它feature使用。需要单独部署的feature通常编译为Feature类型的HAP包,和products下Entry类型的HAP包进行组合部署。features层可以横向调用及依赖common层,同时可以被products层不同设备形态的HAP所依赖,但是不能反向依赖products层。

  • products(产品定制层):用于针对不同设备形态进行功能和特性集成。

    products层各个子目录各自编译为一个Entry类型的HAP包,作为应用主入口。products层不可以横向调用。

代码工程结构抽象后一般如下所示:

/application
├── common                  # 可选。公共能力层, 编译为HAR包或HSP包
├── features                # 可选。基础特性层
│   ├── feature1            # 子功能1, 编译为HAR包或HSP包或Feature类型的HAP包
│   ├── feature2            # 子功能2, 编译为HAR包或HSP包或Feature类型的HAP包
│   └── ...
└── products                # 必选。产品定制层
    ├── wearable            # 智能穿戴泛类目录, 编译为Entry类型的HAP包
    ├── default             # 默认设备泛类目录, 编译为Entry类型的HAP包
    └── ...

说明

  • 部署模型不同,相应的代码工程结构也有差异。部署模型A和部署模型B的主要差异点集中在products层:部署模型A在products目录下同一子目录中做功能和特性集成;部署模型B在products目录下不同子目录中对不同的产品做差异化的功能和特性集成。
  • 开发阶段应考虑不同类型设备间最大程度的复用代码,以减少开发及后续维护的工作量。
  • 整个代码工程最终构建出一个APP包,应用以APP包的形式发布到应用市场中。
Logo

社区规范:仅讨论OpenHarmony相关问题。

更多推荐