鸿蒙开发指南

背景

鸿蒙系统是中国首款完全自主研发的分布式操作系统,其底层技术实现完全自主可控,摆脱了对安卓和iOS的依赖。尤其在当前国际科技竞争加剧的背景下,鸿蒙的诞生降低了我国在操作系统领域对外国技术的依赖,为国家信息安全和产业安全筑起防线。

除了移动端系统,鸿蒙电脑也将在5月份发布,鸿蒙PC端应用开发,工具、环境和移动端都是一样的,可以真正实现一次开发,多端部署,所以大家了解一下还是有价值的。

1.视图

视图单位

image.png

vp类似于Android中的dp,fp类似于Android中的sp

生命周期

UIAbility生命周期

UIAbility是一种包含UI的应用组件,主要用于和用户交互,类似于Android中的Activity

  • 如果开发者希望在任务视图中看到一个任务,建议使用“一个UIAbility+多个页面”的方式,可以避免不必要的资源加载。

  • 如果开发者希望在任务视图中看到多个任务,或者需要同时开启多个窗口,建议使用多个UIAbility实现不同的功能。

生命周期如下:

image.png

UIAbility实例创建完成之后,在进入Foreground之前,系统会创建一个WindowStage。WindowStage创建完成后会进入onWindowStageCreate()回调,可以在该回调中设置UI入口页面(windowStage.loadContent),类似于Android中Fragment的onCreateView()

image.png

页面生命周期

image.png

页面生命周期会区分组件类型,页面入口组件和普通自定义组件生命周期是不一样的

生命周期有两类:

  1. 页面生命周期,即被@Entry 装饰的组件生命周期,有以下生命周期接口:
  • onPageShow:页面每次显示时触发一次,包括路由过程、应用进入前台等场景。

  • onPageHide:页面每次隐藏时触发一次,包括路由过程、应用进入后台等场景。

  • onBackPress:当用户点击返回按钮时触发。

  1. 组件生命周期,即一般用@Component装饰的自定义组件的生命周期,有以下生命周期接口:
  • aboutToAppear:组件即将出现时回调该接口,具体时机为在创建自定义组件的新实例后,在执行其build()函数之前执行。

  • onDidBuild:组件build()函数执行完成之后回调该接口,开发者可以在这个阶段进行埋点数据上报等不影响实际UI的功能。

  • aboutToDisappear:aboutToDisappear函数在自定义组件析构销毁之前执行。

页面入口组件:同时被@Entry @Component修饰的页面,拥有页面级和组件级双重生命周期,即上面的生命周期都能走到

普通自定义组件:仅被@Component修饰的页面,仅包含组件级生命周期,普通自定义组件无法使用 onPageShow/onPageHide/onBackPress 等页面级函数,即便是代码里都能实现生命周期函数,系统也不会回调

适配多尺寸

随着手机形态和尺寸越来越多,不知道大家有没有觉得设备条件判断越来越复杂了。

设计原理:

为了提升用户的全场景体验,需要考虑多设备体验的连续性

  • 原则一:两个宽度相近的窗口,页面布局应相同。

  • 原则二:高度相对宽度较小的窗口,呈现横向窗口或类方型窗口时,页面布局进行差异化设计。

设计方案:

横向和纵向维度分别代表窗口的不同特征,作为判断页面布局和交互体验的条件:

  • 横向断点以窗口宽度值区分,代表窗口宽度,单位是vp

  • 纵向断点以窗口高宽比区分,代表窗口相对高度,可以表示横向、方型或纵向窗口。

image.png

横向维度以应用窗口宽度为判断条件,推荐按照不同的阈值分成5个区间:

image.png

纵向维度以应用窗口的高宽比为判断条件,推荐按照不同的阈值分成3个区间:

image.png

可以解决目前一些痛点问题:

·设备判断条件问题:

这种方法判断与设备类型无关,不需要判断机型,可减少判断条件。比如目前你想修改输入法面板上内容,需要多个判断,比如是否是折叠屏,是否是展开态,是否是小折,是否横竖屏等等,非常麻烦;

设备无关的话,同一套代码,迁移到不同厂商就会简单一些,因为屏幕分辨率接口是一样的;

·布局/皮肤复用问题:

同一个维度区域内的设备可以共用一套皮肤,减少皮肤数量;未来新增设备如果落在已适配的方格区域内,则不需要额外适配。比如现在适配了华为新出的阔折叠,如果未来再出小折就不需要再额外适配

·悬浮键盘缩放问题:

目前悬浮键盘的缩放逻辑,自由缩放就会带来很多显示问题,找一个统一的缩放算法很难,可以通过这个方案去解决,悬浮键盘调节后,用宽度和高宽比唯一确定一个形态,每个方格区域的悬浮形态使用同一个固定缩放值。

输入法高度限制

