鸿蒙应用开发初试
一、简介
- 鸿蒙 (HarmonyOS) 是一款由华为开发的,面向全场景的分布式操作系统。其开源项目为 OpenHarmony。
- 超级小程序
- H5 -> 小程序 -> 超级小程序
- 可剪裁系统
- 128 KB – 128 MB – 4 GB
- 模改通讯协议
- 类似普通话,统一了方言。鸿蒙成为 IoT 互联互通的标准语言
发布会现场
二、储备知识
- 熟悉前端技术栈(HTML、CSS、JS)
- 类 Web 范式编程(鸿蒙开发不是浏览器环境)
- 例如:鸿蒙中的 div 是自己封装的,不是 Web 端的 div
- 熟悉微信小程序
- 页面结构、API、配置方式
- 熟悉 Vue 2
- 鸿蒙自己实现的类 Vue 2 的 MVVM 模式(观察者模式,数据劫持)
- 有安卓开发经验更好
三、鸿蒙系统架构
鸿蒙系统架构
- 应用层
- 鸿蒙的应用由一个或多个 FA(Feature Ability)或 PA(Particle Ability)组成
- FA 有 UI 界面,提供与用户交互的能力;而 PA 无 UI 界面,提供后台运行任务的能力
- 框架层
- 提供了 Java/C/C++/JS 等用户程序框架和 Ability 框架
- 两种 UI 框架(Java UI 框架、JS UI 框架)
- 系统服务层
- 系统服务层是鸿蒙的核心能力集合,通过框架层对应用程序提供服务
- 内核层
- 采用多内核设计,支持针对不同资源受限设备,选用适合的 OS 内核
对Ability(能力)的解释
四、鸿蒙是不是安卓套壳
我们对比一下鸿蒙和安卓的架构
安卓系统
鸿蒙系统
对比
五、鸿蒙应用开发
在线体验:https://playground.harmonyos.com/
开发环境搭建(DevEco Studio)
- 版本
- DevEco Studio 1.0(for EMUI)
- DevEco Studio 2.0(for HarmonyOS)
- 下载
- https://developer.harmonyos.com/cn/develop/deveco-studio#download
- 安装
- 确保安装过程中有网络环境
- 初始化项目
- 了解 JS UI 的基本语法
- JS SDK
- 了解 JS UI 的源码实现
鸿蒙js
- JS UI 框架
- 类 Web 范式编程的 UI 界面展示
- https://gitee.com/openharmony/ace_ace_engine
- JS 应用开发框架
- 轻量级的 MVVM 实现(仿 Vue 2)
- https://gitee.com/openharmony/ace_engine_lite
- JS 原生模块(NAPI)
- 实现 JS 与 C/C++ 代码互相访问
- https://gitee.com/openharmony/ace_napi
- 应用层
- 开发者使用 JS UI 框架开发的 FA 应用
- 前端框架层
- 完成前端页面解析,提供 MVVM 开发模式、页面路由和自定义组件等能力
- 引擎层
- 完成动画解析、DOM 树构建、布局计算、渲染命令构建与绘制、事件管理等
- 适配层
- 完成对平台层的对接,比如:事件对接、渲染管线对接和系统生命周期对接等
- JS Data binding
- JS 数据绑定框架使用 JavaScript 语言提供一套基础的数据绑定能力。
- JS runtime
- 支持 JS 代码的解析和执行。
- JS Runtime 的解析引擎为 JerryScript
- JS framework
- JS 框架部分使用 C++ 语言实现,提供 JS API 和组件的框架机制。
https://jerryscript.net/
JS 原生(NAPI)
- NativeEngine
- JS 引擎抽象层,统一 JS 引擎在 NAPI 层的接口行为。
- ModuleManager
- 管理模块,用于模块加载、模块信息缓存。
- ScopeManager
- 管理 NativeValue 的生命周期。
- ReferenceManager
- 管理 NativeReference 的生命周期。
https://github.com/quickjs-zh/QuickJS
- 数据存储
- https://developer.harmonyos.com/cn/docs/documentation/doc-references/js-apis-data-storage-0000000000626080
- 数据存储的实现
- https://gitee.com/openharmony/ace_napi/tree/master/sample/native_module_storage
六、案例
目录结构
html
<element name="fragment_main" src="../../common/fragment_main/fragment_main.hml"></element><div class="container"> <div class="top-tool-bar"> <image class="toolbar-image1" src="{{ images_resource.image_add }}" onclick="onClick"></image> <image class="toolbar-image2" src="{{ images_resource.image_add }}" onclick="onClick"></image> <image class="toolbar-image3" src="{{ images_resource.image_more }}" onclick="onClick2"></image> </div> <tabs class="tabs" index="0" vertical="false" onchange="change"> <tab-bar class="bottomBar-wrapper" mode="fixed"> <div class="bottomBar-item-wrapper"> <image class="bottomBarItem-image" src="{{ images_resource.image_icon }}"></image> <text class="bottomBarItem-text">{{ $t('strings.tab_name') }}</text> </div> <div class="bottomBar-item-wrapper"> <image class="bottomBarItem-image" src="{{ images_resource.image_icon }}"></image> <text class="bottomBarItem-text">{{ $t('strings.tab_name') }}</text> </div> <div class="bottomBar-item-wrapper"> <image class="bottomBarItem-image" src="{{ images_resource.image_icon }}"></image> <text class="bottomBarItem-text">{{ $t('strings.tab_name') }}</text> </div> <div class="bottomBar-item-wrapper"> <image class="bottomBarItem-image" src="{{ images_resource.image_icon }}"></image> <text class="bottomBarItem-text">{{ $t('strings.tab_name') }}</text> </div> <div class="bottomBar-item-wrapper"> <image class="bottomBarItem-image" src="{{ images_resource.image_icon }}"></image> <text class="bottomBarItem-text">{{ $t('strings.tab_name') }}</text> </div> </tab-bar> <tab-content class="tabContent" scrollable="true"> <div class="item-content"> <fragment_main></fragment_main> </div> <div class="item-content"> <text class="item-title">{{ $t('strings.second_page') }}</text> </div> <div class="item-content"> <text class="item-title">{{ $t('strings.third_page') }}</text> </div> <div class="item-content"> <text class="item-title">{{ $t('strings.fourth_page') }}</text> </div> <div class="item-content"> <text class="item-title">{{ $t('strings.fifth_page') }}</text> </div> </tab-content> </tabs> <div class="fragment_main_wearable"> <fragment_main id="id_fragment_main"></fragment_main> </div></div>
js
import prompt from '@system.prompt';import device from '@system.device';import mediaquery from '@system.mediaquery';const TAG = '[index]';var mMediaQueryList;var context;// js call java// abilityType: 0-Ability; 1-Internal Abilityconst ABILITY_TYPE_EXTERNAL = 0;const ABILITY_TYPE_INTERNAL = 1;// syncOption(Optional, default sync): 0-Sync; 1-Asyncconst ACTION_SYNC = 0;const ACTION_ASYNC = 1;const ACTION_MESSAGE_CODE = 1001;var wearableMediaListener = function (e) { if (e.matches) { // do something console.log(TAG + "Success Media Listen"); context.initial_value = 4; context.$child('id_fragment_main').initial_index_value = 0; context.$child('id_fragment_main').list_data.forEach(element => { element.item_icon = context.images_resource_dark_mode.image_icon; element.item_right_arrow = context.images_resource_dark_mode.image_right_arrow; }); }};var getDeviceInfo = function () { var res = ''; device.getInfo({ success: function (data) { console.log(TAG + 'Success device obtained. screenShape=' + data.screenShape); this.res = data.screenShape; }, fail: function (data, code) { console.log(TAG + 'Failed to obtain device. Error code=' + code + '; Error information: ' + data); }, }); return res;};export default { data: { images_resource: { "image_icon": "common/images/ic.png", "image_add": "common/images/add64.png", "image_more": "common/images/more.png", "image_right_arrow": "common/images/right_arrow.png" }, images_resource_dark_mode: { "image_icon": "common/images/ic_dark_mode.png", "image_add": "common/images/add64.svg", "image_more": "common/images/more.svg", "image_right_arrow": "common/images/right_arrow_dark_mode.png" }, initial_value: 0 }, onInit() { console.log(TAG + 'onInit'); context = this; this.mMediaQueryList = mediaquery.matchMedia("screen and (device-type: wearable)"); this.mMediaQueryList.addListener(wearableMediaListener); console.info(TAG + "java js" + this.getSystemColorModeByJava()); // async call and return null }, onReady() { console.log(TAG + 'onReady'); console.log(TAG + getDeviceInfo()); // getDeviceInfo after Init }, onShow() { console.log(TAG + 'onShow'); }, onDestroy() { console.log(TAG + 'onDestroy'); mMediaQueryList.removeListener(wearableMediaListener); }, onClick() { prompt.showToast({ message: "add", duration: 3000, }); }, onClick2() { prompt.showToast({ message: "more", duration: 3000, }); }, getSystemColorModeByJava: async function() { var actionData = {}; actionData.firstNum = 123; actionData.secondNum = 465; var action = {}; action.bundleName = 'com.example.myapplication'; action.abilityName = 'com.example.myapplication.ServiceAbilityForJS'; action.messageCode = ACTION_MESSAGE_CODE; action.data = actionData; action.abilityType = ABILITY_TYPE_EXTERNAL; action.syncOption = ACTION_SYNC; var result = await FeatureAbility.callAbility(action); var ret = JSON.parse(result); if (ret.code == 0) { console.info('result is:' + JSON.stringify(ret.abilityResult)); } else { console.error('error code:' + JSON.stringify(ret.code)); } if (ret.getColorMode == 0) { console.info(TAG + ret.getColorMode); // get system color mode and do something this.images_resource = this.images_resource_dark_mode; } }}
css
.container { display: flex; flex-direction: column; justify-content: flex-start; align-items: flex-start; width: 100%; left: 0px; top: 0px;}.top-tool-bar { display: flex; flex-direction: row; justify-content: flex-end; align-items: center; width: 100%; height: 56px; padding-left: 24px; padding-right: 24px;}.toolbar-image1 { width: 24px; height: 24px; margin-right: 40px; opacity: 0.9;}.toolbar-image2 { width: 24px; height: 24px; margin-right: 40px; opacity: 0.9;}.toolbar-image3 { width: 24px; height: 24px; opacity: 0.9;}.tabs { width: 100%;}.bottomBar-wrapper { width: 100%; height: 56px; flex-direction: row; justify-content: flex-start; align-items: center; position: fixed; bottom: 0px;}.bottomBar-item-wrapper { width: 100%; height: 100%; flex-direction: column; justify-content: center; align-items: center; margin-top: 0px; margin-bottom: 8px;}.bottomBarItem-image { height: 24px; width: 24px;}.bottomBarItem-text { font-size: 14px; opacity: 0.9; margin-top: 2px;}.tabContent { width: 100%; padding-bottom: 56px;}.item-content { display: flex; flex-direction: column; justify-content: flex-start; align-items: flex-start; width: 100%;}.item-title { width: 100%; height: 100%; font-size: 18px; text-align: center;}/* wearable */@media screen and (device-type: wearable) { .top-tool-bar { display: none; } .bottomBar-wrapper { display: none; }}/* phone */@media screen and (device-type: phone) { .fragment_main_wearable { display: none; }}/* tablet */@media screen and (device-type: tablet) { .fragment_main_wearable { display: none; }}/* tv */@media screen and (device-type: tv) { .fragment_main_wearable { display: none; } .item-title { color: black; } .top-tool-bar { display: none; } .bottomBar-wrapper { display: none; }}
运行结果