专栏首页QQ音乐技术团队的专栏electron 构建跨平台桌面应用

electron 构建跨平台桌面应用

昨日(2016.09.13)本文发表后,获得了一定的阅读和转发量,但经部分网友反馈和仔细审核后发现,在与 NW.js 对比的环节,言辞欠妥,且数据的真实性有待考究,特此将争议部分删除,同时借此诚挚地向 NW.js 的作者以及各位读者反馈表示感谢,期待更多深入的交流和分享,修订后的版本如下:

Stack Overflow 联合创始人 Jeff Wood 曾说过,任何一个能用 JavaScript 编写的应用系统,最终都必将使用 JavaScript 实现。

简介

Electron 是一款可以通过 Web前端技术 构建跨平台桌面应用的框架。其原名为 Atom Shell, 是 Github 社区原本为 Atom 编辑器设计的一个跨平台应用外壳,它将 Chromium 和 Node.js 的事件循环整合在一起,并提供了一些与原生系统交互的 API。

简单地说,通过 Electron,我们可以使用自己所熟悉的前端技术轻松构建出一款能运行在Windows, Linux 和 Mac 上的桌面级应用程序。

现阶段已有许多优秀的桌面应用都是基于 Electron 开发,其中如 Atom 编辑器,VS Code 和 Postman 等等都是我们所熟知的,下面列出这当中的部分应用,是不是看到了许多熟悉的图标呢?

Hello World

案例运行

使用下面三步即可构建最简单的 Hello World 桌面程序。

1. 克隆官方的例子
$ git clone https://github.com/electron/electron-quick-start

2. 进入项目目录
$ cd electron-quick-start

3. 安装项目依赖后执行该程序
$ npm install && npm start

运行效果如下:

案例分析

察看目录结构如下:

.
├── .gitignore    
├── index.html
├── LICENSE.md
├── main.js
├── package.json
├── README.md
└── renderer.js

主要分为三个部分:

  • package.json: 定义了项目的依赖以及程序的主入口文件 main.js。
  • main.js: 负责创建应用窗口,并赋予其与当前操作系统的原生GUI交互的功能。
  • index.html: 定义了页面的渲染内容,即 “Hello World” 字符串。

Electron 程序启动时,会产生两条进程,分别是主进程和渲染进程,main.js 脚本执行的环境就是主进程,负责管理和维护着渲染进程的生命周期,拥有绝大部分 node模块 的调用能力;而在 main.js 中创建的每一个窗体则对应着一个渲染进程,它们之间相互独立,且都具备部分 Node模块 的功能。

主进程与渲染进程的关系如下图所示,它们之间通过 IPC 模块进行消息交互,关于 IPC 模块的使用,下面会提到。

功能模块

这个部分将介绍 Electron 里面常用到的几个功能模块。

IPC

上面提到,Electron 中包含了主进程和渲染进程,事实上主进程就是一个后台进程,掌控着渲染进程的创建与销毁动作,且官方提供的绝大部分模块也只能在该进程中调用。

主进程与渲染进程之间的通信通过 IPC(进程间通信)模块完成,IPC模块可划分为 ipcMain 和 ipcRenderer 两个部分,其中 ipcMain 对应 主进程中的 IPC模块,而 ipcRenderer 则是在渲染进程中使用,下面直接看个例子:

main.js:

// 引入 ipcMain 模块
const ipcMain = require('electron').ipcMain;
// 监听 ‘blabla’ 通道,收到消息后输出,并向 'blibli' 通道发送消息
ipcMain.on('blabla', function(event, arg) {
    console.log(arg);
    event.sender.send('blibli', 'hello client!');
})

index.html:

// 引入 ipcRenderer 模块
const ipcRenderer = require('electron').ipcRenderer;  
// 向 'blabla' 通道发送消息
ipcRenderer.send('blabla', 'hello server!');
// 监听 ‘blibli’ 通道, 收到消息后输出
ipcRenderer.on('blibli', function(event,arg) {
  console.log(arg);
});

输出效果正如你所期望那样:

main.js:

hello server!

index.html:

hello client!

remote

上面提到了大部分模块只能在主进程中调用,为了突破这种限制,Electron 官方还提供了 remote 模块以简化进程间的通讯。

通过 remote 模块,渲染进程可以方便地引用主进程中的模块和全局变量等。

main.js:

global.person = {
  name: 'boxizen',
  sex : 'male',
  age : 24
}

index.html:

// 引用 remote 模块
const remote = require('electron').remote;
// 输出 main.js 中定义的 person 全局对象的 age 属性值
console.log(remote.getGlobal('person').age);

输出效果(index.html):

24

webview

webview 是个比较有趣的标签,可以将线上的页面嵌入进 Electron app 中,与 iframe 不同的是,webview 和应用运行的是不同的进程,不拥有渲染进程的权限。

下面将演示如何将微信网页版嵌入进 Electron 应用里,只需要简单的两步:

index.html:

<webview autosize="on" src="https://wx.qq.com/" style="display:inline-flex; width:1000px; height:764px"></webview>

main.js:

mainWindow = new BrowserWindow({width: 1000, height: 764, resizable: false})

效果图:

这样一个PC版的微信就大功告成了,实际上就是利用 webview 标签加载微信网页版的在线地址,再在main.js中调整窗体大小以适配网页版的微信,是不是很简单呢。

webview 对象中包含 insertCSS()executeJavaScript() 两个方法,表示可以插入样式代码和执行 js 脚本,这样我们就可以对加载页面中的样式及交互逻辑进行修改。默认的 webview 没有 node 功能,而如果设置了 nodeintegration 属性,它将整合node,拥有可以使用系统底层的资源。

