一次开发多端部署

关键问题

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

  • 页面如何适配

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

  • 功能如何兼容

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

  • 工程如何组织

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

关键问题解决思路

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

一多解决思路.png

界面级一多

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

布局能力

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

名称 简介
自适应布局 外部容器大小发生变化时,元素根据相对关系自动适应外部容器变化布局能力。自适应布局能力有7种:拉伸、均分、占比、缩放、延伸、隐藏、折行
响应式布局 外部容器大小发生变化时,元素可以根据断点、栅格、媒体查询来适应外部容器变化布局能力。响应式布局能力有3种:断点、媒体查询、栅格布局

自适应布局

自适应布局能力 使用场景 实现方式
拉伸能力 增加或减小的空间全部分配给容器组件内指定区域 flexGrow和flexShrink属性
均分能力 增加或减小的空间均匀分配给容器组件内所有空白区域 justifyContent设置FlexAlign.SpaceEvenly
占比能力 按照预设的比例进行占比 layoutWeight属性
缩放能力 相对于宽高比例进行等比缩放 aspectRatio属性
延伸能力 随容器组件尺寸变化显示或隐藏。 List组件、Scroll组件
隐藏能力 根据预设优先级进行显示隐藏 displayPriority属性
折行能力 尺寸不足以显示完整内容,自动换行 Flex组件的wrap属性设置FlexWrap.Wrap
  • 拉伸能力

拉伸.gif

代码:

Row(){
// 通过flexGrow和flexShrink属性,将多余的空间全部分配给图片,将不足的空间全部分配给两侧空白区域。
Row().with(150)
.flexGrow(0).flexShrink(1)
Image($r"app.media.tuupian.png").width(400)
.flexGrow(1).flexShrink(0)
Row().width(150)
.flexGrow(0).flexShrink(1)
}
  • 均分能力

    均分能力.gif

代码

Column(){
Row(){
ForEach(this.list,(item:number)=>{...})
}.width("100%")
// 均分配父容器主轴方向的剩余空间
.justifyContent(FlexAlign.SpaceEvenly)
// 同上Row
Row(){...}
}
.width(this.rate*100+"%")
  • 占比能力

    占比能力.gif

代码

RoW(){
Column(){...}
.layoutWeight(1)//设置子组件在父容器主轴方向的布局权重
Column(){...}
.layoutWeight(1)//设置子组件在父容器主轴方向的布局权重
Column(){...}
.layoutWeight(1)//设置子组件在父容器主轴方向的布局权重
.width(this.rate 100+"%")
  • 缩放能力

    缩放能力.gif

代码

Column(){
Column(){
Image($r("app.media.illustrator"))
.width('100%').height('100%')
.aspectRatio(1)//固定宽高比
.height(this.sliderHeight)
.width(this.sliderwidth)
  • 延伸能力

    延伸能力.gif

代码

Row({space:10 })
//通过ist组件实现隐藏能力
List({space:10 }{...}
.listDirection(Axis.Horizontal)
.width(100%)
}
.width(this.rate 100 +"%")
  • 隐藏能力

    隐藏能力.gif

代码

RoW(){
Image($r("app.media.favorite"))
.displayPriority(1)//布局优先级
Image($r("app.media.down"))
.displayPriority(2)//布局优先级
Image($r("app.media.pause"))
.displayPriority(3)//布局优先级
Image(r("app.media.next"))
.displayPriority(2)//布局优先级
Image($r("app.media.list"))
.displayPriority(1)/布局优先级
.width(this.rate 100+"%")
  • 折行能力

    折行能力.gif

代码

Column()
//通过迹Lex组件warp参数实现自适应折行
Flex(
wrap:FlexWrap.Wrap,
direction:FlexDirection.Row
}){
ForEach(this.imageList,(item:Resource)=>{
Image(item).width(183).height(138)
})
}
.width(this.rate 100+"%")
}

响应式布局

响应式布局是指页面内的元素可以根据特定的特征(窗口宽度、屏幕方向等)自动变化以适应外部容器变化的布局能力。

响应式能力 简介
断点 屏幕宽度划分范围(断点),监视窗口尺寸变化,在不同范围中做出不同改变页面布局。
媒体查询 监视窗口宽度、横竖屏、深浅色、设备类型等媒体特征,做出页面布局调整。
栅格布局 将区域划分多列,根据断点来进行不同情况下页面的占比情况。

资源使用

在页面开发中,对于使用到不同情况下的资源,有两种解决方式:

  • 应用资源:开发者在应用中自定义资源,来自行管理这些资源。
$r("app.string.appName") // 使用$r获取app下静态编译时资源
$rawfile("img/huge.png") // 使用$rawfile获取rawfile文件下的运行时资源
  • 系统资源:开发者直接使用系统预置资源定义。
$r("sys.") // 使用sys来调用系统资源

交互归一

不同的设备的交互方式会有些不同,例如:触摸屏、鼠标、触控板等,为了解决开发工作量大的问题,进行了交互归一操作。

交互情况.png

功能级一多

应用开发分为两个部分:UI页面开发功能开发。下面将介绍设备之间系统能力差异的兼容方式。

系统能力

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

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

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

能力集.png

配置联想和要求能力集

在工程模块src/main目录下,手动创建syscap.json文件。如在entry/src/main目录邮件,点击New > File。加入下面以下配置。

// 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": [] // 当该要求能力集为某设备的子集时,应用才可被分发到该设备上
}
}

canIUse接口

在编码阶段,开发者可以通过canIUse接口来进行判断目标设备是否支持某系统能力,进而执行不同的业务逻辑。通常当设备不支持某种能力时,云到这部分代码后,给出友好弹窗,避免应用崩溃。

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

canIUse接口使用.png

工程级一多

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

应用程序包结构

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

Module分为“Ability”和“Library”两种类型:

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

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

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

工程结构

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

  • 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包
└── ...