前言
最近移动开发圈子里,鸿蒙可谓出尽了风头,先是宣布即将正式发布的 Harmony OS Next 将完全剥离 Android 代码,也就是不再兼容 Android,化身为纯血的鸿蒙,紧接着又启动了鸿蒙生态千帆启航,伴随着的是众多大厂已经启动原生鸿蒙适配,包括支付宝、京东、美团等等。
作为一个整天被内卷的客户端开发,不得不加入了。
本文基于 Harmony OS Api 9,如果文章内容与新版本有不一致的地方,一切以新版本为准,望见谅。
熟悉概念
在正式开始前,我们先了解一下鸿蒙开发相关的概念,打开鸿蒙官网,首先看到的就是这几个套件
下面我们来简单认识下
-
DevEco Studio
面向 HarmonyOS 应用及元服务开发者提供的集成开发环境(IDE), 助力高效开发。
也就是鸿蒙开发的 IDE,类似 Android Studio 和 Xcode。
-
ArkTS
ArkTS 是鸿蒙生态的应用开发语言。它在保持 TypeScript (简称TS)基本语法风格的基础上,对 TS 的动态类型特性施加更严格的约束,引入静态类型。同时,提供了声明式 UI、状态管理等相应的能力,让开发者可以以更简洁、更自然的方式开发高性能应用。
TypeScript 曾经风靡一时,弥补了 JavaScript 不支持强类型的缺点,作为一名 Java 开发者,更习惯强类型语言。至于 ArkTS 和 TypeScript 的区别可以暂时不用关心,把它当做标准的 TS 用就行。
-
ArkUI
ArkUI 是一套构建分布式应用界面的声明式 UI 开发框架。它使用极简的 UI 信息语法、丰富的 UI 组件、以及实时界面预览工具,帮助您提升 HarmonyOS 应用界面开发效率30%。您只需使用一套 ArkTS API,就能在多个 HarmonyOS 设备上提供生动而流畅的用户界面体验。
可以理解为用 TS 实现的一套 UI 组件库,包括常用的组件和布局等,类似于 Google 的 material 组件库和 Swift UI。
-
ArkCompiler
ArkCompiler 是华为自研的统一编程平台,包含编译器、工具链、运行时等关键部件,支持高级语言在多种芯片平台的编译与运行,并支撑应用和服务运行在手机、个人电脑、平板、电视、汽车和智能穿戴等多种设备上的需求。
用于将 TS 代码编译为鸿蒙系统可执行指令,俗称编译打包工具。
初识鸿蒙
准备IDE
按照官方教程,我们需要下载鸿蒙开发 IDE,也就是 DevEco Studio,和 AndroidStudio 一样,都是基于 IntelliJ IDEA 二次开发,上手没有难度。
首次启动,需要安装 Node.js、ohpm、Harmony OS SDK 等依赖,根据引导一直下一步即可。
完整依赖如下
可能有些同学还不太熟悉这些概念,我们简单介绍下
-
Node.js
Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境,我们从上面的概念中了解到,鸿蒙使用 TS 作为开发语言,而 TS 实际上就是 JS,因此需要 JS 运行环境也很正常。
-
ohpm
大家应该都听说过 npm,是一个前端比较常用的包管理工具,ohpm 全称应该是
Open Harmony Package Manager
,也就是鸿蒙上使用的包管理工具,类似于 Java 上的 Maven 仓库。 -
Harmony OS SDK
这个应该很好理解,开发鸿蒙需要的软件开发工具包,相当于 Android 上的 Android SDK。
有一点需要注意,IDE 仅支持特定版本的 Node.js,我本地由于安装了高版本的 Node.js,因此无法继续下一步,建议大家先卸载本地安装的 Node.js,通过 IDE 重新安装支持的版本,因为我自己安装的低版本也会报错🤩
创建项目
IDE 配置完成后,我们新建一个项目,IDE 默认提供了很多模板,值得一提的是,把光标放上去还会提示你支持哪些设备
这里出现了 Ability
的概念,看起来和 Android 中的 Activity 很像,也就是一个页面,我们暂且认为它就是一个页面,后面我们再详细介绍。
除了空页面之外,还支持关于、分类、网格、列表、登录、闪屏等模板,还支持创建 C++ 原生项目。
支持的完整模板如下
我们选择 Empty Ability
下一步
接下来到了项目配置页面,支持的配置如下
-
Project name: 项目名
-
Bundle name: 项目唯一标志,类似于 Android 的 Package name
-
Save location: 保存位置
-
Compile SDK: 编译使用的 SDK 版本
-
Model: 应用模型,有
Stage
和FA
可选,官网推荐使用 Stage,我们就先用这个 -
Enable Super Visual: 官方的解释是启用低代码开发,先不管它
-
Language: 开发语言,Stage 模型只能选 ArkTS,FA 模型还可以选 JS
-
Compatible SDK: 字面意思是兼容的的 SDK 版本,暂时不清楚是最低支持版本(minSdk)还是适配的版本(targetSdk),看起来更像是前者,如果是这样的话鸿蒙走的应该是类似苹果的路线,即新版本出来后开发者必须要适配,否则高版本可能用不了
-
Device type: 支持的设备类型
点击 Finish 即可完成创建
项目结构
创建完的项目是这样的
完整目录如下
├── AppScope // app 默认配置
│ ├── resources // 资源
│ │ └── base
│ │ ├── element // 文案、颜色等资源
│ │ │ └── string.json
│ │ └── media // 图片资源
│ │ └── app_icon.png
│ └── app.json5 // app 配置,包括 名称、图标、bundleName、版本号等
├── entry // entry 文件夹,相当于 Android 项目中的 app module
│ ├── src
│ │ ├── main
│ │ │ ├── ets // 源代码,相当于 Android 项目中的 java 目录
│ │ │ │ ├── entryability
│ │ │ │ │ └── EntryAbility.ts // entry 中的页面,一个 entry 可以有多个 Ability
│ │ │ │ └── pages
│ │ │ │ └── Index.ets
│ │ │ ├── resources
│ │ │ │ ├── base // 和语言无关的通用资源
│ │ │ │ │ ├── element // 文案、颜色等资源
│ │ │ │ │ │ ├── color.json
│ │ │ │ │ │ └── string.json
│ │ │ │ │ ├── media // 图片资源
│ │ │ │ │ │ └── icon.png
│ │ │ │ │ └── profile
│ │ │ │ │ └── main_pages.json // entry 中包含的 pages 路径,相当于前端项目中 app.json 中的 pages
│ │ │ │ ├── en_US // 英文下使用的资源,可以用来配置多语言
│ │ │ │ ├── rawfile // 应该是存放二进制文件的目录,相当于 Android 项目中的 res/raw 目录,暂时还没用到
│ │ │ │ └── zh_CN // 同 en_US
│ │ │ └── module.json5 // entry 内部配置文件,包括名称、包含的 Abilities、pages,默认 Ability 等
│ │ └── ohosTest // 测试代码
│ ├── build-profile.json5 // entry 构建配置,包括应用模型、支持的系统类型等
│ ├── hvigorfile.ts // 暂时不清楚
│ └── oh-package.json5 // entry 对外配置文件,包括名称、版本、许可证、依赖项等
├── hvigor // 暂时不清楚,看着像是 Android 项目中的 grade 目录
│ ├── hvigor-config.json5
│ └── hvigor-wrapper.js
├── oh_modules // 项目依赖的三方库,相当于前端项目中的 node_modules
├── build-profile.json5 // app 构建配置,包括 app 签名、编译 SDK 版本、兼容 SDK 版本,以及包含的 entry 列表
├── hvigorfile.ts // 暂时不清楚
├── hvigorw // 暂时不清楚
├── hvigorw.bat
├── local.properties // 本地配置
├── oh-package.json5 // 项目配置,包括名称、版本、许可证、依赖项等
└── oh-package-lock.json5
鸿蒙中支持多种 Module,常用的有以下两种:
-
Entry
相当于 Android 中的 Application Module,每个 Entry 都可以独立运行。
-
Library
相当于 Android 中的 Library Module,仅可被依赖,无法独立运行。
Stage 模型
Stage模型概念图
-
UIAbility组件
UIAbility 组件是一种包含 UI 界面的应用组件,主要用于和用户交互。UIAbility 组件是系统调度的基本单元,为应用提供绘制界面的窗口;一个 UIAbility 组件中可以通过多个页面来实现一个功能模块。每一个 UIAbility 组件实例,都对应于一个最近任务列表中的任务。
-
WindowStage
每个 UIAbility 类实例都会与一个 WindowStage 类实例绑定,该类提供了应用进程内窗口管理器的作用。它包含一个主窗口。也就是说 UIAbility 通过 WindowStage 持有了一个窗口,该窗口为 ArkUI 提供了绘制区域。
-
Context
Context 是应用中对象的上下文,其提供了应用的一些基础信息,例如 resourceManager(资源管理)、applicationInfo(当前应用信息)、dir(应用开发路径)、area(文件分区)等,以及应用的一些基本方法,例如 createBundleContext()、getApplicationContext()等。UIAbility 组件和各种 ExtensionAbility 派生类组件都有各自不同的 Context 类。分别有基类Context、ApplicationContext、AbilityStageContext、UIAbilityContext、ExtensionContext、ServiceExtensionContext等Context。
-
Page
Ability 是一个窗口,不包含内容,而 Page 则是真正显示内容的载体,Page 需要依附于 Ability 才能显示。
上手
熟悉了上面的这些概念,我们就可以尝试开发一个鸿蒙上的 App 了。
之前学习 Compose 的时候做了一个「玩 Android」App,为了方便使用现成接口和 UI,我们来做一个鸿蒙版的「玩 Android」,顺便对比下鸿蒙和 Compose 在 UI 层的差异。
UI开发
和 Compose、SwiftUI 类似,鸿蒙的 ArkUI 也采用声明式开发范式,这也是现代UI开发的共识,相比命令式UI,声明式UI更加简洁高效。
ArkUI 提供了官方组件和布局,基本能够满足常见 UI 界面的开发
组件的详细使用这里不再赘述,我们以文章 Item 布局作为示例,来对比下鸿蒙和 Compose 的实现方式
先看下展示效果
代码对比
可以看出,语法的相似度非常高,以至于我后来直接把 Compose 的代码复制来过改一下就能用了🤣
简单介绍下差异
-
鸿蒙使用 struct 定义组件,而 Compose 使用方法
-
鸿蒙仅支持在 ArkUI 中使用除组件外的特定操作符,包括 if-else、ForEach 等,而 Compose 比较自由,可以在 UI 方法中使用任意代码
-
鸿蒙通过组件对象提供的方法设置属性,如
Text("").fontSize(10)
,而 Compose 则是在构造函数中设置,如Text(text = "", fontSize = 10.sp)
由于鸿蒙和 Compose 都是声明式 UI,因此状态更新也比较相似,都是状态驱动 UI 更新,此外,鸿蒙和 Compose 都支持通过 Diff 算法完成差量更新。
页面路由
在项目结构部分提到,鸿蒙上 UI 界面的单位包括 Ability 和 page。
一般来说一个功能只需要一个 Ability 呢 就够了,那什么时候需要多个 Ability 呢?
举个🌰,如果你当前开发的是通讯录应用,当其他应用需要选择联系人时,需要打开通讯录的选人页面,而打开外部应用页面的最小单元为 Ability,因为 page 无法独立显示,这时你就需要创建多个 Ability。
Ability 跳转
- 在 EntryAbility 中,通过调用 startAbility() 方法启动 UIAbility,want 为 UIAbility 实例启动的入口参数,其中 bundleName 为待启动应用的 Bundle 名称,abilityName 为待启动的 UIAbility 名称,moduleName 在待启动的 UIAbility 属于不同的 Module 时添加,parameters 为自定义信息参数。
let wantInfo = {
deviceId: '', // deviceId为空表示本设备
bundleName: 'com.example.myapplication',
abilityName: 'FuncAbility',
moduleName: 'module1', // moduleName非必选
parameters: { // 自定义信息
info: '来自EntryAbility Index页面',
},
}
// context为调用方UIAbility的AbilityContext
this.context.startAbility(wantInfo).then(() => {
// ...
}).catch((err) => {
// ...
})
- 在 FuncAbility 的生命周期回调文件中接收 EntryAbility 传递过来的参数。
import UIAbility from '@ohos.app.ability.UIAbility';
import Window from '@ohos.window';
export default class FuncAbility extends UIAbility {
onCreate(want, launchParam) {
// 接收调用方UIAbility传过来的参数
let funcAbilityWant = want;
let info = funcAbilityWant?.parameters?.info;
// ...
}
}
- 如需要停止当前UIAbility实例,通过调用
terminateSelf()
方法实现。
// context为需要停止的UIAbility实例的AbilityContext
this.context.terminateSelf((err) => {
// ...
});
同时,鸿蒙也提供了 startAbilityForResult
来实现启动 Ability 并获取返回结果,详细使用可以看官方文档。
可以看出,Ability 和 Android 中的 Activity 不仅在形态上相似,在使用上也基本一致,唯一不同的是,Activity 可以直接显示内容,而 Ability 则是一个容器,需要依赖 page 显示内容。
page 跳转
鸿蒙提供了 Router
模块,通过 url 地址完成 page 之间的路由。
Router 模块提供了两种跳转模式,分别是 router.pushUrl()
和 router.replaceUrl()
,这两种模式决定了目标页是否会替换当前页。
-
router.pushUrl():目标页不会替换当前页,而是压入页面栈。这样可以保留当前页的状态,并且可以通过返回键或者调用
router.back()
方法返回到当前页。 -
router.replaceUrl():目标页会替换当前页,并销毁当前页。这样可以释放当前页的资源,并且无法返回到当前页。
同时,Router 模块提供了两种实例模式,分别是 Standard
和 Single
。这两种模式决定了目标 url 是否会对应多个实例。
-
Standard:标准实例模式,也是默认情况下的实例模式。每次调用该方法都会新建一个目标页,并压入栈顶。
-
Single:单实例模式。即如果目标页的url在页面栈中已经存在同 url 页面,则离栈顶最近的同 url 页面会被移动到栈顶,并重新加载;如果目标页的 url 在页面栈中不存在同 url 页面,则按照标准模式跳转。
function onJumpClick(): void {
router.pushUrl({
url: 'pages/Detail', // 目标url
params: {id: 123} // 添加params属性,传递自定义参数
}, (err) => {
if (err) {
console.error(`Invoke pushUrl failed, code is ${err.code}, message is ${err.message}`);
return;
}
console.info('Invoke pushUrl succeeded.');
})
}
在目标页中,可以通过调用 Router 模块的 getParams()
方法来获取传递过来的参数。
在目标页中,可以通过调用 Router 模块的 getParams()
方法来获取传递过来的参数。
const params = router.getParams(); // 获取传递过来的参数对象
const id = params['id']; // 获取id属性的值
page 的设计思路和前端比较接近,相比 Android 的 Activity 更加轻量,相比 Fragment 使用起来更加简单。
和 Fragment 不同的是,page 是全屏展示,而 Fragment 则比较自由,展示大小可以自定义。
其实很多年前 Android 上就有单 Activity 多 Fragment 的架构设计,最近 Google 也推出了 Jetpack Navigation 库,专门用来做 Fragment 路由,然而用起来并不友好,网上吐槽不少。
单从页面架构设计来看,鸿蒙确实遥遥领先~
网络请求
在互联网时代,网络请求是 App 中最常用的功能之一。
鸿蒙中的网络开发比较简单,官方提供了 Http 模块,可以快速发送网络请求,为了减少重复代码,我们可以封装一个统一的请求方法
async function requestSync<T>(path: string, method: http.RequestMethod, extraData?: Object): Promise<Response<T>> {
return new Promise<Response<T>>((resolve, reject) => {
let url = BASE_URL + path;
let uri = parseUri(url);
let header = {};
if (method === http.RequestMethod.POST) {
header["Content-Type"] = "application/x-www-form-urlencoded";
if (!extraData) {
// POST 必须有请求体,否则会报 Parameter error
extraData = {};
}
}
let httpRequest = http.createHttp();
hilog.info(0, TAG, `start request, path: ${path}, method: ${method}, extraData: ` + JSON.stringify(extraData));
httpRequest.request(
url,
{
method: method,
expectDataType: http.HttpDataType.OBJECT,
header: header,
extraData: extraData
},
(err, data) => {
let res = new Response<T>()
if (!err && data.responseCode === 200) {
Object.assign(res, data.result)
hilog.info(0, TAG, `request success, path: ${path}, result: ${JSON.stringify(res)}`)
} else {
hilog.error(0, TAG, `request error, path: ${path}, error: ${JSON.stringify(err)}`)
res.errorCode = data?.responseCode??-1
res.errorMsg = err?.message??""
}
resolve(res);
}
)
})
}
同时提供统一的返回结构 Response
export class Response<T> {
errorCode: number = 0
errorMsg: string = ""
data: T = null
isSuccess(): boolean {
return this.errorCode === 0
}
isSuccessWithData(): boolean {
return this.errorCode === 0 && !!this.data
}
}
当需要发请求的时候,一行代码就能搞定
async getHomeArticleList(page: number): Promise<Response<ArticleList>> {
return requestSync(`/article/list/${page}/json`, http.RequestMethod.GET);
}
得益于 TS 中的 Promise,我们可以将繁琐的异步回调转为同步调用,和 Kotlin 中的协程有异曲同工之妙,强烈推荐!
持久化
App 的状态保存,免不了要使用持久化数据,持久化主要分为 KV 键值对、SQL 数据库,而鸿蒙上 KV 键值对又分为 Preference 和 KVStore。
-
用户首选项(Preferences):通常用于保存应用的配置信息。数据通过文本的形式保存在设备中,应用使用过程中会将文本中的数据全量加载到内存中,所以访问速度快、效率高,但不适合需要存储大量数据的场景。
-
键值型数据库(KV-Store):一种非关系型数据库,其数据以“键值”对的形式进行组织、索引和存储,其中“键”作为唯一标识符。适合很少数据关系和业务关系的业务数据存储,同时因其在分布式场景中降低了解决数据库版本兼容问题的复杂度,和数据同步过程中冲突解决的复杂度而被广泛使用。相比于关系型数据库,更容易做到跨设备跨版本兼容。
-
关系型数据库(RelationalStore):一种关系型数据库,以行和列的形式存储数据,广泛用于应用中的关系型数据的处理,包括一系列的增、删、改、查等接口,开发者也可以运行自己定义的SQL语句来满足复杂业务场景的需要。
Preferences
用户首选项为应用提供Key-Value键值型的数据处理能力,支持应用持久化轻量级数据,并对其修改和查询。
设计上同样是内存 + 磁盘缓存,首次使用时会将全量数据读取到内存中,因此不是和存储大量数据。
接口如下
用法基本和 Android 中的 SharedPreference 一致,鸿蒙还提供了 on 和 off 来订阅数据变更,遥遥领先~
不过,还是要吐槽一下,目前 DevEcoStudio 3.1.1 正式版每次运行会清楚 App 数据,导致持久化保存的数据全部被清空,希望新版本能优化下。
KV-Store
相比 Preferences,KV-Store 在数据大小上没有太多限制,猜测底层实现应该和 MMKV 一样,通过共享内存完成磁盘同步,因此性能要高不少。
接口如下
鸿蒙直接把 Android 上三方库的功能给内置了,再次领先~
数据库
当我们需要存储复杂数据结构时,就需要用到数据库了,鸿蒙也提供了便捷操作数据库的接口
这么方便的操作数据库,在没有 Room、GreenDAO 等开源库之前,简直不敢想,Android 原生的数据库操作,简直是噩梦😭 鸿蒙仍然领先~
多线程
在移动应用中,免不了要处理各种后台任务,比如接口请求、数据存储、埋点上报等等,因此多线程的性能,直接影响了应用的流畅度。
鸿蒙提供了 TaskPool
和 Worker
两种并发能力
看到第一行,线程间内存不共享的时候,我震惊了,操作系统课堂上老师不是说线程共享进程的资源吗?难道物理学不存在了?
内存不共享的好处是不用担心对象被并发修改,但是坏处是线程间通信必须复制对象,对于多线程同步大量数据的场景,性能应该会有不小的影响。网上搜了下,也没找到鸿蒙这么设计的原因。
既然如此,我们先看下怎么使用吧
TaskPool
TaskPool 支持在主线程封装任务抛给任务队列,系统选择合适的工作线程,进行任务的分发及执行,再将结果返回给主线程,支持任务的执行、取消,工作线程数量上限为 4。
使用
import taskpool from '@ohos.taskpool';
@Concurrent
function imageProcessing(dataSlice: ArrayBuffer) {
// 步骤1: 具体的图像处理操作及其他耗时操作
return dataSlice;
}
function histogramStatistic(pixelBuffer: ArrayBuffer) {
// 步骤2: 分成三段并发调度
let number = pixelBuffer.byteLength / 3;
let buffer1 = pixelBuffer.slice(0, number);
let buffer2 = pixelBuffer.slice(number, number * 2);
let buffer3 = pixelBuffer.slice(number * 2);
let task1 = new taskpool.Task(imageProcessing, buffer1);
let task2 = new taskpool.Task(imageProcessing, buffer2);
let task3 = new taskpool.Task(imageProcessing, buffer3);
taskpool.execute(task1).then((ret: ArrayBuffer[]) => {
// 步骤3: 结果处理
});
taskpool.execute(task2).then((ret: ArrayBuffer[]) => {
// 步骤3: 结果处理
});
taskpool.execute(task3).then((ret: ArrayBuffer[]) => {
// 步骤3: 结果处理
});
}
和 Java 中的 ThreadPool 比较类似,通过任务队列 + 线程池实现多任务并发,相比 ThreadPool,TaskPool 的接口比较简单,内部自动分派线程,不支持配置线程类型和数量,此外,TaskPool 的最大线程数为4,感觉放在现在动辄 8 核的处理器上,好像差了点意思,似乎不能完全发挥处理器性能?
Worker
创建 Worker 的线程称为宿主线程(不一定是主线程,工作线程也支持创建 Worker 子线程),Worker 自身的线程称为 Worker 子线程(或 Actor 线程、工作线程)。每个 Worker 子线程与宿主线程拥有独立的实例,包含基础设施、对象、代码段等。Worker 子线程和宿主线程之间的通信是基于消息传递的,Worker 通过序列化机制与宿主线程之间相互通信,完成命令及数据交互。
使用
- 在主线程中通过调用 ThreadWorker 的 constructor() 方法创建 Worker 对象,当前线程为宿主线程。
import worker from '@ohos.worker';
const workerInstance = new worker.ThreadWorker('entry/ets/workers/MyWorker.ts');
- 在宿主线程中通过调用 onmessage() 方法接收 Worker 线程发送过来的消息,并通过调用 postMessage() 方法向 Worker 线程发送消息。
例如向 Worker 线程发送训练和预测的消息,同时接收 Worker 线程发送回来的消息。
// 接收Worker子线程的结果
workerInstance.onmessage = function(e) {
// data:Worker线程发送的信息
let data = e.data;
console.info('MyWorker.ts onmessage');
}
workerInstance.onerror = function (d) {
// 接收Worker子线程的错误信息
}
// 向Worker子线程发送训练消息
workerInstance.postMessage({ 'type': 0 });
// 向Worker子线程发送预测消息
workerInstance.postMessage({ 'type': 1, 'value': [90, 5] });
- 在 Worker 线程中通过调用 onmessage() 方法接收宿主线程发送的消息内容,并通过调用 postMessage() 方法向宿主线程发送消息。
例如在Worker线程中定义预测模型及其训练过程,同时与主线程进行信息交互。
import worker, { ThreadWorkerGlobalScope, MessageEvents, ErrorEvent } from '@ohos.worker';
let workerPort: ThreadWorkerGlobalScope = worker.workerPort;
// 定义训练模型及结果
let result;
// 定义预测函数
function predict(x) {
return result[x];
}
// 定义优化器训练过程
function optimize() {
result = {};
}
// Worker线程的onmessage逻辑
workerPort.onmessage = function (e: MessageEvents) {
let data = e.data
// 根据传输的数据的type选择进行操作
switch (data.type) {
case 0:
// 进行训练
optimize();
// 训练之后发送主线程训练成功的消息
workerPort.postMessage({ type: 'message', value: 'train success.' });
break;
case 1:
// 执行预测
const output = predict(data.value);
// 发送主线程预测的结果
workerPort.postMessage({ type: 'predict', value: output });
break;
default:
workerPort.postMessage({ type: 'message', value: 'send message is invalid' });
break;
}
}
此外,Worker 的创建和销毁耗费性能,需要自行管理已创建的 Worker 并重复使用。Worker 空闲时也会一直运行,因此当不需要 Worker 时,需要调用 terminate() 接口或 parentPort.close() 方法主动销毁 Worker。Worker 存在数量限制,支持最多同时存在 8 个 Worker。
可以看出,Worker 更接近 Java 中使用 new Thread()
原生创建线程的方式,不同的是 Worker 一旦创建,就会一直运行,占用 CPU 资源,只能手动停止,而 Java 中的线程则更加“智能”,一旦线程中的逻辑执行完,即自动进入 TERMINATED 状态,释放 CPU 资源,并自动销毁。
因此,推荐大家使用 TaskPool。
看起来鸿蒙上使用多线程不太领先,存在各种限制,比如线程间内存不共享,线程数量存在上限。
开发体验
在完整开发完一个 App 后,简单总结下鸿蒙原生开发的优缺点。
优点
-
原生支持声明式 UI
虽然目前 Compose 和 SwiftUI 也支持声明式,但推出这么久之后,真正在线上大规模使用的产品寥寥无几,顶多在新业务上尝试使用,所以鸿蒙在 UI 上有天然优势。
-
系统 API 封装性高
以发送网络请求为例,在 Android 上如果用原生的方式,各种配置用起来头大,而鸿蒙则是通过一个方法即可发起请求,并将常用配置都作为参数开放出来,其他 API 也是类似,鸿蒙的系统 API 用起来更加便捷。
-
TS 学习成本低
对于有前端开发经验的同学来说,几乎是0成本,即使没用过 TS/JS,上手也非常简单。
-
Android 同学上手快
首先 IDE 使用基本一致,其次系统架构和 API 定义也非常相似,可以说是一一对应,如果你刚好又用过 Compose 或 Flutter,那 ArkUI 也不成问题。
缺点
-
系统不开源
在使用系统 API 的时候,有时想看下内部实现,习惯性的点击方法查看源码,发现看不了,如果遇到系统 API 有问题,就很难定位了。
-
配套工具还未完善
在开发中遇到一些bug,比如模拟器上 WebView 无法滚动,而真机正常,还有一些体验不太好的地方,比如每次重新运行会先卸载再安装,导致重新运行后数据丢失,在社区咨询后得知后期都会修复。
-
社区不够成熟
目前比较活跃的社区只有官方的开发者社区,但是目前看下来干活不多,大多是来咨询问题或者反馈bug的,而且很多反馈没人回应,可能是问题太多了忙不过来。
-
开源框架较少
官方维护了一个 开源库列表 ,刚接触鸿蒙时,里面的开源库还寥寥无几,没想到几个月后的现在已经非常丰富了,包括和 OKHttp 能力基本一致的网络库 httpclient ,但是稳定性还需要大家去验证。
深入了解
在开发的过程中,我一直在想一个问题,鸿蒙使用 TS 作为开发语言,那么运行时到底是通过 JS Runtime 还是其他方式呢?如果是 JS Runtime,那和 WebApp 有什么区别呢?如果是通过转译的方式,那虚拟机最终执行的代码是什么形式的?
这些问题可能只有完全了解了 ArkCompiler 才能回答,不过目前官网对 ArkCompiler 的介绍仅局限于短短一页,我们只能尝试分析打包产物,看能否看出一些端倪。
先解包 hap 看下目录结构
!
resources 和 module.json、pack.info 是资源和配置信息,主要看 ets 目录
先看 modules.abc(857KB)
abc 的全称是 Ark byte code,即方舟字节码,暂时不清楚这里保存了什么信息,以及它的运行原理。
接着看 sourceMaps.map(828KB) 和 symbolMap.map(287KB)
可以看出这两个文件保存了所有 TS 文件的原始信息和 mappings 映射,mappings 是不是很眼熟?
没错,这里可能是将 TS 源码进行了混淆,在安装或运行时,虚拟机再通过某种方式获取到真正要执行的 TS 代码。官网上的介绍也能佐证
到这里,我们对鸿蒙运行时有了一个大致的轮廓:虚拟机执行的仍然是原始 TS 代码,只是在编译时被加密了,以此保证源码安全。
我大胆猜测,鸿蒙虚拟机内置了 JS Runtime,用来解释执行 TS 代码,至于 UI 渲染,则是 ArkUI 将 TS 代码映射为相应的 UI 控件,并进行布局和渲染。
More? ArkUI-X
你以为鸿蒙只是一个独立于 Android 和 iOS 的移动操作系统吗?鸿蒙的野心不止于此!
要知道,鸿蒙剥离 Android 之后,需要各个应用开发商重新基于鸿蒙开发一次应用,这个成本绝对不可忽视,尤其对于头部大型 App 来说,业务极其复杂,开发成本也非常大。
因此,除了积极和开发商沟通外,鸿蒙也在考虑如何降低开发成本,当大家还在考虑能否将 Android/iOS 代码转为鸿蒙时,华为悄悄的发布了 ArkUI-X
。
简单来说,使用 ArkUI 完成鸿蒙应用开发后,可以快速构建 Android 和 iOS 平台的应用,相当于鸿蒙版的 Flutter。
总结
本文主要介绍了鸿蒙相关的概念,以及如何上手开发一个鸿蒙原生应用,通过开发一个鸿蒙版的「玩 Android」,带领大家熟悉 ArkUI 和常用 API 的使用,基于开发体验总结了现阶段鸿蒙开发的优势和存在的问题,通过对 hap 包的简单分析了解了鸿蒙运行时的大致轮廓,最后介绍了鸿蒙上的跨平台开发框架 ArkUI-X,希望读完本文对大家有帮助。
为了能让大家更好的学习鸿蒙(HarmonyOS NEXT)开发技术,这边特意整理了《鸿蒙开发学习手册》(共计890页),希望对大家有所帮助:https://qr21.cn/FV7h05
《鸿蒙开发学习手册》:https://qr21.cn/FV7h05
入门必看:https://qr21.cn/FV7h05
1. 应用开发导读(ArkTS)
2. ……
HarmonyOS 概念:https://qr21.cn/FV7h05
- 系统定义
- 技术架构
- 技术特性
- 系统安全
如何快速入门:https://qr21.cn/FV7h05
1. 基本概念
2. 构建第一个ArkTS应用
3. ……
开发基础知识:https://qr21.cn/FV7h05
1. 应用基础知识
2. 配置文件
3. 应用数据管理
4. 应用安全管理
5. 应用隐私保护
6. 三方应用调用管控机制
7. 资源分类与访问
8. 学习ArkTS语言
9. ……
基于ArkTS 开发:https://qr21.cn/FV7h05
1. Ability开发
2. UI开发
3. 公共事件与通知
4. 窗口管理
5. 媒体
6. 安全
7. 网络与链接
8. 电话服务
9. 数据管理
10. 后台任务(Background Task)管理
11. 设备管理
12. 设备使用信息统计
13. DFX
14. 国际化开发
15. 折叠屏系列
16. ……