此外 webview 中的 preload 属性允许在页面的脚本执行前预加载一个指定的脚本,下面我们利用该属性和 executeJavaScript() 方法实现 electron 版微信的未读消息角标展示。

index.html:

// 这里使用 preload 属性预加载了 badge.js 脚本
<webview id="foo" autosize="on" preload="badge.js" src="https://wx.qq.com/" style="display:inline-flex; width:1000px; height:764px"></webview>

var webview = document.getElementById("foo");      
  webview.addEventListener('dom-ready', function () {             
  webview.executeJavaScript('badge.get()');
});

badge.js:

// 引入 IPC 模块
const ipcRenderer = require('electron').ipcRenderer;

badge = {
  get: function () {  
      // 监听微信左侧面板节点变化
    $(".panel").bind('DOMSubtreeModified', function() {          
      var count = 0;       
      // 累加所有未读消息   
      $(".icon.web_wechat_reddot_middle").each(function () {
        count += parseInt(this.textContent);
      });
      // 通过 IPC 发送给主进程
      if (count > 0) {
        ipcRenderer.send('badge-changed', count.toString());
      } else {
        ipcRenderer.send('badge-changed', '');
      }
    })
  }
}

main.js:

const electron = require('electron');
const ipcMain = electron.ipcMain;
const app = electron.app

exports.init = function() {
    // 监听角标通道消息
    ipcMain.on('badge-changed', function (event, num) {
      app.dock.setBadge(num);
    });
}

效果图如下,可以发现 Electron 的 dock 角标显示的未读消息数(11)跟微信中面板中未读消息数量一致:

其他

当然 Electron 中还有许多实用的模块,如作为桌面应用必不可少的 MenuTray 模块、拥有调用当前操作系统功能的 Shell 模块、NW.js 中不具备的自动更新功能 - autoUpdater 模块、自动提交奔溃报告的 crashReporter 模块和全局快捷键模块 global-shortcut 等等,此处不做过多介绍。

打包构建

Electron 打包的方式有很多种,常见的有 electron-builder、electron-packager 和 asar几种,在这里我使用的是 electron-packager 作为应用的打包工具。

首先还是得先安装 electron-packager:

npm install electron-packager --save-dev 

然后在 package.json 中编写构建命令,下面生成了分别在 Windows 和 Mac 下的两条构建命令:

"scripts": {
    "start": "electron .",
    "build-win": "electron-packager . doubanFM --platform=win32 --arch=x64 --version=1.2.6 --icon=./fm.ico --overwrite",
    "build-mac": "electron-packager . doubanFM --platform=darwin --arch=x64 --version=1.2.6 --icon=./fm.icns --overwrite"
  },

执行构建命令, done!

npm run build-mac

最后贴一张最近利用 Electron 构建的桌面版豆瓣FM的截图:

参考资料:

本文分享自微信公众号 - QQ音乐技术团队(gh_287053a877e6),作者:boxizeng

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2016-09-14

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 轻听变色之谜

    轻听是一款小而美的 Android 本地音乐播放器,而它的特点之一就是拥有多彩的外衣,那么轻听是如何实现变色的呢?

    QQ音乐技术团队
  • Android系统线控和歌曲信息屏显的那点事

    目前Android系统中主流的音乐播放器都支持线控的功能,线控设备包括有线耳机和蓝牙耳机或蓝牙车机,当不方便操作手机的时候可以通过线控来控制音乐的播...

    QQ音乐技术团队
  • 一种避免 iOS 内存碎片的方法

    CFStringCreateWithBytes 方法分配的字符串是堆空间,如果数据过长,则很容易产生内存碎片。解决这个问题有两种思路:一是在栈空间分配内存,二是...

    QQ音乐技术团队
  • 【玩转腾讯云】四.使用云服务器CVM轻松部署Node.js

    ②选择自定义配置——计费模式为“按量付费”——地域选择“北京”——可用区选择“随机可用区”——网络选择“默认”即可

    一只特立独行的兔先生
  • 深入typeclass_Haskell笔记4

    OOP中的Class是对象模板,用来描述现实事物,并封装其内部状态。FP中没有内部状态一说,所以Class在函数式上下文指的就是接口。派生自某类(derivin...

    ayqy贾杰
  • 剑指Offer面试题:11.打印1到最大的n位数

      初看之下好像没有问题,但是其并没有考虑大数问题,有可能即使用整型(int)或长整型(long)都会溢出。

    Edison Zhou
  • 开发过程中快速抓包并解析

    这几天小编在工作中遇到了一个灵异事件,客户端使用的是安卓原生系统,服务端使用的是java。需求就是客户端在照相的时候可以实时上传照片。后台接收并保存,并且可以在...

    编程软文
  • 推荐一些最近在关注的开源项目

    最早听到这个项目是收到了MinsDB的邮件邀请,来自伯克利的支持和孵化的AI项目,基于Python 3,看github里面的例子还是很容易上手的。

    jeanron100
  • 在厉害的圈子里耳濡目染 No.110

    很久以前看过一个标题《我在北大当学渣》,大家以前都是学霸,大家到了同一个组织,同一个环境,都得有人当学渣吧?

    大蕉
  • 第一次看到如此原始的R绘图代码

    上面的67个循环,代码就构建了67个长度为2千万的向量,对这两千万的向量画boxplot,一个向量内存约200多M,R语言本身如此低效,怪不得我都没有出图,肯定...

    生信技能树

扫码关注云+社区

领取腾讯云代金券