少湖说 | 科技自媒体

互联网,科技,数码,鸿蒙

本文讲述如何通过 Flutter 开发鸿蒙原生应用。整个过程结合往期文章、实战经验、流程优化,体验丝滑、无痛。

无痛搭建开发环境

为了减少疼痛,这里使用全局唯一的 Flutter 版本开发。高阶用法可以参看往期同系列文章。

硬件准备

一台 Mac,一部 Mate60 Pro,两台显示器。

鸿蒙 Flutter SDK 开发人员爱用 Mac,使用相同设备(优先选择 Arm 版 Mac,X86 也可以,系统不要太老)无忧。

审核人员爱用 Mate60 系列,选择同款无忧。

显示器两台,左侧放别人的代码,右侧是自己的,方便参考(复制粘贴)。

环境准备

下载 DeEco

进入华为开发者联盟网站,https://developer.huawei.com/consumer/cn/download/,根据电脑机型点击下载。

下载 Flutter SDK

1
2
3
4
5
# 创建目录 ~/.fvm/versions
mkdir -p ~/.fvm/versions

# 克隆 Flutter SDK
git clone https://gitee.com/harmonycommando_flutter/flutter.git custom_3.22.0

配置环境变量

打开 ~/.bash_profile,增加以下内容

1
2
3
4
5
6
7
8
9
10
11
12
# Flutter Mirror
export PUB_HOSTED_URL=https://pub.flutter-io.cn
export FLUTTER_STORAGE_BASE_URL=https://storage.flutter-io.cn

# HarmonyOS SDK
export TOOL_HOME=/Applications/DevEco-Studio.app/Contents/
export DEVECO_SDK_HOME=$TOOL_HOME/sdk # command-line-tools/sdk
export PATH=$TOOL_HOME/tools/ohpm/bin:$PATH # command-line-tools/ohpm/bin
export PATH=$TOOL_HOME/tools/hvigor/bin:$PATH # command-line-tools/hvigor/bin
export PATH=$TOOL_HOME/tools/node/bin:$PATH # command-line-tools/tool/node/bin

export PATH="~/.fvm/versions/custom_3.22.0/bin:$PATH"

配置完成以后,关闭命令行工具,并重新打开,使之生效。使用 flutter doctor 命令查看是否有 3.22.0-ohos 字样。

创建项目

使用以下命令创建项目

1
flutter create --platforms ohos .

配置自动签名

使用 Deveco 打开项目,Mate60 Pro 手机连上电脑(必须)。

打开 File -> Project Structure -> Signing Configs 勾选 Automatically generate signature

点击 Apply 应用,OK 完成签名。此时可关闭 DevEco。

alt text

运行项目

  1. 使用 Flutter run 命令,或者在 IDE 中,点击运行按钮
1
2
# 查看设备编号
futter devices

出现以下类似输出

1
2
3
4
Found 3 connected devices:
FMR0224904009635 (mobile) • FMR0224904009635 • ohos-arm64 • Ohos OpenHarmony-5.0.1.115 (API 13)
macOS (desktop) • macos • darwin-x64 • macOS 14.6.1 23G93 darwin-x64
Chrome (web) • chrome • web-javascript • Google Chrome 131.0.6778.108

找到鸿蒙设备的 ID,如上所示为 FMR0224904009635。

1
2
# 运行
flutter run -d FMR0224904009635
  1. 在 IDE 中,点击运行按钮,以下是 VsCode 中的入口

确保安装了 Code Runner 插件

alt text

参考资料

在上期文章中,我们体验了无痛使用 Flutter 快速启动开发的过程,本期重点聚焦上架审核流程。

alt text

无痛打包

提前准备好生产证书。

具体步骤见“鸿蒙Flutter实战:13-鸿蒙应用打包上架流程”,在AGC平台证书栏,“新增证书”成功后,点击现在,得到一个xx.cer文件(数字证书文件);在Profile一栏,添加完之后,点击现在可以得到一个xx.p7b文件(Profile文件)。这两个文件将会在手动签名时使用。

使用最新的 IDE 自动上传功能。

DevEco Studio菜单栏,点击Build > Upload Product,登录开发者账号,进入上传界面。

alt text

选择 Generate app package and upload it to AppGallery Connect for test and publish。

无痛审核

审核实际上是一直非常重要的过程,不要以为开发完、打包上传就结束了,App Gallery 的审核十分严格,提前做好心理准备。

对于新应用,首次上架相对友好宽松,但也要明确没有明显问题,符合大致要求,不要存在侥幸心理。

