Electron+鸿蒙桌面应用实战:跨平台开发完全指南
本文详细介绍如何使用Electron框架开发鸿蒙桌面应用,从环境搭建到上架发布,包含完整可运行代码示例。
前言
随着鸿蒙生态的快速发展,越来越多的开发者开始关注鸿蒙桌面应用的开发。虽然鸿蒙原生支持ArkTS/JS开发,但在某些场景下,我们可能需要:
- 复用现有的Web技术栈
- 快速迁移现有Electron应用
- 利用Electron丰富的生态库
本文将手把手教你使用Electron开发鸿蒙桌面应用,实现一套代码同时运行在Windows、macOS、Linux和鸿蒙桌面系统上。
一、环境准备
1.1 系统环境要求
开发环境:
- Node.js 16.x 或更高版本
- npm 8.x 或更高版本
- Electron 25.x 或更高版本
- 鸿蒙DevEco Studio 3.1+(用于测试)
目标平台:
- 鸿蒙OS 3.0+ 桌面版
- Windows 10+
- macOS 10.15+
- Linux Ubuntu 18.04+
1.2 项目初始化
bash
# 创建项目目录
mkdir electron-harmonyos-demo
cd electron-harmonyos-demo
# 初始化package.json
npm init -y
# 安装Electron
npm install electron --save-dev
# 安装必要依赖
npm install electron-builder --save-dev
npm install electron-packager --save-dev
1.3 项目结构设计
electron-harmonyos-demo/
├── src/
│ ├── main/ # 主进程代码
│ │ └── main.js
│ ├── renderer/ # 渲染进程代码
│ │ ├── index.html
│ │ ├── renderer.js
│ │ └── style.css
│ └── preload/ # 预加载脚本
│ └── preload.js
├── assets/ # 静态资源
│ ├── icons/
│ └── images/
├── dist/ # 打包输出目录
├── package.json
└── electron-builder.yml # 打包配置文件
二、主进程开发
2.1 主进程入口文件
创建 src/main/main.js:
javascript
const { app, BrowserWindow, ipcMain, Tray, Menu } = require('electron')
const path = require('path')
const { autoUpdater } = require('electron-updater')
// 保持对window对象的全局引用
let mainWindow
let tray
// 创建浏览器窗口
function createWindow() {
mainWindow = new BrowserWindow({
width: 1200,
height: 800,
minWidth: 800,
minHeight: 600,
icon: path.join(__dirname, '../../assets/icons/icon.png'),
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
preload: path.join(__dirname, '../preload/preload.js')
}
})
// 加载应用
if (process.env.NODE_ENV === 'development') {
mainWindow.loadURL('http://localhost:3000')
mainWindow.webContents.openDevTools()
} else {
mainWindow.loadFile(path.join(__dirname, '../renderer/index.html'))
}
// 窗口关闭事件
mainWindow.on('closed', function () {
mainWindow = null
})
}
// Electron初始化完成
app.whenReady().then(() => {
createWindow()
// 创建系统托盘
createTray()
// 检查更新
checkForUpdates()
app.on('activate', function () {
if (mainWindow === null) createWindow()
})
})
// 所有窗口关闭
app.on('window-all-closed', function () {
if (process.platform !== 'darwin') app.quit()
})
// 创建系统托盘
function createTray() {
tray = new Tray(path.join(__dirname, '../../assets/icons/tray.png'))
const contextMenu = Menu.buildFromTemplate([
{
label: '显示主窗口',
click: () => {
if (mainWindow) {
mainWindow.show()
mainWindow.focus()
}
}
},
{
label: '关于',
click: () => {
// 显示关于对话框
}
},
{
label: '退出',
click: () => {
app.quit()
}
}
])
tray.setToolTip('Electron鸿蒙应用')
tray.setContextMenu(contextMenu)
}
// 检查更新
function checkForUpdates() {
if (process.env.NODE_ENV === 'production') {
autoUpdater.checkForUpdatesAndNotify()
autoUpdater.on('update-available', () => {
mainWindow.webContents.send('update-available')
})
autoUpdater.on('update-downloaded', () => {
mainWindow.webContents.send('update-downloaded')
})
}
}
2.2 预加载脚本
创建 src/preload/preload.js:
javascript
const { contextBridge, ipcRenderer } = require('electron')
// 通过contextBridge暴露安全的API给渲染进程
contextBridge.exposeInMainWorld('electronAPI', {
// 窗口控制
minimize: () => ipcRenderer.invoke('window-minimize'),
maximize: () => ipcRenderer.invoke('window-maximize'),
close: () => ipcRenderer.invoke('window-close'),
// 文件系统
readFile: (path) => ipcRenderer.invoke('file-read', path),
writeFile: (path, content) => ipcRenderer.invoke('file-write', path, content),
// 系统信息
getSystemInfo: () => ipcRenderer.invoke('system-info'),
// 更新相关
onUpdateAvailable: (callback) => ipcRenderer.on('update-available', callback),
onUpdateDownloaded: (callback) => ipcRenderer.on('update-downloaded', callback),
// 鸿蒙特有API
harmonyOS: {
getDeviceInfo: () => ipcRenderer.invoke('harmony-device-info'),
shareFile: (filePath) => ipcRenderer.invoke('harmony-share', filePath)
}
})
三、渲染进程开发
3.1 HTML主页面
创建 src/renderer/index.html:
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Electron鸿蒙桌面应用</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div id="app">
<!-- 标题栏 -->
<div class="titlebar">
<div class="titlebar-title">Electron鸿蒙桌面应用</div>
<div class="titlebar-controls">
<button id="minimize-btn" class="titlebar-btn">−</button>
<button id="maximize-btn" class="titlebar-btn">□</button>
<button id="close-btn" class="titlebar-btn close">×</button>
</div>
</div>
<!-- 主内容区 -->
<div class="container">
<header>
<h1>🚀 Electron + 鸿蒙开发实战</h1>
<p class="subtitle">跨平台桌面应用开发完全指南</p>
</header>
<main>
<section class="card">
<h2>系统信息</h2>
<div id="system-info" class="info-box">
加载中...
</div>
<button id="refresh-info" class="btn primary">刷新信息</button>
</section>
<section class="card">
<h2>文件操作</h2>
<div class="file-operations">
<input type="file" id="file-input" class="file-input">
<button id="read-file" class="btn">读取文件</button>
<button id="write-file" class="btn">写入文件</button>
</div>
<div id="file-content" class="file-content"></div>
</section>
<section class="card">
<h2>鸿蒙特性</h2>
<div class="harmony-features">
<button id="device-info" class="btn harmony">获取设备信息</button>
<button id="share-file" class="btn harmony">分享文件</button>
</div>
</section>
</main>
<footer>
<p>Powered by Electron & HarmonyOS | 版本 1.0.0</p>
</footer>
</div>
</div>
<script src="renderer.js"></script>
</body>
</html>
3.2 渲染进程JavaScript
创建 src/renderer/renderer.js:
javascript
document.addEventListener('DOMContentLoaded', () => {
// 窗口控制按钮
document.getElementById('minimize-btn').addEventListener('click', () => {
window.electronAPI.minimize()
})
document.getElementById('maximize-btn').addEventListener('click', () => {
window.electronAPI.maximize()
})
document.getElementById('close-btn').addEventListener('click', () => {
window.electronAPI.close()
})
// 系统信息
const loadSystemInfo = async () => {
try {
const info = await window.electronAPI.getSystemInfo()
document.getElementById('system-info').innerHTML = `
<p><strong>操作系统:</strong> ${info.platform}</p>
<p><strong>架构:</strong> ${info.arch}</p>
<p><strong>内存:</strong> ${Math.round(info.totalMemory / 1024 / 1024 / 1024)} GB</p>
<p><strong>CPU:</strong> ${info.cpus[0].model}</p>
<p><strong>鸿蒙版本:</strong> ${info.harmonyVersion || 'N/A'}</p>
`
} catch (error) {
console.error('获取系统信息失败:', error)
}
}
document.getElementById('refresh-info').addEventListener('click', loadSystemInfo)
loadSystemInfo()
// 文件操作
document.getElementById('read-file').addEventListener('click', async () => {
const filePath = document.getElementById('file-input').value
if (!filePath) {
alert('请输入文件路径')
return
}
try {
const content = await window.electronAPI.readFile(filePath)
document.getElementById('file-content').textContent = content
} catch (error) {
console.error('读取文件失败:', error)
alert('读取文件失败: ' + error.message)
}
})
document.getElementById('write-file').addEventListener('click', async () => {
const filePath = prompt('请输入保存路径:', 'output.txt')
const content = document.getElementById('file-content').textContent
if (!filePath) return
try {
await window.electronAPI.writeFile(filePath, content)
alert('文件保存成功!')
} catch (error) {
console.error('写入文件失败:', error)
alert('写入文件失败: ' + error.message)
}
})
// 鸿蒙特性
document.getElementById('device-info').addEventListener('click', async () => {
try {
const deviceInfo = await window.electronAPI.harmonyOS.getDeviceInfo()
alert(`设备信息:\n${JSON.stringify(deviceInfo, null, 2)}`)
} catch (error) {
console.error('获取设备信息失败:', error)
}
})
document.getElementById('share-file').addEventListener('click', async () => {
const filePath = prompt('请输入要分享的文件路径:', '')
if (!filePath) return
try {
await window.electronAPI.harmonyOS.shareFile(filePath)
alert('文件分享成功!')
} catch (error) {
console.error('文件分享失败:', error)
}
})
})
3.3 CSS样式
创建 src/renderer/style.css:
css
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', 'Microsoft YaHei', sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: #333;
height: 100vh;
overflow: hidden;
}
#app {
height: 100vh;
display: flex;
flex-direction: column;
}
/* 标题栏样式 */
.titlebar {
height: 32px;
background: rgba(255, 255, 255, 0.95);
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 10px;
-webkit-app-region: drag;
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
}
.titlebar-title {
font-size: 14px;
font-weight: 600;
color: #333;
}
.titlebar-controls {
display: flex;
-webkit-app-region: no-drag;
}
.titlebar-btn {
width: 46px;
height: 32px;
border: none;
background: transparent;
font-size: 16px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
}
.titlebar-btn:hover {
background: rgba(0, 0, 0, 0.1);
}
.titlebar-btn.close:hover {
background: #e74c3c;
color: white;
}
/* 主容器 */
.container {
flex: 1;
padding: 20px;
overflow-y: auto;
background: rgba(255, 255, 255, 0.9);
backdrop-filter: blur(10px);
}
header {
text-align: center;
margin-bottom: 30px;
padding-bottom: 20px;
border-bottom: 2px solid #667eea;
}
header h1 {
color: #333;
font-size: 2.5rem;
margin-bottom: 10px;
}
.subtitle {
color: #666;
font-size: 1.2rem;
}
/* 卡片样式 */
.card {
background: white;
border-radius: 12px;
padding: 20px;
margin-bottom: 20px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.card:hover {
transform: translateY(-2px);
box-shadow: 0 8px 12px rgba(0, 0, 0, 0.15);
}
.card h2 {
color: #667eea;
margin-bottom: 15px;
font-size: 1.5rem;
}
/* 信息框 */
.info-box {
background: #f8f9fa;
border-radius: 8px;
padding: 15px;
margin-bottom: 15px;
font-family: 'Consolas', monospace;
line-height: 1.6;
}
/* 按钮样式 */
.btn {
background: #667eea;
color: white;
border: none;
padding: 10px 20px;
border-radius: 6px;
cursor: pointer;
font-size: 14px;
transition: all 0.3s ease;
}
.btn:hover {
background: #764ba2;
transform: translateY(-2px);
}
.btn.primary {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.btn.harmony {
background: linear-gradient(135deg, #ff6b35 0%, #f7c948 100%);
}
/* 文件操作区域 */
.file-operations {
display: flex;
gap: 10px;
margin-bottom: 15px;
flex-wrap: wrap;
}
.file-input {
flex: 1;
min-width: 200px;
padding: 8px;
border: 2px dashed #ddd;
border-radius: 6px;
background: #f8f9fa;
}
.file-content {
background: #f8f9fa;
border-radius: 8px;
padding: 15px;
min-height: 100px;
max-height: 300px;
overflow-y: auto;
font-family: 'Consolas', monospace;
white-space: pre-wrap;
word-break: break-all;
}
/* 鸿蒙特性区域 */
.harmony-features {
display: flex;
gap: 10px;
flex-wrap: wrap;
}
/* 页脚 */
footer {
text-align: center;
margin-top: 30px;
padding-top: 20px;
border-top: 1px solid #eee;
color: #666;
font-size: 14px;
}
/* 滚动条样式 */
::-webkit-scrollbar {
width: 8px;
}
::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 4px;
}
::-webkit-scrollbar-thumb {
background: #888;
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: #555;
}
四、鸿蒙平台适配
4.1 鸿蒙特有API封装
创建 src/main/harmony-bridge.js:
javascript
const { ipcMain } = require('electron')
const { exec } = require('child_process')
const util = require('util')
const execPromise = util.promisify(exec)
// 鸿蒙设备信息获取
ipcMain.handle('harmony-device-info', async (event) => {
try {
// 检测是否为鸿蒙系统
if (process.platform === 'linux' && process.env.DESKTOP_SESSION) {
const { stdout } = await execPromise('getprop hw_sc.build.platform.Version')
return {
platform: 'HarmonyOS',
version: stdout.trim(),
deviceType: 'Desktop',
architecture: process.arch
}
}
return {
platform: process.platform,
version: 'N/A',
deviceType: 'Unknown',
architecture: process.arch
}
} catch (error) {
console.error('获取鸿蒙设备信息失败:', error)
return null
}
})
// 鸿蒙文件分享
ipcMain.handle('harmony-share', async (event, filePath) => {
try {
if (process.platform === 'linux' && process.env.DESKTOP_SESSION) {
// 调用鸿蒙分享API
await execPromise(`hm share file "${filePath}"`)
return true
}
return false
} catch (error) {
console.error('鸿蒙文件分享失败:', error)
throw error
}
})
4.2 鸿蒙应用打包配置
更新 package.json:
json
{
"name": "electron-harmonyos-demo",
"version": "1.0.0",
"description": "Electron鸿蒙桌面应用实战",
"main": "src/main/main.js",
"scripts": {
"start": "electron .",
"build": "electron-builder",
"build:harmony": "electron-builder --platform linux --arch arm64",
"build:win": "electron-builder --platform win32",
"build:mac": "electron-builder --platform darwin",
"pack": "electron-packager . --platform=linux --arch=arm64 --out=dist/"
},
"build": {
"appId": "com.example.electronharmonyos",
"productName": "Electron鸿蒙应用",
"directories": {
"output": "dist"
},
"files": [
"src/**/*",
"assets/**/*",
"package.json"
],
"linux": {
"target": "AppImage",
"arch": ["arm64", "x64"],
"category": "Utility",
"icon": "assets/icons/icon.png",
"synopsis": "Electron开发的鸿蒙桌面应用",
"description": "使用Electron框架开发的跨平台鸿蒙桌面应用"
},
"harmonyos": {
"target": "hap",
"arch": ["arm64"],
"ability": {
"name": ".MainAbility",
"icon": "$media:icon",
"label": "$string:app_name",
"launchType": "standard",
"description": "$string:description_main_ability",
"visible": true
}
}
}
}
五、打包与发布
5.1 打包为鸿蒙应用
bash
# 安装打包工具
npm install -g electron-builder
# 打包鸿蒙版本
npm run build:harmony
# 输出目录: dist/
# 生成文件:
# - dist/linux-arm64/Electron鸿蒙应用-1.0.0-arm64.AppImage
# - dist/linux-arm64/Electron鸿蒙应用-1.0.0-arm64.deb
# - dist/linux-arm64/Electron鸿蒙应用-1.0.0-arm64.rpm
5.2 鸿蒙应用签名
bash
# 生成密钥库
keytool -genkey -alias harmony-app -keyalg RSA -keysize 2048 -validity 10000 -keystore harmony-keystore.jks
# 签名应用
jarsigner -verbose -sigalg SHA256withRSA -digestalg SHA-256 -keystore harmony-keystore.jks dist/linux-arm64/Electron鸿蒙应用-1.0.0-arm64.apk harmony-app
5.3 上架鸿蒙应用市场
- 注册开发者账号: 访问华为开发者联盟官网注册
- 创建应用: 在AppGallery Connect中创建应用
- 上传应用包: 上传签名后的.hap或.apk文件
- 填写应用信息: 包括应用名称、描述、截图等
- 提交审核: 等待华为审核通过
六、性能优化
6.1 启动速度优化
javascript
// 使用V8快照加速启动
const snapshot = require('v8-heap-snapshot')
app.on('ready', () => {
// 创建快照
const snap = snapshot.createSnapshot()
// 使用快照恢复状态
if (snap) {
snapshot.restoreSnapshot(snap)
}
createWindow()
})
6.2 内存优化
javascript
// 监听内存警告
process.on('warning', (warning) => {
if (warning.name === 'MaxListenersExceededWarning') {
// 清理事件监听器
mainWindow.webContents.removeAllListeners()
}
})
// 定期垃圾回收
setInterval(() => {
if (global.gc) {
global.gc()
}
}, 300000) // 5分钟
七、调试技巧
7.1 开发工具配置
javascript
// 开发模式下打开DevTools
if (process.env.NODE_ENV === 'development') {
mainWindow.webContents.openDevTools({
mode: 'detach',
activate: true
})
// 热重载
require('electron-reload')(__dirname, {
electron: require(`${__dirname}/node_modules/electron`)
})
}
7.2 日志记录
javascript
const fs = require('fs')
const path = require('path')
// 创建日志目录
const logDir = path.join(app.getPath('userData'), 'logs')
if (!fs.existsSync(logDir)) {
fs.mkdirSync(logDir, { recursive: true })
}
// 日志写入函数
function writeLog(level, message) {
const timestamp = new Date().toISOString()
const logMessage = `[${timestamp}] [${level}] ${message}\n`
fs.appendFileSync(
path.join(logDir, `app-${new Date().toISOString().split('T')[0]}.log`),
logMessage
)
}
// 重写console方法
console.log = (...args) => {
writeLog('INFO', args.join(' '))
process.stdout.write(`[INFO] ${args.join(' ')}\n`)
}
console.error = (...args) => {
writeLog('ERROR', args.join(' '))
process.stderr.write(`[ERROR] ${args.join(' ')}\n`)
}
八、实战案例:文件管理器
8.1 功能实现
javascript
// src/renderer/file-manager.js
class FileManager {
constructor() {
this.currentPath = app.getPath('home')
this.init()
}
init() {
this.renderFileList()
this.bindEvents()
}
async renderFileList() {
const files = await window.electronAPI.readDirectory(this.currentPath)
const fileList = document.getElementById('file-list')
fileList.innerHTML = files.map(file => `
<div class="file-item ${file.isDirectory ? 'directory' : 'file'}" data-path="${file.path}">
<img src="${file.isDirectory ? 'folder.png' : 'file.png'}" class="file-icon">
<span class="file-name">${file.name}</span>
<span class="file-size">${this.formatSize(file.size)}</span>
</div>
`).join('')
}
formatSize(bytes) {
if (bytes === 0) return '0 B'
const k = 1024
const sizes = ['B', 'KB', 'MB', 'GB']
const i = Math.floor(Math.log(bytes) / Math.log(k))
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
}
bindEvents() {
document.getElementById('file-list').addEventListener('click', (e) => {
const fileItem = e.target.closest('.file-item')
if (fileItem) {
const path = fileItem.dataset.path
this.openFile(path)
}
})
}
async openFile(filePath) {
const stat = await window.electronAPI.getFileStats(filePath)
if (stat.isDirectory) {
this.currentPath = filePath
this.renderFileList()
} else {
// 使用默认应用打开文件
window.electronAPI.openFile(filePath)
}
}
}
九、总结
本文详细介绍了使用Electron开发鸿蒙桌面应用的完整流程,包括:
- 环境搭建: Node.js + Electron + 鸿蒙DevEco Studio
- 核心开发: 主进程、渲染进程、预加载脚本
- 鸿蒙适配: 特有API封装、平台检测
- 打包发布: 多平台打包、应用签名、上架流程
- 性能优化: 启动加速、内存管理
- 调试技巧: 开发工具、日志记录
通过Electron,我们可以利用Web技术栈快速开发跨平台桌面应用,同时针对鸿蒙系统进行深度适配,为用户提供原生体验。
参考资料
作者简介: 资深全栈开发者,专注Electron、鸿蒙应用开发,热爱分享技术干货。
版权声明: 本文为原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。