美文网首页Android精选
通过协议唤起Electron应用

通过协议唤起Electron应用

作者: 寂寞的原子 | 来源:发表于2018-12-31 13:24 被阅读116次

    自从有了 Electron,桌面应用的开发变得更简单了。这里总结一下,在 Electron v4 里,如何通过协议链接唤起一个已有的 Electron 应用。

    注:这里使用的是 Electron v4.x。

    背景

    对于一个 URL 来说,其实就是指定了一个协议(protocol),然后让系统用对应的应用去打开它。如 myapp://startapp/here?a=1&b=2,系统会去找到已经注册了 myapp 这个协议的应用,然后把 URL 当做参数传过去。

    这样我们就可以在浏览器中通过一个 <a> 标签简单地唤起应用了。

    <a href="myapp://startapp/here?a=1&b=2">去吧,皮卡丘</a>
    

    原理

    Electron 应用要怎么处理协议唤起的流程呢?

    单实例运行

    首先,每次打开一个协议 URL,系统都会启动一个新的应用。这就需要应用自己去判断,把 URL 当做参数传给已有的应用,还是自己直接处理。

    Electron 提供了一个简单的方法,来获取一个锁,只有第一个调用的实例才能获取成功,后面的其他实例则把参数传过去,然后退出就可以了。

    const { app } = require('electron');
    
    // 获取单实例锁
    const gotTheLock = app.requestSingleInstanceLock();
    if (!gotTheLock) {
      // 如果获取失败,说明已经有实例在运行了,直接退出
      app.quit();
    }
    

    注册协议

    我们期望通过协议来启动应用,所以要先注册一个协议到系统中,调用 API 即可:

    const PROTOCOL = 'myapp';
    
    app.setAsDefaultProtocolClient(PROTOCOL, process.execPath, args);
    

    这里的 args 是预定义的参数,对 macOS 没有作用,但是在 Windows 上却是必不可少的。

    在 Windows 上启动一个协议URL时,实际上是用如下参数启动了我们的应用:

    ${process.execPath} ${...args} myapp://...
    

    需要注意的是,在开发阶段,我们是通过 electron . 或者 electron path/to/script.js 来启动的应用,所以 process.argv[1] 是我们的脚本路径,传给系统时,这个参数也不能少,否则启动的就是一个纯粹的 Electron 壳,而不是我们的应用了。这时,这个参数就要通过这里的 args 一起注册到系统中了。

    根据这个帖子,我们可以在预定义的参数最后加一个 -- ,来阻止其他参数直接被 Electron 处理。

    const args = [];
    if (!app.isPackaged) {
      // 如果是开发阶段,需要把我们的脚本的绝对路径加入参数中
      args.push(path.resolve(process.argv[1]));
    }
    // 加一个 `--` 以确保后面的参数不被 Electron 处理
    args.push('--');
    

    获取参数

    第二个实例运行的时候,自己就退出了,那么第一个实例如何能获取到启动第二个实例的参数呢?这里 macOS 和 Windows 上的行为是不一致的,需要分别处理。

    // 如果打开协议时,没有其他实例,则当前实例当做主实例,处理参数
    handleArgv(process.argv);
    
    // 其他实例启动时,主实例会通过 second-instance 事件接收其他实例的启动参数 `argv`
    app.on('second-instance', (event, argv) => {
      // Windows 下通过协议URL启动时,URL会作为参数,所以需要在这个事件里处理
      if (process.platform === 'win32') {
        handleArgv(argv);
      }
    });
    
    // macOS 下通过协议URL启动时,主实例会通过 open-url 事件接收这个 URL
    app.on('open-url', (event, urlStr) => {
      handleUrl(urlStr);
    });
    

    Windows 下 argv 的处理有一个需要注意的地方,就是开发阶段要跳过前两个参数(execPath 和当前启动脚本):

    function handleArgv(argv) {
      const prefix = `${PROTOCOL}:`;
      // 开发阶段,跳过前两个参数(`electron.exe .`)
      // 打包后,跳过第一个参数(`myapp.exe`)
      const offset = app.isPackaged ? 1 : 2;
      const url = argv.find((arg, i) => i >= offset && arg.startsWith(prefix));
      if (url) handleUrl(url);
    }
    

    协议链接

    完整的协议链接跟HTTP链接类似,如下:

    myapp://host/path?a=1&b=2
    

    但大部分情况下,我们可能只需要最后的 key-value 参数就可以了,所以可以省略成:

    myapp:?a=1&b=2
    

    链接可以通过全局对象 URL 来解析:

    function handleUrl(urlStr) {
      // myapp:?a=1&b=2
      const urlObj = new URL(urlStr);
      const { searchParams } = urlObj;
      console.log(urlObj.query); // -> a=1&b=2
      console.log(searchParams.get('a')); // -> 1
      console.log(searchParams.get('b')); // -> 2
      // 根据需要做其他事情
    }
    

    结束

    至此,我们就完成了对协议链接的处理。完整代码如下:

    const path = require('path');
    const { app } = require('electron');
    
    const gotTheLock = app.requestSingleInstanceLock();
    
    if (!gotTheLock) {
      app.quit();
    }
    
    const args = [];
    if (!app.isPackaged) {
      args.push(path.resolve(process.argv[1]));
    }
    args.push('--');
    const PROTOCOL = 'myapp';
    app.setAsDefaultProtocolClient(PROTOCOL, process.execPath, args);
    
    handleArgv(process.argv);
    
    app.on('second-instance', (event, argv) => {
      if (process.platform === 'win32') {
        // Windows
        handleArgv(argv);
      }
    });
    
    // macOS
    app.on('open-url', (event, urlStr) => {
      handleUrl(urlStr);
    });
    
    function handleArgv(argv) {
      const prefix = `${PROTOCOL}:`;
      const offset = app.isPackaged ? 1 : 2;
      const url = argv.find((arg, i) => i >= offset && arg.startsWith(prefix));
      if (url) handleUrl(url);
    }
    
    function handleUrl(urlStr) {
      // myapp:?a=1&b=2
      const urlObj = new URL(urlStr);
      const { searchParams } = urlObj;
      console.log(urlObj.query); // -> a=1&b=2
      console.log(searchParams.get('a')); // -> 1
      console.log(searchParams.get('b')); // -> 2
    }
    

    相关文章

      网友评论

        本文标题:通过协议唤起Electron应用

        本文链接:https://www.haomeiwen.com/subject/uivilqtx.html