名称

名字要确保唯一,提前在商城搜索,另外不要使用容易误解、太通用的的名称,如“文件管理”,“二维码生成”,建议增加品牌前缀,如有商标版权更加无痛。

图标

按规范设计,不要和系统图标相似,不要和其他应用雷同。

备案

提前对域名、App进行备案,应用名称、包名确保一一对应。

软著

使用电子软著,或者直接找中介全包,只需要提供名字等基础信息即可,全程无痛。

软著中的名称或简称要与App名称对应。

充分测试

确保充分测试,如果分发平台选择了平板,确保在平板上测试。

录制测试视频

审核人员测试很仔细,会帮你找bug,所以有测试视频也挺重要。
如果不具备平板测试条件,建议后期更新版本再勾选。上架只关注手机更无痛。

备注说明

如果应用有些特殊的设计理念,需要备注说明。不要指望审核人员能轻松理解你的意图。

测试账号

如果设计登录功能,最好提供一个可以正常使用的、甚至是有数据的测试账号,让审核人员也无痛。

提交时间

提前提交,避开周六。建议提交时间为周日-周四的每个晚上 。提交以后不要撤回,不然要重新排队。

参考资料

前言

在之前的文章现有Flutter项目支持鸿蒙II中,介绍了如果使何第三方插件,同时给出了非常多的使用案例,如
flutter_inappwebview,video_player, image_picker 等,本文将开始介绍如何集成高德地图。

整体方案

通过 MethodChannel 进行消息通信,在 Dart 侧调用原生API,在 ArkTS 侧收到相关调用后,根据参数跳转到指定页面

Dart 侧

1
2
3
4
5
static Future<dynamic> redirectNative(String url) {
return _methodChannel.invokeMethod("redirectNative", {
"url": url,
});
}

ArkTS 侧

ohos/entry/src/main/ets/entryability 创建 OhosPlugin.ets 文件,这里收到到消息后,调用 router.pushUrl 方法跳转到指定页面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
export default class OhosPlugin implements FlutterPlugin {
...
onAttachedToEngine(binding: FlutterPluginBinding): void {
this.channel.setMethodCallHandler({
onMethodCall : (call: MethodCall, result: MethodResult) => {
switch (call.method) {
case "redirectNative":
let url = String(call.argument("url"));
router.pushUrl({ url: url})
break;
default:
result.notImplemented();
break;
}
}
})
}
}

插件写好后,需要在 EntryAbility 中注册:

1
this.addPlugin(new OhosPlugin())

添加原生页面,回到 DevEco,在 pages 目录右键,创建一个空页面, 命名为 Amap

alt text

ohos/entry/oh-package.json 文件中引入高德地图SDK:

1
2
3
4
5
"dependencies": {
"@amap/amap_lbs_common": ">=1.1.0",
"@amap/amap_lbs_map3d": ">=2.1.1",
...
}

调用高德地图SDK,显示地图组件:

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
import { AMap, MapsInitializer, MapView, MapViewComponent, MapViewManager, } from '@amap/amap_lbs_map3d';
// 配置 API KEY
MapsInitializer.setApiKey("xxx");
MapViewManager.getInstance().registerMapViewCreatedCallback((mapview?: MapView, mapViewName?: string) => {
if (!mapview) {
return;
}
let mapView = mapview;
mapView.onCreate();
mapView.getMapAsync((map) => {
let aMap: AMap = map;
})
})

@Entry
@Component
struct Index {
build() {
Row() {
MapViewComponent()
.width('100%')
.height('100%')
}
}
}

调用

1
PlartformCall.redirectNative('pages/Amap');

注意事项

如果在运行时,遇到以下错误, 根据官方提醒, 需要配置 useNormalizedOHMUrl

1
ERROR: Bytecode HARs: [@amap/amap_lbs_map3d, @amap/amap_lbs_common] not supported when useNormalizedOHMUrl is not true.

打开文件 /ohos/build-profile.json5, 添加以下配置

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"app": {
"products": [
{
"buildOption": {
"strictMode": {
"useNormalizedOHMUrl": true
}
}
}
]
}
}

截图

alt text

源码

https://gitee.com/zacks/flutter-ohos-demo

参考资料

本文以同层渲染为例,介绍如何集成高德地图

完整代码见 Flutter 鸿蒙版 Demo

概述

Dart 侧

核心代码如下,通过 OhosView 来承载原生视图

