背景
鴻蒙系統(tǒng)是中國首款完全自主研發(fā)的分布式操作系統(tǒng),其底層技術實現(xiàn)完全自主可控,擺脫了對安卓和iOS的依賴。尤其在當前國際科技競爭加劇的背景下,鴻蒙的誕生降低了我國在操作系統(tǒng)領域對外國技術的依賴,為國家信息安全和產(chǎn)業(yè)安全筑起防線。
除了移動端系統(tǒng),鴻蒙電腦也將在5月份發(fā)布,鴻蒙PC端應用開發(fā),工具、環(huán)境和移動端都是一樣的,可以真正實現(xiàn)一次開發(fā),多端部署,所以大家了解一下還是有價值的。
1.視圖
視圖單位

vp類似于Android中的dp,fp類似于Android中的sp
生命周期
UIAbility生命周期
UIAbility是一種包含UI的應用組件,主要用于和用戶交互,類似于Android中的Activity
如果開發(fā)者希望在任務視圖中看到一個任務,建議使用“一個UIAbility+多個頁面”的方式,可以避免不必要的資源加載。
如果開發(fā)者希望在任務視圖中看到多個任務,或者需要同時開啟多個窗口,建議使用多個UIAbility實現(xiàn)不同的功能。
生命周期如下:

UIAbility實例創(chuàng)建完成之后,在進入Foreground之前,系統(tǒng)會創(chuàng)建一個WindowStage。WindowStage創(chuàng)建完成后會進入onWindowStageCreate()回調,可以在該回調中設置UI入口頁面(windowStage.loadContent),類似于Android中Fragment的onCreateView()

頁面生命周期

頁面生命周期會區(qū)分組件類型,頁面入口組件和普通自定義組件生命周期是不一樣的
生命周期有兩類:
- 頁面生命周期,即被@Entry 裝飾的組件生命周期,有以下生命周期接口:
onPageShow:頁面每次顯示時觸發(fā)一次,包括路由過程、應用進入前臺等場景。
onPageHide:頁面每次隱藏時觸發(fā)一次,包括路由過程、應用進入后臺等場景。
onBackPress:當用戶點擊返回按鈕時觸發(fā)。
- 組件生命周期,即一般用@Component裝飾的自定義組件的生命周期,有以下生命周期接口:
aboutToAppear:組件即將出現(xiàn)時回調該接口,具體時機為在創(chuàng)建自定義組件的新實例后,在執(zhí)行其build()函數(shù)之前執(zhí)行。
onDidBuild:組件build()函數(shù)執(zhí)行完成之后回調該接口,開發(fā)者可以在這個階段進行埋點數(shù)據(jù)上報等不影響實際UI的功能。
aboutToDisappear:aboutToDisappear函數(shù)在自定義組件析構銷毀之前執(zhí)行。
頁面入口組件:同時被@Entry @Component修飾的頁面,擁有頁面級和組件級雙重生命周期,即上面的生命周期都能走到
普通自定義組件:僅被@Component修飾的頁面,僅包含組件級生命周期,普通自定義組件無法使用 onPageShow/onPageHide/onBackPress 等頁面級函數(shù),即便是代碼里都能實現(xiàn)生命周期函數(shù),系統(tǒng)也不會回調
適配多尺寸
隨著手機形態(tài)和尺寸越來越多,不知道大家有沒有覺得設備條件判斷越來越復雜了。
設計原理:
為了提升用戶的全場景體驗,需要考慮多設備體驗的連續(xù)性
原則一:兩個寬度相近的窗口,頁面布局應相同。
原則二:高度相對寬度較小的窗口,呈現(xiàn)橫向窗口或類方型窗口時,頁面布局進行差異化設計。
設計方案:
橫向和縱向維度分別代表窗口的不同特征,作為判斷頁面布局和交互體驗的條件:
橫向斷點以窗口寬度值區(qū)分,代表窗口寬度,單位是vp
縱向斷點以窗口高寬比區(qū)分,代表窗口相對高度,可以表示橫向、方型或縱向窗口。

橫向維度以應用窗口寬度為判斷條件,推薦按照不同的閾值分成5個區(qū)間:

縱向維度以應用窗口的高寬比為判斷條件,推薦按照不同的閾值分成3個區(qū)間:

