595

【Electron】酷家乐客户端开发实践分享 — 入坑篇

 4 years ago
source link: https://www.tuicool.com/articles/jymeYn2
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

本文的初衷

Electron所使用的技术栈(JavaScript、NodeJs、HTML、CSS)和web前端工程师完美契合。于是,越来越多的前端工程师,用Electron来开发桌面客户端的开发,我也是其中的一员。

虽然Electron技术栈对前端工程师比较友好,但是概念较多,和web前端开发还是有很大差别的,写个入坑指南希望能帮助读者快速上手Electron。

了解客户端

首先抛出一个问题,web应用是桌面客户端吗?显然不是。那么,问题来了,什么样的软件才是桌面客户端呢?我们既然要从web前端转到客户端开发,那么就需要了解客户端,就像我们当初了解web应用一样。

回到刚刚那个问题,桌面客户端有两个重要的特点:

  1. 独立运行于操作系统上(桌面客户端只是PC,那么限定windows、MacOS、linux这几个主流PC操作系统)
  2. 有自己的GUI(用户图形界面 graphical user interface)

web应用有自己的GUI,必须在浏览器中执行,因此不是桌面客户端。

浏览器能直接运行在操作系统上,而且有自己的GUI,因此浏览器是桌面客户端。

Electron的能力

在刚刚接触Electorn的时候,文档看的我是眼花缭乱。在某个加班的深夜,我不禁对天长叹:这个东西到底能干啥?

这东西能干啥?在经历了Electron的反复摩擦之后,我总结了Electron的几个关键能力:

  1. NodeJs全部能力,与 操作系统 交互
    • operation system 与操作系统相关的操作
    • HTTP(s)、HTTP2
    • process、child process 进程相关
    • file system 文件系统
    • ...省略
  2. Electron提供的基础模块,主要与 操作系统 交互
    • app 主进程声明周期管理,控制MacOS任务栏dock、windows任务栏taskbar
    • BrowserWindow 控制窗口,在MacOS和windows中窗口非常重要!
    • screen 操作用户显示器
    • globalShortcut 系统级别快捷键
    • ...省略
  3. Chromium提供的能力,主要提供 GUI图形界面
    • 解析HTML、CSS、JS
    • ajax请求
    • cookie、localstorage
    • ...省略

能力越大,责任越大

如果用户安装了我们的桌面客户端,那么我们的软件在用户电脑上运行时,就有了非常大的权利,这是把双刃剑。

用户选择了我们的软件,我们也要对用户的电脑负责。能力越大,责任也就越大:

1.注意内存的占用,特别是chromium,简直是内存怪兽。可以通过os来获取用户电脑的配置,然后根据电脑的配置和可用资源,来制定合理的策略。

  1. 为软件增加代码签名,提升安全性
  2. 谨慎操作注册表、用户敏感目录

一旦被贴上【流氓软件】、【不好用】的标签,就很难再改变用户的印象了。

主进程、渲染进程

IRrQVnV.jpg!web

生命周期

主进程:从整个应用启动到结束,该进程一直存在。 主进程只有一个

渲染进程:主进程可用创建/销毁渲染进程,因此渲染进程的生命周期是不固定的。渲染进程可以有多个。

执行环境

ERbAFnY.png!webiAJFNbR.png!web

在Electron的API文档中,会在文档顶部标识该模块在哪个进程可用,例如: ipcRenderer

职责划分

主进程 渲染进程 控制app的生命周期,为app注册关键事件 解析HTML,渲染窗口内容 阻止一些默认行为,例如webContents的跳转、download事件的默认行为等等(在渲染进程无法做到) 处理窗口的交互逻辑 创建BrowserWindow,也就是渲染进程。合理设置窗口的参数,控制窗口的生命周期(例如何时销毁窗口),决定BrowserWindow加载何处的HTML 与主进程通信,实现高级交互

窗口、前端资源

我们回顾一下刚刚讲到的执行流程,其中有一个有趣的点,就是Electron的窗口会加载一个HTML来渲染窗口的内容。