1
2
3
4
5
6
OhosView(
viewType: 'com.shaohushuo.app/customView',
onPlatformViewCreated: _onPlatformViewCreated,
creationParams: const <String, dynamic>{'initParams': 'hello world'},
creationParamsCodec: const StandardMessageCodec(),
)

其中 viewType 为自定义的 ohosView 的名称,onPlatformViewCreated 为创建完成回调,creationParams 为创建时传入的参数,creationParamsCodec 为参数编码格式。

ArkTS 侧

这里面我们按照《如何使用PlatformView》中的示例操作,首先需要创建一个显示高德地图的视图,其核心代码如下:

完整文件 AmapWidgetFactory.ets

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

MapsInitializer.setApiKey("e4147e927a1f63a0acff45cecf9419b5");
MapViewManager.getInstance().registerMapViewCreatedCallback((mapview?: MapView, mapViewName?: string) => {
if (!mapview) {
return;
}
let mapView = mapview;
mapView.onCreate();
mapView.getMapAsync((map) => {
let aMap: AMap = map;
})
})

@Component
struct ButtonComponent {
@Prop params: Params
customView: AmapWidgetView = this.params.platformView as AmapWidgetView

build() {
Row() {
MapViewComponent().width('100%').height('100%')
}
}
}

接下来创建一个 AmapWidgetFactory.ets

1
2
3
4
5
6
7
8
9
10
11
12
export class AmapWidgetFactory extends PlatformViewFactory {
message: BinaryMessenger;

constructor(message: BinaryMessenger, createArgsCodes: MessageCodec<Object>) {
super(createArgsCodes);
this.message = message;
}

public create(context: common.Context, viewId: number, args: Object): PlatformView {
return new AmapWidgetView(context, viewId, args, this.message);
}
}

最终需要创建一个 AmapWidgetPlugin.ets

1
2
3
4
5
6
7
8
9
10
11
12
export class AmapWidgetPlugin implements FlutterPlugin {
getUniqueClassName(): string {
return 'AmapWidgetPlugin';
}

onAttachedToEngine(binding: FlutterPluginBinding): void {
binding.getPlatformViewRegistry()?.
registerViewFactory('com.shaohushuo.app/customView', new AmapWidgetFactory(binding.getBinaryMessenger(), StandardMessageCodec.INSTANCE));
}

onDetachedFromEngine(binding: FlutterPluginBinding): void {}
}

插件创建好之后,记得在 EntryAbility 中注册插件

1
this.addPlugin(new AmapWidgetPlugin())

需要注意的是,视图ID一定要两侧保持一致,如这里名为 ‘com.shaohushuo.app/customView’,否则无法正常显示

截图

alt text

参考资料

引言

在对插件鸿蒙化时,除了往期文章现有Flutter项目支持鸿蒙II中讲到的使用 dependency_overrides 来配置鸿蒙适配库的两种方式以外,如果三方插件本身使用了联合插件的形式,也可以通过下面这种方式来添加鸿蒙平台的实现:

1
2
3
4
5
6
7
dependencies:

image_picker: ^1.1.2
image_picker_ohos:
git:
url: "https://gitcode.com/openharmony-sig/flutter_packages.git"
path: "packages/image_picker/image_picker_ohos"

这也是一种非常优雅的方式,不用修改插件的代码,直接在项目配置中增加鸿蒙适配库即可。

什么是联合插件?

Federated plugins (联合插件) 是一种将对不同平台的支持分为单独的软件包。所以,联合插件能够使用针对 iOS、Android、Web 甚至是针对汽车 (例如在 IoT 设备上)分别使用对应的 package。除了这些好处之外,它还能够让领域专家在他们最了解的平台上扩展现有平台插件。

联合插件需要以下 package:

面向应用的 package

该 package 是用户使用插件的的直接依赖。它指定了 Flutter 应用使用的 API。

平台 package

一个或多个包含特定平台代码的 package。面向应用的 package 会调用这些平台 package—— 除非它们带有一些终端用户需要的特殊平台功能,否则它们不会包含在应用中。

平台接口 package

将面向应用的 package 与平台 package 进行整合的 package。该 package 会声明平台 package 需要实现的接口,供面向应用的 package 使用。使用单一的平台接口 package 可以确保所有平台 package 都按照各自的方法实现了统一要求的功能。

什么是未整合的联合插件?

相对的,整合的联合插件,也就是说插件在某个平台的实现,被整合进了主package,也就是”面向应用的 package”。如果插件已经整合了ohos实现,如 fluwx,则直接使用即可,无需再添加鸿蒙平台的实现。

