前言
最近移動開發圈子里,鴻蒙可謂出盡了風頭,先是宣布即將正式發布的 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,希望讀完本文對大家有幫助。