在传统的应用程序设计中,我们可以看到很多通过浏览器唤起本地应用的案例,比如百度网盘、迅雷等工具,他们在浏览器访问一个非 http/https 协议开头的地址时,会自动打开其自己的应用程序并传递一定的参数。该功能的实现方式网络上有很多示例,在 Windows 和 macOS 不同平台下他们分别需要如下设置:
Windows 注册自定义 URL Scheme
代码语言:javascript复制Windows Registry Editor Version 5.00
[HKEY_CLASSES_ROOTMYSCHEME]
@="MyApp Name"
"URL Protocol"="E:\Documents\Repositories\temporary\MyApp\bin\MyApp.exe"
[HKEY_CLASSES_ROOTMYSCHEMEshell]
[HKEY_CLASSES_ROOTMYSCHEMEshellopen]
[HKEY_CLASSES_ROOTMYSCHEMEshellopencommand]
@=""E:\Documents\Repositories\temporary\MyApp\bin\MyApp.exe" --argument="%1""
在你的程序安装包中需要写入如下注册表内容,告诉系统我们要注册一个自定义 URL Scheme,上面的例子中
MYSCHEME
是自己的 URL Scheme 名称E:\Documents\Repositories\temporary\MyApp\bin\MyApp.exe
是自己应用安装后的实际路径--argument="%1
是启动自己应用后后面要加的参数
当我们通过浏览器访问 MYSCHEME://auth?username=abc&password=def
时,将会唤起 E:\Documents\Repositories\temporary\MyApp\bin\MyApp.exe
这个程序并传递参数为 --argument=MYSCHEME://auth?username=abc&password=def
,浏览器会先弹出提示是否打开自己注册的应用:
在选择打开应用后,程序自动启动,并且后面追加了命令行参数:
macOS 注册自定义 URL Scheme
macOS 下与 Windows 在自定义 URL 的实现上有差异,你需要在应用 boundle 里面,修改 Info.plist 增加如下字段:
代码语言:javascript复制<array>
<dict>
<key>CFBundleURLName</key>
<string>com.company.group.app</string>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLSchemes</key>
<array>
<string>MyApp</string>
</array>
</dict>
</array>
其中 MyApp 则为自动注册到系统中的自定义 URL Scheme,当把你的应用安装在应用程序安装目录后,并在 macOS 下通过浏览器访问 MyApp:// 的地址时会自动唤起你的应用。
以上是两种系统中实现自定义 URL Scheme 的不同,到这里都已经能成功唤起我们的应用了,但本文主要叙述的内容并不是唤起相关的能力,而是如何在应用已经启动的情况下,又通过浏览器去唤起应用来实现参数的传递。
Windows 下对已启动应用传参
在 Windows 下我并没有找到像 macOS 一样方便的方式来实现这个功能,我的处理办法是,在第一个客户端启动时检测一下是否已经创建指定命名管道(Linux 下使用 Domian socket)如果未创建则创建并启动应用,如果已经创建则打开命名管道将本次启动时的命令行参数通过管道发送给创建命名管道的实例进程中,这样就实现了一个间接的通讯将参数动态传递给已经运行的程序。
如果你上层应用使用的是 Qt,可以使用 LocalSocket 和 LocalServer,其中 LocalServer 来实现管道服务端的功能,LocalSocket 来实现客户端功能在每次应用启动时尝试一次连接。
macOS 下对已启动应用传参
macOS 下相对简单一些,由于 macOS 系统级别限制,仅允许启动一个同名 Boundle ID 的实例,所以像上面 Windows 一样多进程启动后通过管道传递参数的方式就行不通了。但系统提供了另外一组能力,能帮助我们更方便的实现这一功能。
在 Stackoverflow 中有这样一篇回答,清晰的描述了如何使用 OC 的方式监听应用二次启动传参以及如何使用 Qt 来处理以上事件:点击查看链接
其中 Qt 的方式非常简单,只需要响应应用的 QFileOpen 事件即可实现此功能,代码如下:
代码语言:javascript复制bool FileOpenEventFilter::eventFilter(QObject* obj, QEvent* event)
{
if (event->type() == QEvent::FileOpen)
{
QFileOpenEvent* fileEvent = static_cast<QFileOpenEvent*>(event);
if (!fileEvent->url().isEmpty())
{
m_lastUrl = fileEvent->url().toString();
emit urlOpened(m_lastUrl);
}
else if (!fileEvent->file().isEmpty())
{
emit fileOpened(fileEvent->file());
}
return false;
}
else
{
// standard event processing
return QObject::eventFilter(obj, event);
}
}
总结
在应用启动过程中动态传递参数在 Windows 和 macOS 上使用的是不同的机制,两个平台不同的实现步骤我们再列一下清单,避免日后遗忘:
Windows
- 通过注册表注册 URL Scheme 到系统
- 程序首次启动实现自动创建管道能力
- 程序二次启动实现读取管道并广播通知参数能力
macOS
- 通过 Info.plist 将 URL Scheme 注册到系统
- 原生程序实现
NSApplicationWillFinishLaunchingNotification
- Qt 程序处理 QFileOpen 消息