如果插件没有整合ohos实现,如 image_picker, 则需要添加鸿蒙平台的实现。:

这种方式称作 “未整合的联合插件”, 在上面的配置中,
image_picker 是一个联合插件, 这里直接使用官方社区的最新版本,观察该插件的 pubspec.yaml 的文件,通过其结构可以发现联合插件的特点, 该插件的依赖项为:

1
2
3
4
5
dependencies:
image_picker_platform_interface: ^2.10.0
...
image_picker_android: ^0.8.7
image_picker_ios: ^0.8.8

image_picker_platform_interface 是一个抽象层,它定义了平台相关的接口,下面是各个平台的实现,通过拆分成包,以依赖的方式加载,那同样的原理,就可以再添加一条鸿蒙平台的实现包,就可以完成鸿蒙化适配, 也就是上面案例中的 image_picker_ohos

1
2
3
4
image_picker_ohos:
git:
url: "https://gitcode.com/openharmony-sig/flutter_packages.git"
path: "packages/image_picker/image_picker_ohos"

注意事项

需要注意,并非所有的插件都适合这种方式,有两种情况并不适合:

1.插件并非联合插件,也就是将所有平台的实现聚合在一个 package 中,这种建议使用 dependence_override 来覆写插件依赖

2.插件虽然是联合插件,但是鸿蒙化需要修改抽象层代码,典型的如用了 Platform.ohos 这种只有鸿蒙 Flutter SDK 才有的 API,这种也建议使用 dependence_override

引言

在前面的系列文章中,我们从搭建开发环境开始,讲到如何使用、集成第三方插件,如何将现有项目进行鸿蒙化改造,以及上架审核等内容;还以高德地图的 HarmonyOS SDK 的使用为例,
讲解了如何将高德地图集成至项目当中。

混合开发

除了使用 Flutter 工程做为主项目开发以外,还有一种常见的开发方式,即混合开发,主项目工程为 HarmonyOS 工程, Flutter 工程以模块形式存在,,以依赖方式加载到主项目工程中,最终实现混合开发 。

想必混合开发也不会陌生,因为我们也简要介绍过,鸿蒙 Flutter 项目混合开发的两种形式。

从本章开始,我们将进一步深入,从原理出发、以工程实战为主线,探索这两种混合开发方式。

混合开发的两种方式

  1. 以 Har 包的方式加载到 HarmonyOS 工程

HAR(Harmony Archive)是静态共享包,可以包含代码、C++库、资源和配置文件。通过HAR可以实现多个模块或多个工程共享ArkUI组件、资源等相关代码。

alt text

这种开发方式,就是将 Flutter 模块编译打包成 HAR 包,在原生鸿蒙项目中,以 Har 包的方式引入这个模块包,从而实现混合开发。

  1. 以源码的方式加载到 HarmonyOS 工程

从方式 1 的介绍中,可以发现,每次 Flutter 模块的更新都需要重新编译成 Har 包,并且需要重新打包到原生鸿蒙工程中,非常麻烦。于是就有了源码依赖的方式,也就是说,

让原生鸿蒙工程依赖 Flutter 模块的源码,这样 Flutter 代码变更时,不需要重新打包成 Har 包,而且可以在开发过程中,实现热更新,实时刷新界面。

开发流程

  1. 总目录

为了方便管理/演示,本示例的目录名为 ohos_flutter_module_demo, 我们将原生鸿蒙工程和 Flutter 模块都在这个目录下创建。

  1. 创建原生鸿蒙工程

这个也就是宿主工程,这里我们使用 DevEco Studio,在 ohos_flutter_module_demo 目录下面, 创建一个原生鸿蒙工程,本文中工程名命名为 ohos_app。

  1. 创建 Flutter 模块

这个流程都一样,我们可以使用命令以下命令创建一个 Flutter 模块:

1
flutter create --template=module my_flutter_module

最终项目目录结构如下:

1
2
3
ohos_flutter_module_demo
├── my_flutter_module
├── ohos_app

这样,为了方便维护,Flutter 模块在宿主项目外部创建,与宿主项目同级目录。本文中,上级目录为 ohos_flutter_module_demo, 其下有两个子目录,分别是 ohos_app (宿主项目),和 my_flutter_module (Flutter 模块)。

  1. 编译 Flutter 模块

如何使用 Har 包模式,需要先将 Flutter 模块编译成 Har 包;如何使用源码依赖的方式,则不需要这一步。

使用以下命令将 Flutter 模块编译成 Har 包:

1
flutter build har --debug
  1. 配置原生鸿蒙工程

