鴻蒙即將拋棄Android,你還不來學習一下?

前言

最近移動開發圈子里,鴻蒙可謂出盡了風頭,先是宣布即將正式發布的 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: 應用模型,有 StageFA 可選,官網推薦使用 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 跳轉

  1. 在 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) => {
    // ...
})
  1. 在 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;
        // ...
    }
}
  1. 如需要停止當前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 模塊提供了兩種實例模式,分別是 StandardSingle。這兩種模式決定了目標 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 原生的數據庫操作,簡直是噩夢?? 鴻蒙仍然領先~

多線程

在移動應用中,免不了要處理各種后臺任務,比如接口請求、數據存儲、埋點上報等等,因此多線程的性能,直接影響了應用的流暢度。

鴻蒙提供了 TaskPoolWorker 兩種并發能力

看到第一行,線程間內存不共享的時候,我震驚了,操作系統課堂上老師不是說線程共享進程的資源嗎?難道物理學不存在了?

內存不共享的好處是不用擔心對象被并發修改,但是壞處是線程間通信必須復制對象,對于多線程同步大量數據的場景,性能應該會有不小的影響。網上搜了下,也沒找到鴻蒙這么設計的原因。

既然如此,我們先看下怎么使用吧

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 通過序列化機制與宿主線程之間相互通信,完成命令及數據交互。

使用

  1. 在主線程中通過調用 ThreadWorker 的 constructor() 方法創建 Worker 對象,當前線程為宿主線程。
import worker from '@ohos.worker';

const workerInstance = new worker.ThreadWorker('entry/ets/workers/MyWorker.ts');
  1. 在宿主線程中通過調用 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] });
  1. 在 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,希望讀完本文對大家有幫助。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,563評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,694評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,672評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,965評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,690評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,019評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,013評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,188評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,718評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,438評論 3 360
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,667評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,149評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,845評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,252評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,590評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,384評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,635評論 2 380

推薦閱讀更多精彩內容