HTML,以及HTML加载的css、js文件,统称为前端资源

如果不加载HTML的,客户端还能用吗?不妨来试试

// main process
    const win1 = new BrowserWindow();
    const win2 = new BrowserWindow();

上述代码在主进程中执行,创建了两个窗口,窗口并没加载HTML文件。但是窗口却是真实存在的,带有系统标准的控制栏,可拖动,是货真价实的系统窗口!

jQrQN3n.png!web

我们可以发现,前端资源和窗口是分离的。由主进程创建的的窗口(BrowserWindow),既是一个系统原生窗口,同时也是一个加载&渲染前端资源的容器

窗口通常会通过file协议和http(s)协议来加载前端资源,接下来我们看看这两种方式的区别。

通过file协议加载HTML

在Electron的官方入门例子中,就是通过file协议来加载HTML的

通过file协议加载HTML,无论有没有网络,都可以加载到HTML文件,这是file协议核心优势。缺点也比较明显:

  1. 如果页面资源要更新,那么只能通过发版来解决(如果你用webview,那么webview的内容就可以自动更新,不过webview也需要有网络才能加载)
  2. 在file协议下,无法通过ajax来请求数据(协议不同),只能通过NodeJs的http(s)模块来发起网络请求

通过http协议加载HTML

通过http协议加载HTML,优点是可以随时通过web页面的部署,更新渲染进程的资源,并且在https协议下,你可以在页面中使用前端熟悉的ajax请求来获取数据。

当然,缺点也比较明显:

  1. 没有网络,并且在你没有做HTML的缓存时,你的窗口内容无法加载
  2. 必须通过https来加载,保证页面内容的安全性

代码示例

方便读者更好理解上文的内容,写了一个小demo,源代码地址 https://github.com/littlecold233/electron-demo ,例子有以下特点:

  • 创建主窗口,阻止关闭主窗口关闭的默认事件,不销毁窗口。(大部分客户端的主窗口,关闭主窗口的时候,实际上是隐藏了该窗口,例如QQ、微信)

  • 应用退出时,会尝试关闭所有窗口,再退出应用。如果主窗口的关闭行为默认事件被阻止,那么会导致主窗口无法关闭,整个应用无法退出。因此使用 forceQuit 这个变量来控制。

  • 使用http或者file协议加载窗口前端资源(例子中,默认加载的是微信)

const { app, BrowserWindow } = require('electron')

async function main () {
  await app.whenReady();

  let forceQuit = false;

  const majorWindow = new BrowserWindow({
    title: '主窗口',
    width: 1000,
    height: 750,
    minWidth: 1000,
    minHeight: 750,
    backgroundColor: '#f2f2f2',
  }); // 主窗口

  // 阻止标题更新
  majorWindow.on('page-title-updated', (e) => {
    e.preventDefault();
  });

  majorWindow.on('close', (e) => {
      // 用户希望退出的时候,不作处理,默认会销毁这个窗口
      if (forceQuit) return;
      e.preventDefault();

      // macOS全屏的处理
      if (majorWindow.isFullScreen()) {
        majorWindow.once('leave-full-screen', () => {
          majorWindow.hide();
        });
        majorWindow.setFullScreen(false);
      } else {
        majorWindow.hide();  // 隐藏窗口
      }
  });

  // 点击dock打开主窗口
  app.on('activate', () => {
    majorWindow.show();
  });

  // 用户使用cmd + Q、代码中调用app.quit等情况
  // 此时用户希望能够退出应用,因此将forceQuit改为true
  app.on('before-quit', () => {
    forceQuit = true;
  });
  
  app.dock.setIcon('./img/icon.png'); // 在app打包后,这一句代码其实是不需要的

  majorWindow.loadURL('https://wx.qq.com'); // http协议加载前端资源,随便加载一个微信试试
  // majorWindow.loadURL('file://index.html'); // file协议加载前端资源

}

main();

在本地跑一下这个例子 F3umYzB.png!web

最后

欢迎大家在评论区讨论,技术交流 & 内推 -> [email protected]


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK