前言
时光荏苒,Android开发旅途即将告一段落,在找工作的过程中最终决定进入鸿蒙应用赛道,开个文档记录一下自己的学习之旅。
快速入门
根据官网的入门开发手册学习一下鸿蒙应用开发。
UI框架
| 开发范式名称 | 语言生态 | UI更新方式 | 适用场景 | 适用人群 |
|---|---|---|---|---|
| 声明式开发范式 | ArkTS语言 | 数据驱动更新 | 复杂度较大、团队合作度较高的程序 | 移动系统应用开发人员、系统应用开发人员 |
| 类Web开发范式 | JS语言 | 数据驱动更新 | 界面较为简单的程序应用和卡片 | Web前端开发人员 |
HarmonyOS提供了一套UI开发框架,即方舟开发框架(ArkUI框架),而这个框架提供了两种开发范式,根据上面的表格,作为一名Android开发,毫不犹豫选择声明式开发范式。
DevEco Studio
在鸿蒙开发之前,首先需要准备IDE,官方介绍DevEco Studio是面向HarmonyOS应用及元服务开发者提供的集成开发环境(IDE)。developer.huawei.com/consumer/cn…
配置环境
下载安装完以后发现这个IDE真是熟悉呀,依次下载Node.js、ohpm、Harmony OS SDK等依赖。
-
Node.js
Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境。
-
ohpm
open harmony package manager 包管理工具
-
Harmony OS SDK
类比 Android SDK。
-
TypeScript
TypeScript 是 JavaScript 的一个超集,它在 JavaScript 的基础上添加了静态类型检查等功能,使代码更加健壮和可维护。也是鸿蒙应用的主要开发语言。
创建应用
像AS新建app一样,直接创建一个项目,这里有一个Model,官方的解释是
- FA(Feature Ability)模型: HarmonyOS API 7开始支持的模型,已经不再主推。FA模型开发可见FA模型开发概述。
- Stage模型: HarmonyOS API 9开始新增的模型,是目前主推且会长期演进的模型。在该模型中,由于提供了AbilityStage、WindowStage等类作为应用组件和Window窗口的“舞台”,因此称这种应用模型为Stage模型。Stage模型开发可见Stage模型开发概述。
默认选了Stage模型,Stage模型也比较新,所以直接用Stage就好了
Stage模型
-
相当于Application,每个Entry类型或者Feature类型的HAP在运行期都有一个AbilityStage类实例,当HAP中的代码首次被加载到进程中的时候,系统会先创建AbilityStage实例。
-
UIAbility组件和ExtensionAbility组件
这里官方的概念比较抽象,可以先狭义地把Ability当作是Activity一般。
-
每个UIAbility类实例都会与一个WindowStage类实例绑定,WindowStage类起到了应用进程内窗口管理器的作用,它包含一个主窗口。也就是说,UIAbility通过WindowStage持有了一个窗口,该窗口为ArkUI提供了绘制区域。
-
经典的上下文概念,和Android中差异不大。
-
ArkUI Page
真正的UI载体,有过compose开发经验的同学可以理解为Composable组件,或者Flutter中的Widget概念。但Page必须由UIAbility的Window承载。
UI
Hello World
@Entry
@Component
struct Index {
@State message: string = 'Hello World'
build() {
Row() {
Column() {
Text(this.message)
.fontSize(50)
.fontWeight(FontWeight.Bold)
}
.width('100%')
}
.height('100%')
}
}
我们来分析一下新建好的项目的代码。
首先Index是我们页面UI的部分,这是一个Page,而这个Page上面有两个注解,一个Entry,一个Component
-
ComponentDefining Component ClassDecorator
就当作是一个声明这是组件的注解,和AS的@Composable差不多。
-
EntryEntry is a ClassDecorator and it supports LocalStorage as parameters.
官方注释说的很清楚被@Entry装饰的@Component,可以被分配一个LocalStorage实例,此组件的所有子组件实例将自动获得对该LocalStorage实例的访问权限
这里其实还有一个State注解,这个一看就是有状态无状态那些概念,先不关注。
和Compose方法不同,Index是一个结构体,然后build方法内进行声明式UI编写,基本上代码很容易看懂。 但是这个Index是在哪里被调用的呢?
export default class EntryAbility extends UIAbility {
onCreate(want, launchParam) {
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate');
}
onDestroy() {
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onDestroy');
}
onWindowStageCreate(windowStage: window.WindowStage) {
// Main window is created, set main page for this ability
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate');
windowStage.loadContent('pages/Index', (err, data) => {
if (err.code) {
hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');
return;
}
hilog.info(0x0000, 'testTag', 'Succeeded in loading the content. Data: %{public}s', JSON.stringify(data) ?? '');
});
}
onWindowStageDestroy() {
// Main window is destroyed, release UI related resources
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageDestroy');
}
onForeground() {
// Ability has brought to foreground
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onForeground');
}
onBackground() {
// Ability has back to background
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onBackground');
}
}
没错,搜了一下就是在EntryAbility中,它继承了UIAbility,还记得上面的概念,UIAbility是Page的载体,而UIAbility中也存在着页面的生命周期方法。
在onWindowStageCreate中使用了windowStage的loadContent方法把Index结构体加载了进来。
而EntryAbility又是怎么作为应用入口的呢,这个时候就要看一下项目结构了。
-
AppScope > app.json5:应用的全局配置信息。
-
entry:HarmonyOS工程模块,编译构建生成一个HAP包。
- src > main > ets:用于存放ArkTS源码。
- src > main > ets > entryability:应用/服务的入口。
- src > main > ets > pages:应用/服务包含的页面。
- src > main > resources:用于存放应用/服务所用到的资源文件,如图形、多媒体、字符串、布局文件等。关于资源文件,详见资源分类与访问。
- src > main > module.json5:Stage模型模块配置文件。主要包含HAP包的配置信息、应用/服务在具体设备上的配置信息以及应用/服务的全局配置信息。具体的配置文件说明,详见module.json5配置文件。
- build-profile.json5:当前的模块信息、编译信息配置项,包括buildOption、targets配置等。其中targets中可配置当前运行环境,默认为HarmonyOS。
- hvigorfile.ts:模块级编译构建任务脚本,开发者可以自定义相关任务和代码实现。
-
oh_modules:用于存放三方库依赖信息。关于原npm工程适配ohpm操作,请参考历史工程迁移。
-
build-profile.json5:应用级配置信息,包括签名、产品配置等。
-
hvigorfile.ts:应用级编译构建任务脚本。
根据定义可以知道其实module.json5便有点类似于AndroidManifest的概念
{
"module": {
"name": "entry",
"type": "entry",
"description": "$string:module_desc",
"mainElement": "EntryAbility",
"deviceTypes": [
"phone",
"tablet"
],
"deliveryWithInstall": true,
"installationFree": false,
"pages": "$profile:main_pages",
"abilities": [
{
"name": "EntryAbility",
"srcEntry": "./ets/entryability/EntryAbility.ts",
"description": "$string:EntryAbility_desc",
"icon": "$media:icon",
"label": "$string:EntryAbility_label",
"startWindowIcon": "$media:icon",
"startWindowBackground": "$color:start_window_background",
"exported": true,
"skills": [
{
"entities": [
"entity.system.home"
],
"actions": [
"action.system.home"
]
}
]
}
]
}
}
当前这个module的mainElement便是入口。
UI结构
看完了Hello world,来看更多的UI结构概念。
布局
声明式UI提供了以下9种常见布局,开发者可根据实际应用场景选择合适的布局进行页面开发。
| 布局 | 说明 |
|---|---|
| 线性布局(Row、Column) | 最常规的布局 |
| 层叠布局(Stack) | compose中的box,android中的framelayout |
| 弹性布局(Flex) | 弹性布局是与线性布局类似的布局方式。区别在于弹性布局默认能够使子组件压缩或拉伸。在子组件需要计算拉伸或压缩比例时优先使用此布局,可使得多个容器内子组件能有更好的视觉上的填充容器效果。 |
| 相对布局(RelativeContainer) | 相对布局是在二维空间中的布局方式,不需要遵循线性布局的规则,布局方式更为自由。通过在子组件上设置锚点规则(AlignRules)使子组件能够将自己在横轴、纵轴中的位置与容器或容器内其他子组件的位置对齐。设置的锚点规则可以天然支持子元素压缩、拉伸,堆叠或形成多行效果。在页面元素分布复杂或通过线性布局会使容器嵌套层数过深时推荐使用。 |
| 栅格布局(GridRow、GridCol) | 栅格是多设备场景下通用的辅助定位工具,通过将空间分割为有规律的栅格。栅格不同于网格布局固定的空间划分,它可以实现不同设备下不同的布局,空间划分更随心所欲,从而显著降低适配不同屏幕尺寸的设计及开发成本,使得整体设计和开发流程更有秩序和节奏感,同时也保证多设备上应用显示的协调性和一致性,提升用户体验。推荐手机、大屏、平板等不同设备,内容相同但布局不同时使用。 |
| 媒体查询(@ohos.mediaquery) | 媒体查询可根据不同设备类型或同设备不同状态修改应用的样式。例如根据设备和应用的不同属性信息设计不同的布局,以及屏幕发生动态改变时更新应用的页面布局。 |
| 列表(List) | 类似于 recyclerview |
| 网格(Grid) | gridview |
| 轮播(Swiper) | 轮播组件通常用于实现广告轮播、图片预览、可滚动应用等。 |
组件
路由
页面路由
页面路由的方式鸿蒙已经提供,先导入依赖
import router from '@ohos.router';
然后直接使用即可
Router模块提供了两种跳转模式,分别是router.pushUrl()和router.replaceUrl()。这两种模式决定了目标页是否会替换当前页。
- router.pushUrl():目标页不会替换当前页,而是压入页面栈。这样可以保留当前页的状态,并且可以通过返回键或者调用router.back()方法返回到当前页。
- router.replaceUrl():目标页会替换当前页,并销毁当前页。这样可以释放当前页的资源,并且无法返回到当前页。
同时,Router模块提供了两种实例模式,分别是Standard和Single。这两种模式决定了目标url是否会对应多个实例。
- Standard:标准实例模式,也是默认情况下的实例模式。每次调用该方法都会新建一个目标页,并压入栈顶。
- Single:单实例模式。即如果目标页的url在页面栈中已经存在同url页面,则离栈顶最近的同url页面会被移动到栈顶,并重新加载;如果目标页的url在页面栈中不存在同url页面,则按照标准模式跳转。
页面的返回使用
router.back();
back里可以增加url和param。
Ability跳转
和andorid中起一个intent然后跳转activity类似。
let wantInfo = {
deviceId: '', // deviceId为空表示本设备
bundleName: 'com.example.myapplication', //包名
abilityName: 'SecondAbility',
moduleName: 'module2',
parameters: {
},
}
this.context.startAbility(wantInfo).then(() => {
// ...
}).catch((err) => {
// ...
})
UI中还有一些如事件、动画、绘制等看看官方文档也就是那么回事,先不多研究了。
网络和持久化
网络请求
// 引入包名
import http from '@ohos.net.http';
// 每一个httpRequest对应一个HTTP请求任务,不可复用
let httpRequest = http.createHttp();
// 用于订阅HTTP响应头,此接口会比request请求先返回。可以根据业务需要订阅此消息
// 从API 8开始,使用on('headersReceive', Callback)替代on('headerReceive', AsyncCallback)。 8+
httpRequest.on('headersReceive', (header) => {
console.info('header: ' + JSON.stringify(header));
});
httpRequest.request(
// 填写HTTP请求的URL地址,可以带参数也可以不带参数。URL地址需要开发者自定义。请求的参数可以在extraData中指定
"EXAMPLE_URL",
{
method: http.RequestMethod.POST, // 可选,默认为http.RequestMethod.GET
// 开发者根据自身业务需要添加header字段
header: {
'Content-Type': 'application/json'
},
// 当使用POST请求时此字段用于传递内容
extraData: {
"data": "data to send",
},
expectDataType: http.HttpDataType.STRING, // 可选,指定返回数据的类型
usingCache: true, // 可选,默认为true
priority: 1, // 可选,默认为1
connectTimeout: 60000, // 可选,默认为60000ms
readTimeout: 60000, // 可选,默认为60000ms
usingProtocol: http.HttpProtocol.HTTP1_1, // 可选,协议类型默认值由系统自动指定
}, (err, data) => {
if (!err) {
// data.result为HTTP响应内容,可根据业务需要进行解析
console.info('Result:' + JSON.stringify(data.result));
console.info('code:' + JSON.stringify(data.responseCode));
// data.header为HTTP响应头,可根据业务需要进行解析
console.info('header:' + JSON.stringify(data.header));
console.info('cookies:' + JSON.stringify(data.cookies)); // 8+
} else {
console.info('error:' + JSON.stringify(err));
// 取消订阅HTTP响应头事件
httpRequest.off('headersReceive');
// 当该请求使用完毕时,调用destroy方法主动销毁
httpRequest.destroy();
}
}
);
| 接口名 | 功能描述 |
|---|---|
| createHttp() | 创建一个http请求。 |
| request() | 根据URL地址,发起HTTP网络请求。 |
| destroy() | 中断请求任务。 |
| on(type: 'headersReceive') | 订阅HTTP Response Header 事件。 |
| off(type: 'headersReceive') | 取消订阅HTTP Response Header 事件。 |
| once('headersReceive')8+ | 订阅HTTP Response Header 事件,但是只触发一次。 |
官方直接提供了http和ws的api,但感觉有点原始,可以自己封装一下,但是这个httprequest是不可复用的。
持久化
- 用户首选项(Preferences):提供了轻量级配置数据的持久化能力,并支持订阅数据变化的通知能力。不支持分布式同步,常用于保存应用配置信息、用户偏好设置等。
- 键值型数据管理(KV-Store):提供了键值型数据库的读写、加密、手动备份能力。分布式功能暂不支持。
- 关系型数据管理(RelationalStore):提供了关系型数据库的增删改查、加密、手动备份能力。分布式功能暂不支持。
- 分布式数据对象(DataObject):独立提供对象型结构数据的分布式能力。分布式功能暂不支持。
- 跨应用数据管理(DataShare):提供了向其他应用共享以及管理其数据的方法。仅系统应用可用,非系统应用无需关注,下文不做具体介绍。
官方提供了5种方式的持久化,对应不同的业务场景。
第一篇先到这儿吧~