鸿蒙Harmony开发初探

Posted by Guofeng Blog on December 4, 2023

一、背景

9月25日华为秋季全场景新品发布会,余承东宣布鸿蒙HarmonyOS NEXT蓄势待发,不再支持安卓应用。网易有道、同程旅行、美团、国航、阿里等公司先后宣布启动鸿蒙原生应用开发工作。

二、鸿蒙Next介绍

HarmonyOS是一款面向万物互联,全新的分布式操作系统。

1、鸿蒙Next(5.0)系统底座全栈自研,去掉了传统的AOSP代码。

2、仅支持鸿蒙内核和鸿蒙系统的应用。

3、业内人士向证券时报公司记者表示:“华为内部确实有这计划,就是明年Q1推出不兼容安卓的鸿蒙版本,但目前内部还没有下发相关通知,所以具体何时推出暂不明确

三、鸿蒙系统开发

鸿蒙系统架构

image-20231204210402419

3.1 IDE

HUAWEI DevEco Studio 基于IntelliJ IDEA Community开源版本定制开发,支持HarmonyOS和OpenHarmony应用及服务开发。

下载地址:https://developer.harmonyos.com/cn/develop/deveco-studio/

3.2 示例讲解

github地址: https://github.com/guofeng007/Harmonydemo

界面效果:

xxxxx

全局配置

image-20231204211454946

1
2
3
4
5
6
7
8
9
10
11
{
  "app": {
  // 最重要的是包名,版本号,图片和app名称
    "bundleName": "com.example.myapplication", 
    "vendor": "example",
    "versionCode": 1000000,
    "versionName": "1.0.0",
    "icon": "$media:app_icon",
    "label": "$string:app_name"
  }
}

模块配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
{
  "module": {
    "name": "entry",
    "type": "entry",
    // 模块生命周期入口 
    "srcEntry": "./ets/MyAbilityStage.ts", 
    "description": "$string:module_desc",
    // 应用入口
    "mainElement": "EntryAbility",
    "deviceTypes": [
      "phone"
    ],
    "deliveryWithInstall": true,
    "installationFree": false,
    "pages": "$profile:main_pages",
    "abilities": [
      {
      // 入口具体声明配置,参考android
        "name": "EntryAbility",
        "srcEntry": "./ets/entryability/EntryAbility.ts",
        "description": "$string:EntryAbility_desc",
        "icon": "$media:icon",
        "label": "$string:EntryAbility_label",
        "startWindowIcon": "$media:icon",
        "startWindowBackground": "$color:start_window_background",
        "exported": true,
        "skills": [
          {
            "entities": [
              "entity.system.home"
            ],
            "actions": [
              "action.system.home"
            ]
          }
        ]
      }
    ]
  }
}

APP模块全局声明周期

1
2
3
4
5
6
7
8
9
10
11
import AbilityStage from '@ohos.app.ability.AbilityStage';

export default class MyAbilityStage extends AbilityStage {
  onCreate() {
    // 应用的HAP在首次加载的时,为该Module初始化操作
  }
  onAcceptWant(want) {
    // 仅specified模式下触发
    return "MyAbilityStage";
  }
}

入口任务栈

这个比较重要,对从安卓转过来的同学来说,可以理解为一个TaskStack,在手机的多任务栏,显示为一个任务,是一个任务容器。

EntryAbility 继承自 UIAbility 并实现了其中的 onCreate 、onDestroy 、 onWindowStageCreate 、 onWindowStageDestroy 、 onForeground 、 onBackground 等方法,显然,这些方法就是这个 ability 的生命周期。 然后在 onWindowStageCreate 生命周期中通过 windowStage.loadContent 加载了 pages/Index 的内容,pages/Index就相当于我们的入口UI页面。

UIAbility介绍: UIAbility是一种包含用户界面的应用组件,主要用于和用户进行交互。UIAbility也是系统调度的单元,为应用提供窗口在其中绘制界面。 每一个UIAbility实例,都对应于一个最近任务列表中的任务。 一个应用可以有一个UIAbility,也可以有多个UIAbility。 一个UIAbility可以对应于多个页面。

img

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import UIAbility from '@ohos.app.ability.UIAbility';
import hilog from '@ohos.hilog';
import window from '@ohos.window';

export default class EntryAbility extends UIAbility {
  onCreate(want, launchParam) {
    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate');
  }

  onDestroy() {
    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onDestroy');
  }

  onWindowStageCreate(windowStage: window.WindowStage) {
    // Main window is created, set main page for this ability
    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate');

    windowStage.loadContent('pages/Index', (err, data) => {
      if (err.code) {
        hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');
        return;
      }
      hilog.info(0x0000, 'testTag', 'Succeeded in loading the content. Data: %{public}s', JSON.stringify(data) ?? '');
    });
  }

  onWindowStageDestroy() {
    // Main window is destroyed, release UI related resources
    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageDestroy');
  }

  onForeground() {
    // Ability has brought to foreground
    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onForeground');
  }