如果是通过 Har 包模式,则可将 Har 包添加至依赖文件中:

首先先构建出的 Har 包复制到 ohos 鸿蒙工程中:

1
cp -r my_flutter_module/.ohos/har/* ohos_app/har/
1
2
3
4
5
6
7
"dependencies": {
"@ohos/flutter_module": "file:har/flutter_module.har",
"@ohos/flutter_ohos": "file:har/flutter.har"
},
"overrides" {
"@ohos/flutter_ohos": "file:har/flutter.har",
}

如果是通过源码模式,则需将 Flutter 模块的源码添加至依赖文件中:

1
2
3
"dependencies": {
"@ohos/flutter_module": "../flutter_module"
}
  1. 修改入口文件(可选)
    修改入口文件, 将 Flutter 模块生成的 .ohos目录中的 EntryAbility.ets 和 Index.ets 文件复制到宿主工程中进行替换
1
2
cp my_flutter_module/.ohos/entry/src/main/ets/entryability/EntryAbility.ets ohos_app/entry/src/main/ets/entryability/EntryAbility.ets
cp my_flutter_module/.ohos/entry/src/main/ets/pages/Index.ets ohos_app/entry/src/main/ets/pages/Index.ets

创建工作

创建一个根目录

1
mkdir ohos_flutter_module_demo

这个目录用于存放 flutter 项目和鸿蒙项目。

创建 Flutter 模块

首先创建一个 Flutter 模块,我们选择与 ohos_app 项目同级目录

1
flutter create --template=module my_flutter_module

如果使用了 fvm,首先确定当前目录使用的 flutter 版本为鸿蒙的 SDK 版本,如可以使用 fvm use custom_3.22.0设置,然后在 flutter 命令前加上 fvm,上面的命令也就变成了 fvm flutter create --template=module my_flutter_module

命令行出现以下输出:

1
2
3
4
5
6
7
8
Creating project my_flutter_module...
Resolving dependencies in `my_flutter_module`...
Downloading packages...
Got dependencies in `my_flutter_module`.
Wrote 12 files.

All done!
Your module code is in my_flutter_module/lib/main.dart.

创建 Flutter 模块成功之后,目录结构如下:

alt text

创建 DevEco 工程

使用 DevEco 在 ohos_flutter_module_demo 目录下,新建一个名为 ohos_app 的工程。

alt text

注意保存的目录为 xxxx/ohos_flutter_module_demo/ohos_app

创建成功后,整个目录结构如下:

alt text

可以看到,我们将 Flutter 模块放在了与 ohos_app 项目同级。my_flutter_module 中自动创建了 .ohos 目录, 这也是一个简单的鸿蒙项目,不过会包含一个名为 flutter_module 的模块。

将 Flutter 模块打包成 Har 包

接下来,我们使用 flutter build har 命令将 Flutter 模块打包成 Har 包。

打包前首先配置签名,用 DevEco 打开 .ohos 目录,然后对项目签名,操作如下:

1
DevEco Studio 打开 my_flutter_module/.ohos 工程后配置调试签名(File -> Project Structure -> Signing Configs 勾选 Automatically generate signature)
1
flutter build har --debug

命令行出现以下输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Running Hvigor task assembleHar...                                 47.5s

Consuming the Module
1. Open <host project>/oh-package.json5
2. Add flutter_module to the dependencies list:

"dependencies": {
"@ohos/flutter_module": "file:path/to/har/flutter_module.har"
}

3. Override flutter and plugins dependencies:

"overrides" {
"@ohos/flutter_ohos": "file:path/to/har/flutter.har",
}

观察目录 my_flutter_module/.ohos/har 目录,可以看到 Flutter 模块的 Har 包已经生成了, 里面生成了两个文件,分别是 flutter_module.har 和 flutter.har。

注意,生成的 flutter_module.har 是默认名称,与项目名无关。如何想要修改生成的名称,可在 my_flutter_module/.ohos/flutter_module/oh-package.json5 文件中修改包名。

alt text

引入 Har 包到 ohos 项目中

接下来,我们将生成的 har 包复制到宿主项目 ohos 中,然后回到 ohos 项目工程,将上面生成的 Har 包添依赖配置中。

  1. 复制 Har 包
1
cp -r my_flutter_module/.ohos/har/* ohos/har/
  1. 编辑 ohos_app/oh-package.json5 文件:
1
2
3
4
5
6
7
"dependencies": {
"@ohos/flutter_module": "file:har/my_flutter_module.har",
"@ohos/flutter_ohos": "file:har/my_flutter.har"
},
"overrides" {
"@ohos/flutter_ohos": "file:har/flutter.har",
}

注意:如何不想使用复制Har包的方式,也可以通过相对路径直接引入原Har的位置,可使用以下方式引入:

1
2
3
4
5
6
7
"dependencies": {
"@ohos/flutter_module": "file:../my_flutter_module/.ohos/har/flutter_module.har",
"@ohos/flutter_ohos": "file:../my_flutter_module/.ohos/har/flutter.har"
},
"overrides": {
"@ohos/flutter_ohos": "file:../my_flutter_module/.ohos/har/flutter.har"
},

这里需要配置 overrides ,为了解决依赖冲突问题,因为 @ohos/flutter_module依赖了 @ohos/flutter_ohos, 但因为使用的是相对目录,会导致加载失败,故这里通过 overrides 来重新指定 @ohos/flutter_ohos 的路径。

另外,与上文提示或者官方文档中不同的是,我们在 dependencies 也添加了 @ohos/flutter_ohos ,这是为了 IDE 提示的问题,不加的话会出现以下错误信息

1
Cannot find module '@ohos/flutter_ohos' or its corresponding type declarations. <ArkTSCheck>

最后, 再次对 ohos 项目签名,并运行 DevEco 项目。

接下来

现在我们只是将 Har 包引入到 ohos 项目中,在接下来的文章 跳转Flutter页面中,我们将介绍如何在 ohos 原生项目中,初始化 Flutter 引擎,并在合适的地方跳转打开 Flutter 页面。

总结

  1. 这种模式适合较大的项目团队,常见的场景是,负责 Flutter 开发的同事开发好指定的模块,以 Har 包的形式交付给鸿蒙原生的开发团队。

  2. 在这种模式下,鸿蒙原生的开发团队,不需要太多关注 Flutter 部分的内容,甚至不需要安装 Flutter 开发环境,可以更好的职责分离。

  3. 缺点,由于 Flutter 模块打包成了 Har 包,以 so 文件存在,故 Flutter 无法热重载。

参考资料

引言

在前面的文章混合开发详解-2-Har包模式引入中,我们介绍了如何将 Flutter 模块打包成 Har 包,并引入到原生鸿蒙工程中。本文中,我们将介绍如何通过源码依赖的方式,将 Flutter 模块引入到原生鸿蒙工程中。

创建工作

创建一个根目录

1
mkdir ohos_flutter_module_demo

这个目录用于存放 flutter 项目和鸿蒙项目。

创建 Flutter 模块

首先创建一个 Flutter 模块,我们选择与 ohos_app 项目同级目录

1
flutter create --template=module my_flutter_module

如果使用了 fvm,首先确定当前目录使用的 flutter 版本为鸿蒙的 SDK 版本,如可以使用 fvm use custom_3.22.0设置,然后在 flutter 命令前加上 fvm,上面的命令也就变成了 fvm flutter create --template=module my_flutter_module

命令行出现以下输出:

1
2
3
4
5
6
7
8
Creating project my_flutter_module...
Resolving dependencies in `my_flutter_module`...
Downloading packages...
Got dependencies in `my_flutter_module`.
Wrote 12 files.

All done!
Your module code is in my_flutter_module/lib/main.dart.

创建 Flutter 模块成功之后,目录结构如下:

alt text

创建 DevEco 工程

使用 DevEco 在 ohos_flutter_module_demo 目录下,新建一个名为 ohos_app 的工程。

alt text

注意保存的目录为 xxxx/ohos_flutter_module_demo/ohos_app

DevEco 工程创建好之后,顺便对项目签名,签名方式如下。

1
DevEco Studio 打开 my_flutter_module/.ohos 工程后配置调试签名(File -> Project Structure -> Signing Configs 勾选 Automatically generate signature),然后依次点击 Apply,OK。

创建成功后,整个目录结构如下:

alt text

可以看到,我们将 Flutter 模块放在了与 ohos_app 项目同级。my_flutter_module 中自动创建了 .ohos 目录, 这也是一个简单的鸿蒙项目,不过会包含一个名为 flutter_module 的模块。

配置源码依赖

.ohos软连接至主项目

由于开源鸿蒙官方文档中给出的方案并不理想,这里我们使用软连接的方案,来实现基于源码的联动开发。

正常情况下,my_flutter_module 创建成功后,会包含一个 .ohos 目录,这个目录是一个鸿蒙工程(里面包含 flutter_module 模块),它可以做为 Flutter 的宿主运行。但是这个宿主工程,并不是我们期望的 ohos_app, 两个工程没有任何关联,所以也无法联动开发。

所以我们执行以下操作:

1
2
3
4
5
6
7
8
9
# ⚠️首先需要将flutter_module复制到鸿蒙宿主工程,避免出现错误 “Error: Parse ohos module.json5 error: Error: Can not found module.json5 at”
cp -r my_flutter_module/.ohos/flutter_module ohos_app/

# 进入目录 my_flutter_module,在此处创建软连接
cd my_flutter_module
# 删除 .ohos 目录
rm -rf .ohos
# 创建软连接至鸿蒙宿主工程,根据需要更改目录名称
ln -s ../ohos_app .ohos

通过以上操作,我们将 .ohos 目录以软连接的方式,替换成了 ohos_app 鸿蒙工程,这样一来,当我们运行 Flutter 代码时,就会把 ohos_app 做为宿主,这样就实现了联动源码开发,也支持 hot reload (热重载)。

更新项目

经过上操作后,我们运行 flutter run , 让 Flutter 来自动更新项目配置

1
2
# 运行 flutter 代码,以更新鸿蒙项目目录
flutter run

查看 ohos_app/build-profile.json5 文件, 可以看到命令会自动添加模块配置:

1
2
3
4
5
6
7
  "modules": [
...
+ {
+ "name": "flutter_module",
+ "srcPath": "./flutter_module"
+ }
]

同时查看 ohos_app/har 目录,可以看到自动生成了 flutter.har 文件。

可以看到运行 flutter run时,控制台输出以下内容:

1
2
3
4
5
6
Launching lib/main.dart on FMR0224904009635 in debug mode...
start hap build...
...
Running Hvigor task assembleHap... 95.7s
✓ Built ../ohos_app/entry/build/default/outputs/default/entry-default-signed.hap.
installing hap. bundleName: com.shaohushuo.ohos_app

经过一段等待之后,我们的 App 就运行起来了,应用展示的是一个原生页面,Flutter 引擎及 Flutter 页面并没有加载,我们将在接下来的章节中,介绍这些实现。

参考资料

概述

将 Flutter 模块添加至宿主鸿蒙项目中后,接下需要实现页面跳转、消息通信等功能,本文重点介绍如何初始化 Flutter。

项目配置

添加依赖

编辑 ohos_app/oh-package.json 文件

  1. 如果通过 Har 包方式引入 Flutter 模块,则需要添加如下内容
1
2
3
4
5
6
7
"dependencies": {
"@ohos/flutter_module": "file:har/my_flutter_module.har",
"@ohos/flutter_ohos": "file:har/my_flutter.har"
},
"overrides" {
"@ohos/flutter_ohos": "file:har/flutter.har",
}
  1. 如果通过源码方式引入 Flutter 模块,则需要添加如下内容:
1
2
3
4
"dependencies": {
"@ohos/flutter_module": "./flutter_module",
"@ohos/flutter_ohos": "./har/flutter.har"
},

Flutter 引擎初始化

编辑 ohos_app/entry/src/main/ets/entryability/EntryAbility.ts 文件,按以下方式修改:

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
49
50
-import { AbilityConstant, ConfigurationConstant, UIAbility, Want } from '@kit.AbilityKit';
-import { hilog } from '@kit.PerformanceAnalysisKit';
-import { window } from '@kit.ArkUI';
+import { FlutterAbility, FlutterEngine } from '@ohos/flutter_ohos';
+import { GeneratedPluginRegistrant } from '@ohos/flutter_module';

-const DOMAIN = 0x0000;
-
-export default class EntryAbility extends UIAbility {
- onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
- this.context.getApplicationContext().setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET);
- hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onCreate');
- }
-
- onDestroy(): void {
- hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onDestroy');
- }
-
- onWindowStageCreate(windowStage: window.WindowStage): void {
- // Main window is created, set main page for this ability
- hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onWindowStageCreate');
-
- windowStage.loadContent('pages/Index', (err) => {
- if (err.code) {
- hilog.error(DOMAIN, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err));
- return;
- }
- hilog.info(DOMAIN, 'testTag', 'Succeeded in loading the content.');
- });
- }
-
- onWindowStageDestroy(): void {
- // Main window is destroyed, release UI related resources
- hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onWindowStageDestroy');
- }
-
- onForeground(): void {
- // Ability has brought to foreground
- hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onForeground');
- }
-
- onBackground(): void {
- // Ability has back to background
- hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onBackground');
+export default class EntryAbility extends FlutterAbility {
+ configureFlutterEngine(flutterEngine: FlutterEngine) {
+ super.configureFlutterEngine(flutterEngine)
+ GeneratedPluginRegistrant.registerWith(flutterEngine);
}
}

最终 EntryAbility.ts 文件内容如下:

1
2
3
4
5
6
7
8
9
import { FlutterAbility, FlutterEngine } from '@ohos/flutter_ohos';
import { GeneratedPluginRegistrant } from '@ohos/flutter_module';

export default class EntryAbility extends FlutterAbility {
configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
GeneratedPluginRegistrant.registerWith(flutterEngine);
}
}

EntryAbility 继承自 FlutterAbility,而 FlutterAbility 继承自 UIAbility, 它在 UIAbility 上增加了以下功能:

  1. 引擎管理
  • 初始化Flutter引擎(FlutterEngine)
  • 通过Delegate处理Flutter与原生能力绑定
  • 管理窗口生命周期(create/destroy)
  1. UI交互
  • 创建FlutterView视图容器
  • 处理系统配置变化(深色模式/字体缩放)
  • 实现多语言/无障碍服务适配
  1. 生命周期协调
  • 转发原生生命周期事件到Flutter层(onForeground/onBackground)
  • 处理异常恢复(appRecovery.restartApp)
  1. 扩展支持
  • 提供插件管理接口(addPlugin)
  • 支持热重载配置同步(onConfigurationUpdate)

总结

本节主要介绍了如何初始化 Flutter 引擎,以及 初始化 Flutter Module。下一节我们将介绍如何由原生跳转至 Flutter 并展示界面。

参考资料

概述

在上一章中,我们介绍了如何初始化 Flutter 引擎,本文重点介绍如何添加并跳转至 Flutter 页面。

跳转原理

跳转原理如下:

本质上是从一个原生页面A 跳转至另一个原生页面 B,不过区别在于,页面 B是一个页面容器,内嵌了 Flutter 内容。
同时当打开页面 B 之前,我们需要通知 Flutter 提前切换页面,这里使用了 Flutter 提供的通信机制,也就是 EventChannel。

添加 FlutterPage

为了显示 Flutter 内容,我们需要创建一个原生页面,作为承载 Flutter 的容器。

entry/src/main/etc/pages 目录下添加一个页面, 例如名称为 FlutterContainerPage, 鼠标右键点击 ohos/entry/src/main/ets/pages 目录,依次选择 New->Page->Empty Page 修改 PageName 为 FlutterContainerPage, 点击 Finish, 随后修改页面内容如下:

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
import { FlutterEntry, FlutterPage, FlutterView } from '@ohos/flutter_ohos'

@Entry
@Component
struct Index {

private flutterEntry?: FlutterEntry;
private flutterView?: FlutterView;

aboutToAppear() {
this.flutterEntry = new FlutterEntry(getContext(this));
this.flutterEntry.aboutToAppear();
this.flutterView = this.flutterEntry.getFlutterView();
}

aboutToDisappear() {
this.flutterEntry?.aboutToDisappear();
}

onPageShow() {
this.flutterEntry?.onPageShow();
}

onPageHide() {
this.flutterEntry?.onPageHide();
}

build() {
RelativeContainer() {
FlutterPage({ viewId: this.flutterView?.getId()})
}
}
}

FlutterPage 是 OpenHarmony Flutter SDK 提供的一个组件,用于在 ArkUI中渲染 Flutter 页面。其原理是使用了 ArkUI 中的 XComponent 来自定义渲染内容。

修改原生页面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { router } from '@kit.ArkUI';

@Entry
@Component
struct Index {
build() {
Column() {
Text('Hello World').fontSize('50fp').fontWeight(FontWeight.Bold)
Blank().height(80)
Button('跳转Flutter').onClick(() => {
router.pushUrl({ url: 'pages/FlutterContainerPage'})
})
}.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
.width('100%')
.height('100%')
}
}

我们在原生页面处添加一个按钮,点击按钮时跳转至 Flutter 页面

实现动态跳转

经过上面的步骤,我们已经成功跳转至 Flutter 页面,但是跳转的页面是固定的,如何实现动态跳转呢?

我们可以在打开新页面之前,向 Flutter 发送事件,当 Flutter 收到事件时,提前切换页面,然后我们打开这个承载Flutter的原生页面。

  1. 创建一个 Flutter 跳转事件,并添加到 EventChannel 中

0%