尽管 Electron 在跨平台方面表现出色,但不同平台间细微差异依然存在。仅仅通过单一平台下的测试无法充分证明应用的健壮性(当然,若只为特定平台开发则另当别论)。因此,在针对不同发布平台时,我们需要采取兼容性措施。
据我个人感受,macOS 支持的特性相对更丰富,其中许多是独有的。因此,某些能够在 macOS 上实现的功能并不一定能在 Windows 上完全呈现。针对 Windows 用户,在确保整体应用可用性的前提下,可能需要做出一些妥协和调整。不过,Windows 平台上的操作习惯也可以反过来服务 macOS 平台。
因此,有必要花一小节的篇幅,为大家说明一下 Electron 在不同平台下,一些常见的兼容性问题以及如何做好一些兼容性措施。
在开发 Electron 应用时,我们常常只专注于查找 API 的名称,而忽略了该 API 可用的平台限制。在官方文档中,针对一些独占的 API,通常会有标识来指明它们的适用平台,比如 macOS 下,Electron app 模块独有的一些钩子函数。
这些钩子函数因为和操作系统底层相关,所以有些只有特殊的平台才会有。也有些是特殊平台才具有的功能性 API。
在这些特殊平台才具有的功能 API 或钩子函数中,Electron 官方都会通过 tag 标签为我们标注清楚,我们只需要注意使用即可。不过需要注意的是,还有一些未有平台 tag 标识的 API 里的配置项、或者通用的配置项,不同平台的值和具体表现可能也有差异。
所以,当你在使用这些 API 和钩子函数的时候,需要多多留意这些差异,避免出现不符合预期的 bug。
虽然 Electron 的 API 帮助我们解决了跨平台的绝大多数场景的问题,但是因为操作系统本身存在着天然的差异性,所以在面对一些 Electron 不提供的原生 API 时,我们就不得不考虑不同平台的差异性问题。
比如,当我们使用 C++ 编写原生 node addon 开发的时候,为了实现不同平台的能力,需要编写兼容性代码:
// binding.gyp
{
"targets": [
{
"cflags!": ["-fno-exceptions"],
"cflags_cc!": ["-fno-exceptions"],
"defines": ["NAPI_DISABLE_CPP_EXCEPTIONS"],
"target_name": "addon",
"include_dirs": ["<!(node -p \"require('node-addon-api').include_dir\")"],
"sources": ["export.cc"],
"conditions": [
[
'OS=="mac"',
{
// ...
}
],
[
'OS=="win"',
{
// ...
}
]
]
}
]
}就算我们可能用不到底层原生模块来编写跨平台的扩展,而当我们用 js 来调用不同系统的能力时,也需要编写一些兼容性代码来进行适配:
let action;
// 判断当前平台
const platform = {
linux() {
return process.platform === 'linux';
},
macOS() {
return process.platform === 'darwin';
},
windows() {
return process.platform === 'win32';
},
};
// 平台判断
if (platform.macOS()) {
action = require("./darwin");
} else if (platform.windows()) {
action = require("./win");
} else if (platform.linux()) {
action = require("./linux");
}
export default action;我们知道在桌面端,macOS 和 Windows 有着特别多的操作差异性,比如在窗口管理上,Windows 用户习惯使用最大化、最小化和关闭窗口的按钮,而 macOS 用户则通常使用红、黄、绿色的按钮分别表示关闭、最小化和全屏。
除此之外,在 macOS 中,即使所有窗口关闭了,应用仍然在底部的菜单栏中保持活动状态。因此,通常需要特殊处理以确保用户主动退出应用程序。而在其他平台(如 Windows 或 Linux)中,通常情况下关闭最后一个窗口也意味着退出应用程序是合理的行为。所以为了实现这个操作习惯,我们也可以增加一个情况判断:
// 当窗口都被关闭了
app.on('window-all-closed', () => {
// 如果不是macOS
if (process.platform !== 'darwin') {
// 应用退出
app.quit();
}
});除此之外,我们还知道,不同操作系统对于一些常用功能的键盘快捷键可能有所不同,比如复制、粘贴、撤销等,Windows 中使用的 Ctrl 作为修饰键,而 macOS 中是使用的 Command 作为修饰键。所以,当我们需要注册此类快捷键的时候,需要注意使用 CommandOrControl+xx 关键词:
globalShortcut.register('CommandOrControl+X', () => {
// todo
});需要留意的是在 Electron 的通用 app.getPath API 中,获取的返回路径是有差异性的,比如:
import { app } from 'electron';
// 获取用户的应用程序数据目录
app.getPath('appData');
// MacOS: ~/Library/Application Support
// Windows: C:\Users\<you>\AppData\Roaming
// Linux: ~/.config此外,在 Windows 下,文件路径的分隔符为 \,而在 macOS 和 Linux 下,文件路径的分隔符为 /。
所以,如果我们在 MacOS 系统上编写了操作路径的应用程序,放到 Windows 上去运行,就有可能因为路径而出现问题。正确的做法是使用 path 模块,帮助处理路径,比如使用 path.join 方法来拼接路径:
const path = require('path')
// windows __dirname = D:\my\folder
const relativePath = './image/a.png';
console.log(path.join(__dirname, relativePath)); // D:\my\folder\image\a.png
// macOS __dirname = /my/folder
const relativePath = './image/a.png';
console.log(path.join(__dirname, relativePath)); // /my/folder/image/a.png在 Electron 中,应用程序可以通过 Tray 类来创建托盘图标。
let icon;
if (commonConst.macOS()) {
icon = './icons/iconTemplate@2x.png';
} else if (commonConst.windows()) {
icon =
parseInt(os.release()) < 10
? './icons/icon@2x.png'
: './icons/icon.ico';
} else {
icon = './icons/icon@2x.png';
}
const appIcon = new Tray(path.join(__static, icon));在代码中,有三种图标:iconTemplate@2x.png、icon.ico 和 icon@2x.png。其中iconTemplate@2x.png 用于在 macOS 中显示模板图标,而 icon@2x.png 用于在 Windows < 10 以及 Linux 操作系统中显示图标。icon.ico 用于在 Windows >= 10 的操作系统中展示。
在 macOS 使用 Template 时:
@2x 图片是 144dpi 。@2x 图片需要与标准图片文件名相同,否则 MacOS 不会神奇地反转图片的颜色或使用高分图片。在 Windows 上使用 icon 时注意:
ICO 图标获取最佳视觉效果。在 macOS 中,未签名的应用可能会面临一些安全提示和限制。其中一个问题是在安装前需要用户确认并授权运行该应用。这是 macOS 的安全特性,用以确保用户知晓并授权运行未签名的应用程序,关于如何对 Electron 应用程序签名和公证,可以在《通用篇:Electron 应用打包》章节详细阅读。
另一种处理方式是,在终端运行以下指令:
sudo spctl --master-disable # 关闭限制,安装前运行一次即可另一个则是在 Mac M1、M2 系统架构中,安装后打开会提示文件已损坏。
该问题的处理方案并不麻烦。
sudo xattr -r -d com.apple.quarantine /Applications/[your app name].app在 Windows 中,如果软件需要管理员权限运行,只需要在打包时声明requestedExecutionLevel 为 requireAdministrator。这将弹出一个 UAC 提示框,请求用户授予管理员权限运行软件。
// 打包配置
electronBuilder: {
win: {
requestedExecutionLevel: 'requireAdministrator',
}
}而在 macOS 和 Linux 下,如果需要软件以 root 权限运行,需要使用 sudo 命令来获取权限。具体来说,可以使用以下命令运行软件,并输入 sudo 密码:
sudo /path/to/your/app/executable当然这种方式不是很好,推荐在需要输入指令时再调用 sudo 密码确认框。当然,如果你需要单独申请 Mac 上的特定权限,比如 文件夹访问、屏幕录制 权限等等,也可以试试这个库:node-mac-permissions。
本小节,我们可以通过管中窥豹的形式了解了 Electron 在开发跨平台应用时需要注意到的一些兼容性问题,文中列举的都是一些较为常见的兼容性问题和解决方案,如果你也遇到了类似的问题,希望可以对你有启发和帮助!
在接下来的章节内容,你也会发现在实现很多功能的时候,都需要进行跨平台兼容性处理,我们也会针对用户使用最多的 Windows 和 MacOS 分别进行详细的跨平台兼容性处理方案介绍,相信你会有更加深刻的认知。