可以解決目前一些痛點問題:
·設備判斷條件問題:
這種方法判斷與設備類型無關,不需要判斷機型,可減少判斷條件。比如目前你想修改輸入法面板上內容,需要多個判斷,比如是否是折疊屏,是否是展開態(tài),是否是小折,是否橫豎屏等等,非常麻煩;
設備無關的話,同一套代碼,遷移到不同廠商就會簡單一些,因為屏幕分辨率接口是一樣的;
·布局/皮膚復用問題:
同一個維度區(qū)域內的設備可以共用一套皮膚,減少皮膚數(shù)量;未來新增設備如果落在已適配的方格區(qū)域內,則不需要額外適配。比如現(xiàn)在適配了華為新出的闊折疊,如果未來再出小折就不需要再額外適配
·懸浮鍵盤縮放問題:
目前懸浮鍵盤的縮放邏輯,自由縮放就會帶來很多顯示問題,找一個統(tǒng)一的縮放算法很難,可以通過這個方案去解決,懸浮鍵盤調節(jié)后,用寬度和高寬比唯一確定一個形態(tài),每個方格區(qū)域的懸浮形態(tài)使用同一個固定縮放值。
輸入法高度限制
和Android輸入法不同,Android端輸入法Dialog可以占滿全屏,但在鴻蒙系統(tǒng)中,輸入法高度最高不能超過屏幕高度70%,輸入法進程顯示的內容也不能超過向系統(tǒng)申請的高度,比如隱私彈框等彈框的高度都不能超過輸入法申請的高度
輸入法生命周期
import { Want } from '@kit.AbilityKit';
import keyboardController from './model/KeyboardController';
import { InputMethodExtensionAbility } from '@kit.IMEKit';
export default class InputDemoService extends InputMethodExtensionAbility {
onCreate(want: Want): void {
keyboardController.onCreate(this.context); // 初始化窗口并注冊對輸入法框架的事件監(jiān)聽
}
onDestroy(): void {
console.log("onDestroy.");
keyboardController.onDestroy(); // 銷毀窗口并去注冊事件監(jiān)聽
}
}
// 調用輸入法框架的getInputMethodAbility方法獲取實例,并由此實例調用輸入法框架功能接口
const inputMethodAbility: inputMethodEngine.InputMethodAbility = inputMethodEngine.getInputMethodAbility();
export class KeyboardController {
private mContext: InputMethodExtensionContext | undefined = undefined; // 保存InputMethodExtensionAbility中的context屬性
private panel: inputMethodEngine.Panel | undefined = undefined;
private textInputClient: inputMethodEngine.InputClient | undefined = undefined;
private keyboardController: inputMethodEngine.KeyboardController | undefined = undefined;
constructor() {
}
public onCreate(context: InputMethodExtensionContext): void
{
this.mContext = context;
this.initWindow(); // 初始化窗口
this.registerListener(); // 注冊對輸入法框架的事件監(jiān)聽
}
public onDestroy(): void // 應用生命周期銷毀
{
this.unRegisterListener(); // 去注冊事件監(jiān)聽
if(this.panel) { // 銷毀窗口
inputMethodAbility.destroyPanel(this.panel);
}
if(this.mContext) {
this.mContext.destroy();
}
}
public insertText(text: string): void {
if(this.textInputClient) {
this.textInputClient.insertText(text);
}
}
public deleteForward(length: number): void {
if(this.textInputClient) {
this.textInputClient.deleteForward(length);
}
}
private initWindow(): void // 初始化窗口
{
if(this.mContext === undefined) {
return;
}
let dis = display.getDefaultDisplaySync();
let dWidth = dis.width;
let dHeight = dis.height;
let keyHeightRate = 0.47;
let keyHeight = dHeight * keyHeightRate;
let nonBarPosition = dHeight - keyHeight;
let panelInfo: inputMethodEngine.PanelInfo = {
type: inputMethodEngine.PanelType.SOFT_KEYBOARD,
flag: inputMethodEngine.PanelFlag.FLG_FIXED
};
inputMethodAbility.createPanel(this.mContext, panelInfo).then(async (inputPanel: inputMethodEngine.Panel) => {
this.panel = inputPanel;
if(this.panel) {
await this.panel.resize(dWidth, keyHeight);
await this.panel.moveTo(0, nonBarPosition);
await this.panel.setUiContent('InputMethodExtensionAbility/pages/Index');
}
});
}
private registerListener(): void
{
this.registerInputListener(); // 注冊對輸入法框架服務的監(jiān)聽
// 注冊隱藏鍵盤事件監(jiān)聽等
}
private registerInputListener(): void { // 注冊對輸入法框架服務的開啟及停止事件監(jiān)聽
inputMethodAbility.on('inputStart', (kbController, textInputClient) => {
this.textInputClient = textInputClient; // 此為輸入法客戶端實例,由此調用輸入法框架提供給輸入法應用的功能接口
this.keyboardController = kbController;
})
inputMethodAbility.on('inputStop', () => {
this.onDestroy(); // 銷毀KeyboardController
});
}
private unRegisterListener(): void
{
inputMethodAbility.off('inputStart');
inputMethodAbility.off('inputStop', () => {});
}
}
const keyboardController = new KeyboardController();
export default keyboardController;
與Android輸入法框架不同,InputMethodExtensionAbility僅提供onCreate()和onDestroy()兩個生命周期方法,其他的事件(inputStart、inputStop等)需要自己去注冊監(jiān)聽。
2.數(shù)據(jù)
多進程
和Android端不同的是,鴻蒙輸入法App端和鍵盤端是不一樣的進程
鴻蒙目前不支持手動設置多進程,進程分配均由系統(tǒng)設置
通常情況下,應用中(同一Bundle名稱)的所有UIAbility均是運行在同一個獨立進程(主進程)中,如下圖中綠色部分的“Main Process”。
應用中(同一Bundle名稱)的所有同一類型ExtensionAbility均是運行在一個獨立進程中,如下圖中藍色部分的“FormExtensionAbility Process”、“InputMethodExtensionAbility Process”、其他ExtensionAbility Process。
WebView擁有獨立的渲染進程,如下圖中黃色部分的“Render Process”。