  onBackground() {
    // Ability has back to background
    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onBackground');
  }
}

路由配置

1
2
3
4
5
6
7
8
{
  "src": [
    "pages/Index",
    "pages/routes/FirstPage",
    "pages/routes/SecondPage",
    "pages/routes/WebComponent"
  ]
}

3.3 ArkUI

ArkUI的基本单元是组件,组件是一个独立子页面或者子模块。示例代码有注释,包含页面UI组件,状态,route跳转。

组件声明周期

img

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import router from '@ohos.router'

@Entry //@Entry表示该自定义组件为入口组件
@Component //@Component表示自定义组件
struct Index {
  //@State表示组件中的状态变量,状态变量变化会触发UI刷新
  @State count: number = 1
  @State url: string = "https://img1.baidu.com/it/u=3302184040,3713353210&fm=253&fmt=auto&app=138&f=JPEG?w=889&h=500"
  private product: string[] = ['PC。', "平板。", `手环。`]
  //UI描述:以声明式的方式来描述UI的结构,例如build()方法中的代码块。
  build() {
    //系统组件:ArkUI框架中默认内置的基础和容器组件,比如示例中的Row、Column、Text
    //不允许在UI描述里直接使用声明局部变量
    // let a = 1 ERROR
    Row() {
      Column() {
        Button("Page跳转")
          .onClick(()=>{
            router.pushUrl({ url: "pages/routes/FirstPage", params: {
              param: "第一个页面传来的值",
            } })
          })
        Button("Webview跳转")
          .onClick(()=>{
            router.pushUrl({ url: "pages/routes/WebComponent", params: {
              param: "第一个页面传来的值",
            } })
          })
        Text(this.count.toString())
          //属性方法:组件可以通过链式调用配置多项属性,如fontSize()、width()、height()、backgroundColor()等
          .fontWeight(FontWeight.Bold)
            //定义扩展原生组件样式
          .setCustomText(30)
          }
         }
       }
}

3.4 Webview

在原生APP组件开发过程中,很多活动页面都是h5实现,但是需要很多原生的能力,比如相机,这种情况下就需要类似JsBridge方式的WebView

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import webView from '@ohos.web.webview';

@Entry
@Component
struct WebComponent {
  controller: webView.WebviewController = new webView.WebviewController();
  webUrl: string = 'https://www.baidu.com/'
  jsBridge = {
    callNaMethod() {
      console.log("H5调用Native方法")

    }
  }

  aboutToAppear(){
    webView.WebviewController.setWebDebuggingAccess(true)
  }

  build() {
    Stack() {
      //加载网络url
      // Web({ src: this.webUrl, controller: this.controller })
      //加载本地html
      Web({ src: $rawfile("demo.html"), controller: this.controller })
        //允许访问本地文件
        .fileAccess(true)
          //设置是否允许执行JavaScript脚本
        .javaScriptAccess(true)
          //注入JavaScript对象到window对象中,并在window对象中调用该对象的方法
        .javaScriptProxy({
          object: this.jsBridge,
          name: "jsBridge",
          methodList: ["callNaMethod"],
          controller: this.controller
        })
        .onPageEnd(event => {
          //异步执行JavaScript脚本 调用H5方法
          this.controller.runJavaScript("callH5Method()")
            .then(result => {
              console.log(`H5返回值=${result}`)
            })
            .catch(error => {
              console.error("error: " + error);
            })
        })
    }
  }
}

测试html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!DOCTYPE html>

<html>

<body>




<!--调用原生方法-->
<button type="button" onclick="window.jsBridge.callNaMethod()">点击调用原生界面方法</button>
</body>

<script type="text/javascript">
  function callH5Method() {
     
      return "从H5返回"
  }
</script>
</html>

3.5 Devtools调试webview

  1. 代码中允许webview调试 web_webview.WebviewController.setWebDebuggingAccess(true);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// xxx.ets
import web_webview from '@ohos.web.webview';

@Entry
@Component
struct WebComponent {
  controller: web_webview.WebviewController = new web_webview.WebviewController();
  aboutToAppear() {
    // 配置web开启调试模式
    web_webview.WebviewController.setWebDebuggingAccess(true);
  }
  build() {
    Column() {
      Web({ src: 'www.example.com', controller: this.controller })
    }
  }
}
  1. 用hdc命令行工具

(需要全局设置path /Users/你的用户名/Library/Huawei/Sdk/hmscore/3.1.0/toolchains/)

1
2
3
4
5
6
7
// 添加映射 

hdc fport tcp:9222 tcp:9222 

// 查看映射 

hdc fport ls
  1. 在PC端chrome浏览器地址栏中输入chrome://inspect/#devices,页面识别到设备后,就可以开始页面调试。调试效果如下:

image-20231205135433565

总结

鸿蒙整体上开发很像Kotlin Compose,也借鉴了很多大前端比如Vue的响应式编程理念,有以上经验的很容易上手。