
欢迎大家加入开源鸿蒙跨平台开发者社区,一起共建开源鸿蒙跨平台生态。
📌 概述
在 HarmonyOS Next 生态中,Cordova 框架提供了一种优雅的方式来构建跨平台应用。本文将深入探讨如何设计和实现一个完整的 HarmonyOS Cordova 混合应用架构,包括从原生层到 Web 层的完整链路。
🔗 完整的链路流程
1. 应用启动流程
当用户启动应用时,HarmonyOS 系统会加载应用进程,执行 EntryAbility 的 onCreate 生命周期,然后加载 Index.ets 页面。Index.ets 初始化 Cordova 的 MainPage 组件,该组件负责创建 ArkWeb 容器并加载 rawfile/www/index.html。HTML 文件加载完成后,JavaScript 模块依次初始化数据库、业务逻辑和 UI 管理器,最后应用就绪并显示主界面。
2. 原生与 Web 通信流程
Web 层通过 cordova.exec() 调用原生功能。该方法接收成功回调、错误回调、插件名称、方法名称和参数数组。Cordova 框架将请求通过通信桥接转发给原生层,原生层执行相应功能后,通过 PluginResult 将结果返回给 JavaScript 的回调函数。这种机制实现了 Web 和原生的无缝通信。
📂 项目目录结构
项目采用模块化结构,分为 AppScope(应用全局配置)、entry(应用入口模块)和 cordova(Cordova 框架模块)三个主要部分。AppScope 包含应用配置和资源文件。entry 模块包含 EntryAbility.ets(应用入口)和 Index.ets(主页面)。cordova 模块包含 Cordova 框架代码和 Web 应用资源,Web 应用代码位于 rawfile/www 目录,包括 index.html、css 和 js 子目录。
🔧 核心组件详解
1. EntryAbility - 应用入口
EntryAbility 是 HarmonyOS 应用的入口点,负责应用的生命周期管理。
typescript
import { UIAbility, Want, AbilityConstant } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { window } from '@kit.ArkUI';
const TAG: string = 'EntryAbility';
const DOMAIN_NUMBER: number = 0xFF00;
export default class EntryAbility extends UIAbility {
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
hilog.info(DOMAIN_NUMBER, TAG, '%{public}s', 'Ability onCreate');
}
onDestroy(): void {
hilog.info(DOMAIN_NUMBER, TAG, '%{public}s', 'Ability onDestroy');
}
onWindowStageCreate(windowStage: window.WindowStage): void {
// 主窗口创建,加载 Index.ets 页面
hilog.info(DOMAIN_NUMBER, TAG, '%{public}s', 'Ability onWindowStageCreate');
windowStage.loadContent('pages/Index', (err) => {
if (err.code) {
hilog.error(DOMAIN_NUMBER, TAG, 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');
return;
}
hilog.info(DOMAIN_NUMBER, TAG, 'Succeeded in loading the content.');
});
}
onWindowStageDestroy(): void {
// 主窗口销毁
hilog.info(DOMAIN_NUMBER, TAG, '%{public}s', 'Ability onWindowStageDestroy');
}
onForeground(): void {
// 应用进入前台
hilog.info(DOMAIN_NUMBER, TAG, '%{public}s', 'Ability onForeground');
}
onBackground(): void {
// 应用进入后台
hilog.info(DOMAIN_NUMBER, TAG, '%{public}s', 'Ability onBackground');
}
}
代码解释:
EntryAbility 类继承自 UIAbility,是 HarmonyOS 应用的入口点。onCreate() 方法在应用创建时调用,用于初始化应用状态。onWindowStageCreate() 方法在窗口舞台创建时调用,这是加载主页面的关键时刻,通过 windowStage.loadContent() 方法加载 Index.ets 页面。loadContent() 的第一个参数是页面路径,第二个参数是回调函数,用于处理加载成功或失败的情况。onForeground() 在应用进入前台时调用,onBackground() 在应用进入后台时调用,这两个方法可以用于管理应用的资源和状态。
2. Index.ets - 主页面容器
Index.ets 是应用的主页面,负责初始化 Cordova 框架和加载 Web 应用。
typescript
import {
MainPage,
pageBackPress,
pageHideEvent,
pageShowEvent,
PluginEntry
} from '@magongshou/harmony-cordova/Index';
@Entry
@Component
struct Index {
/**
* Cordova 插件配置
* 如果需要自定义原生插件,在这里配置
*/
cordovaPlugs: Array<PluginEntry> = [];
/**
* 页面显示生命周期
* 当页面从隐藏状态变为显示状态时调用
*/
onPageShow() {
pageShowEvent();
}
/**
* 返回键拦截
* 返回 true 表示拦截返回键,由 Cordova 处理
*/
onBackPress() {
pageBackPress();
return true;
}
/**
* 页面隐藏生命周期
* 当页面从显示状态变为隐藏状态时调用
*/
onPageHide() {
pageHideEvent();
}
/**
* 构建页面 UI
* 使用 MainPage 组件加载 Cordova 应用
*/
build() {
RelativeContainer() {
MainPage({
isWebDebug: false, // 是否开启 Web 调试
cordovaPlugs: this.cordovaPlugs // 传入插件配置
});
}
.height('100%')
.width('100%')
}
}
代码解释:
@Entry 和 @Component 是 ArkTS 的装饰器,标记这是应用的入口页面。Index 结构体定义了页面的逻辑和 UI。cordovaPlugs 是一个数组,用于配置自定义的原生插件。onPageShow() 方法在页面显示时调用,可以用于初始化页面状态。onBackPress() 方法拦截返回键事件,返回 true 表示由应用处理返回逻辑,而不是系统默认行为。onPageHide() 方法在页面隐藏时调用,可以用于清理资源。build() 方法定义了页面的 UI 结构,使用 RelativeContainer 作为根容器,然后在其中放置 MainPage 组件。MainPage 是由 Cordova 框架提供的组件,负责加载和管理 Web 应用。isWebDebug 参数控制是否开启 Web 调试模式,开发时可以设为 true 以便调试。
3. MainPage - Cordova 容器
MainPage 是由 Cordova 框架提供的组件,它负责初始化 ArkWeb 容器并加载 Web 应用。
typescript
// MainPage 的初始化过程(框架内部实现)
// 1. 创建 ArkWeb 实例
// 2. 配置 ArkWeb 参数
// 3. 加载 rawfile/www/index.html
// 4. 初始化 Cordova 运行时
// 5. 注册插件通信桥接
MainPage 会自动:
- 加载
rawfile/www/index.html作为应用主页面 - 初始化 Cordova 框架和运行时
- 建立 Web 和原生之间的通信桥接
- 处理页面生命周期事件
4. index.html - Web 应用入口
index.html 是 Web 应用的入口文件,包含了应用的 HTML 结构、样式和脚本引用。
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<meta http-equiv="Content-Security-Policy" content="default-src * data: gap: 'unsafe-eval' 'unsafe-inline'; style-src * 'unsafe-inline'; script-src * 'unsafe-inline' 'unsafe-eval'">
<title>待办事项</title>
<!-- Cordova 框架脚本 -->
<script src="cordova.js"></script>
<!-- 应用样式 -->
<link rel="stylesheet" href="css/ui-components.css">
<link rel="stylesheet" href="css/app.css">
</head>
<body>
<!-- 应用主容器 -->
<div id="app-container">
<!-- 侧边栏和主内容区 -->
</div>
<!-- 应用脚本 -->
<script src="js/ui-components.js"></script>
<script src="js/db.js"></script>
<script src="js/taskManager.js"></script>
<script src="js/categoryManager.js"></script>
<script src="js/uiManager.js"></script>
<script src="js/modules.js"></script>
<script src="js/app.js"></script>
</body>
</html>
代码解释:
index.html 是 Web 应用的入口文件,定义了应用的 HTML 结构。<script src="cordova.js"></script> 是必须的,它加载 Cordova 框架脚本,建立 Web 和原生层的通信桥接。Content-Security-Policy 元标签定义了内容安全策略,允许加载本地资源和执行脚本。样式和脚本的加载顺序很重要,应该先加载基础库(如 ui-components.js 和 db.js),再加载业务逻辑(如 app.js),这样可以确保依赖关系正确。app-container 是应用的主容器,所有页面内容都会被渲染到这个 div 中。
🔄 应用初始化流程
当应用启动时,以下步骤会依次执行:
javascript
// 1. Cordova 框架初始化
document.addEventListener('deviceready', function() {
console.log('Cordova 已就绪');
// 2. 初始化数据库
db.init().then(() => {
console.log('数据库初始化完成');
// 3. 初始化业务模块
taskManager.init();
categoryManager.init();
// 4. 初始化 UI
uiManager.init();
// 5. 加载初始数据
uiManager.renderTaskList();
console.log('应用初始化完成');
});
});
流程说明:
应用初始化过程从 deviceready 事件开始,这个事件表示 Cordova 框架已完全加载,Web 和原生层的通信桥接已建立。在这个事件的回调函数中,首先调用 db.init() 初始化 IndexedDB 数据库,这是一个异步操作,返回一个 Promise。当数据库初始化完成后,依次初始化任务管理器(taskManager)和分类管理器(categoryManager),这些管理器负责业务逻辑。然后初始化 UI 管理器(uiManager),它负责页面的渲染和事件处理。最后调用 uiManager.renderTaskList() 加载初始数据并渲染到 UI,这样用户就可以看到应用的主界面。整个初始化过程是按顺序执行的,确保每个模块都在其依赖的模块初始化完成后才开始初始化。
🔌 插件通信机制
Web 调用原生功能
javascript
// 使用 cordova.exec() 调用原生功能
cordova.exec(
function(success) {
// 成功回调
console.log('原生功能执行成功:', success);
},
function(error) {
// 错误回调
console.error('原生功能执行失败:', error);
},
'PluginName', // 插件名称
'methodName', // 方法名称
[arg1, arg2] // 参数数组
);
参数说明:
cordova.exec() 方法用于从 Web 层调用原生功能。第一个参数是成功回调函数,当原生方法执行成功时调用,接收原生方法返回的数据。第二个参数是错误回调函数,当原生方法执行失败时调用,接收错误信息。第三个参数是插件名称,必须与原生代码中注册的插件名称一致。第四个参数是要调用的方法名称。第五个参数是传递给原生方法的参数数组。这种机制实现了 Web 和原生的异步通信,Web 层不需要知道原生的具体实现细节,只需要调用 cordova.exec() 方法并提供回调函数来处理结果。
原生调用 Web 函数
在原生代码中,可以通过 Cordova 框架提供的接口调用 Web 层的函数。原生层可以在特定事件发生时(例如收到通知、传感器数据变化等)主动调用 Web 层的方法,将数据传递给 Web 应用。
typescript
// ArkTS 代码示例 - 原生插件调用 Web 函数
import { CordovaPlugin, CallbackContext } from '@magongshou/harmony-cordova/Index';
import { PluginResult, MessageStatus } from '@magongshou/harmony-cordova/Index';
export class NotificationPlugin extends CordovaPlugin {
// 当收到系统通知时,调用 Web 层的函数
async onNotificationReceived(callbackContext: CallbackContext, args: string[]): Promise<void> {
try {
const notificationData = {
title: args[0],
message: args[1],
timestamp: new Date().toISOString()
};
// 通过 Cordova 框架调用 Web 层的函数
// 这会触发 Web 层注册的回调函数
const result = PluginResult.createByString(
MessageStatus.OK,
JSON.stringify(notificationData)
);
callbackContext.sendPluginResult(result);
} catch (error) {
const result = PluginResult.createByString(
MessageStatus.ERROR,
(error as Error).message
);
callbackContext.sendPluginResult(result);
}
}
}
原生代码解释:
NotificationPlugin 是一个自定义的 Cordova 插件,继承自 CordovaPlugin。onNotificationReceived 方法在原生层收到系统通知时被调用。该方法接收 CallbackContext 对象和参数数组,CallbackContext 用于将结果返回给 Web 层。方法首先将通知数据封装成一个对象,包括标题、消息和时间戳。然后使用 PluginResult.createByString() 创建一个成功的结果对象,将数据序列化为 JSON 字符串。最后通过 callbackContext.sendPluginResult() 将结果发送回 Web 层,Web 层会收到这个数据并执行相应的回调函数。如果发生错误,会创建一个错误结果对象并发送给 Web 层。
Web 层接收原生数据
Web 层可以通过注册监听器来接收来自原生层的数据:
javascript
// JavaScript 代码 - 监听原生层的通知
window.addEventListener('notification', function(event) {
const notificationData = JSON.parse(event.detail);
console.log('收到原生通知:', notificationData);
// 显示通知给用户
UIComponents.showToast(notificationData.message, {
type: 'info',
duration: 5000
});
// 更新应用状态
if (notificationData.title === 'TaskReminder') {
taskManager.handleReminder(notificationData);
}
});
Web 层代码解释:
Web 层通过监听 'notification' 事件来接收来自原生层的数据。当原生层发送通知时,这个事件会被触发,event.detail 包含了原生层发送的数据。Web 层将 JSON 字符串解析为对象,然后根据通知的类型执行相应的操作。例如,如果通知是任务提醒,会调用 taskManager.handleReminder() 方法来处理这个提醒。这种机制实现了原生层主动向 Web 层推送数据的功能。
� 总结
HarmonyOS Cordova 混合应用架构通过清晰的分层设计,将应用分为 ArkTS 原生层、Cordova 框架层和 Web 应用层。这种架构既充分利用了 Web 技术的开发效率,又能够灵活地调用原生能力。通过 Cordova 的 exec 接口,Web 和原生层可以无缝通信,实现了高效的跨平台开发。