輸入法目前會存在3個進程,App端進程(UIAbility),鍵盤端進程(InputMethodExtensionAbility),換機克隆進程(BackupExtensionAbility)
進程間通信通過公共事件機制(commonEventManager) 實現(xiàn),只能通過異步消息通信,無法實現(xiàn)Android中類似AIDL的同步通信
獨立沙箱


和Android一樣,有全局的App Context,也有頁面級別的UI Context,不同的Context獲取的資源目錄不一樣:

基礎模式/完整模式
這個是輸入法鍵盤端獨有的設置,用戶可以在系統(tǒng)設置項控制,僅控制鍵盤端,App端不受影響,相當于在系統(tǒng)級別實現(xiàn)了純凈模式
基礎模式下,輸入法擴展(InputMethodExtensionAbility)進程無法拉起其他UIAbility或ExtensionAbility。
基礎模式下,輸入法擴展會受到系統(tǒng)管控,不能使用涉及訪問或泄漏用戶個人數(shù)據(jù)的各種接口,同時無法將數(shù)據(jù)傳遞出進程。管控功能包括但不限于:網(wǎng)絡、短信、電話、麥克風、定位、相機、藍牙、壁紙、支付、日歷、游戲、揚聲器、Wi-Fi、剪切板、多媒體、聯(lián)系人、公共事件、系統(tǒng)賬號、健康數(shù)據(jù)、地圖服務、推送服務、融合搜索、共享內存、分布式特性、廣告設備標識、振動等。
共享沙箱

為了方便基礎模式下,鍵盤端和App端能共享數(shù)據(jù),專門設計了共享沙箱。比如基礎模式下,設置項的開關,鍵盤端需要讀取
基礎模式下,輸入法鍵盤端對共享沙箱只讀,對輸入法鍵盤端自己的獨立沙箱可讀寫;App端可以對共享沙箱可讀寫。
共享沙箱的使用,需要向鴻蒙申請group id,并在應用的profile里面配置data-group-ids和在InputMethodExtensionAbility所在的module.json5里面配置dataGroupIds實現(xiàn)
代碼中,如果需要和基礎模式的鍵盤端共享數(shù)據(jù),需要在創(chuàng)建Preference和數(shù)據(jù)庫的時候,傳入dataGroupId,才能和基礎模式鍵盤共享數(shù)據(jù);如果需要共享文件,需要將文件放在getGroupDir目錄。
3.性能
async/await:
async/await 是 JavaScript 的語法特性,它是 ES2017(ES8)中引入的異步編程語法糖,旨在簡化基于 Promise 的異步操作,使代碼更接近同步風格
鴻蒙系統(tǒng)功能接口一般會提供兩個接口,一個是同步的,一個是異步的,這樣會讓人誤以為異步接口不會影響性能,其實async不切換線程,本質還是在同一個線程執(zhí)行
類似協(xié)程機制,僅用來調度任務,不會切換線程,如果耗時任務全部丟在主線程async函數(shù)中,同樣會導致App卡死
多線程:
為了避免主線程卡頓問題,我們還是需要多線程
與Java共享內存模型不一樣,鴻蒙使用消息傳遞模型,線程間不共享內存
并發(fā)模型的差異:
共享內存模型:

線程間內存共享,訪問共享內存時需要先獲取鎖
消息傳遞模型/Actor模型:

同一個進程的線程間不共享內存,避免了鎖競爭,但會增加內存消耗,鴻蒙限制最多同時運行的Worker子線程數(shù)量為64個,代碼上也會導致主線程的全局變量和SDK無法在子線程共享使用,需要重新初始化