和Android输入法不同,Android端输入法Dialog可以占满全屏,但在鸿蒙系统中,输入法高度最高不能超过屏幕高度70%,输入法进程显示的内容也不能超过向系统申请的高度,比如隐私弹框等弹框的高度都不能超过输入法申请的高度

输入法生命周期


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); // 初始化窗口并注册对输入法框架的事件监听

 }

 onDestroy(): void {

 console.log("onDestroy.");

 keyboardController.onDestroy(); // 销毁窗口并去注册事件监听

 }

}

// 调用输入法框架的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(); // 注册对输入法框架的事件监听

 }

 public onDestroy(): void // 应用生命周期销毁

 {

 this.unRegisterListener(); // 去注册事件监听

 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(); // 注册对输入法框架服务的监听

 // 注册隐藏键盘事件监听等

 }

 private registerInputListener(): void { // 注册对输入法框架服务的开启及停止事件监听

 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等)需要自己去注册监听。

2.数据

多进程

和Android端不同的是,鸿蒙输入法App端和键盘端是不一样的进程

鸿蒙目前不支持手动设置多进程,进程分配均由系统设置

  • 通常情况下,应用中(同一Bundle名称)的所有UIAbility均是运行在同一个独立进程(主进程)中,如下图中绿色部分的“Main Process”。

  • 应用中(同一Bundle名称)的所有同一类型ExtensionAbility均是运行在一个独立进程中,如下图中蓝色部分的“FormExtensionAbility Process”、“InputMethodExtensionAbility Process”、其他ExtensionAbility Process。

  • WebView拥有独立的渲染进程,如下图中黄色部分的“Render Process”。

image.png

输入法目前会存在3个进程,App端进程(UIAbility),键盘端进程(InputMethodExtensionAbility),换机克隆进程(BackupExtensionAbility)

进程间通信通过公共事件机制(commonEventManager) 实现,只能通过异步消息通信,无法实现Android中类似AIDL的同步通信

独立沙箱

image.png
image.png

和Android一样,有全局的App Context,也有页面级别的UI Context,不同的Context获取的资源目录不一样:

image.png

基础模式/完整模式

这个是输入法键盘端独有的设置,用户可以在系统设置项控制,仅控制键盘端,App端不受影响,相当于在系统级别实现了纯净模式

  1. 基础模式下,输入法扩展(InputMethodExtensionAbility)进程无法拉起其他UIAbility或ExtensionAbility。

  2. 基础模式下,输入法扩展会受到系统管控,不能使用涉及访问或泄漏用户个人数据的各种接口,同时无法将数据传递出进程。管控功能包括但不限于:网络、短信、电话、麦克风、定位、相机、蓝牙、壁纸、支付、日历、游戏、扬声器、Wi-Fi、剪切板、多媒体、联系人、公共事件、系统账号、健康数据、地图服务、推送服务、融合搜索、共享内存、分布式特性、广告设备标识、振动等。

共享沙箱

image.png

为了方便基础模式下,键盘端和App端能共享数据,专门设计了共享沙箱。比如基础模式下,设置项的开关,键盘端需要读取

基础模式下,输入法键盘端对共享沙箱只读,对输入法键盘端自己的独立沙箱可读写;App端可以对共享沙箱可读写。

共享沙箱的使用,需要向鸿蒙申请group id,并在应用的profile里面配置data-group-ids和在InputMethodExtensionAbility所在的module.json5里面配置dataGroupIds实现

代码中,如果需要和基础模式的键盘端共享数据,需要在创建Preference和数据库的时候,传入dataGroupId,才能和基础模式键盘共享数据;如果需要共享文件,需要将文件放在getGroupDir目录。

3.性能

async/await:

async/await 是 JavaScript 的语法特性,它是 ES2017(ES8)中引入的异步编程语法糖,旨在简化基于 Promise 的异步操作,使代码更接近同步风格

鸿蒙系统功能接口一般会提供两个接口,一个是同步的,一个是异步的,这样会让人误以为异步接口不会影响性能,其实async不切换线程,本质还是在同一个线程执行

类似协程机制,仅用来调度任务,不会切换线程,如果耗时任务全部丢在主线程async函数中,同样会导致App卡死

多线程:

为了避免主线程卡顿问题,我们还是需要多线程

与Java共享内存模型不一样,鸿蒙使用消息传递模型,线程间不共享内存

并发模型的差异:

共享内存模型:

image.png

线程间内存共享,访问共享内存时需要先获取锁

消息传递模型/Actor模型:

image.png

同一个进程的线程间不共享内存,避免了锁竞争,但会增加内存消耗,鸿蒙限制最多同时运行的Worker子线程数量为64个,代码上也会导致主线程的全局变量和SDK无法在子线程共享使用,需要重新初始化

image.png
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容