从零开始的electron开发-主进程-窗口启动

上一篇咱们简单介绍了electron基本脚手架的搭建,主要是区分主进程及渲染进程,处理环境变量,让打不一样的包有其对应的环境,接下来几篇主要讲主进程的一些处理,本篇主要讲述如何建立窗口。html

变量

这里先简单说一下vue-cli-plugin-electron-builder注入的环境变量和electron的一些变量vue

process.env.WEBPACK_DEV_SERVER_URL:看名字就知道了,其实就是webpack启的服务(npm run serve)http://localhost:xxx
process.env.VUE_APP_ENV:咱们本身用的环境变量,上一期在.env.xxx中设置的
process.env.NODE_ENV: 判断咱们是本地开发,仍是打包以后的
const isMac = process.platform === 'darwin':判断是mac系统仍是其余系统

建立窗口

electron的窗口建立一般使用BrowserWindow进行建立,而后经过loadURL载入url地址或者file://协议的本地HTML文件的路径实现文档内容的加载node

import { app, protocol } from 'electron'
let win

// 先注册一个app协议用来加载文档,用做于打包后的文档载入,其实就是相似于file://协议加载本地文件
protocol.registerSchemesAsPrivileged([
  { scheme: 'app', privileges: { secure: true, standard: true } }
])
// 建立一个函数来建立窗口,winConfig是BrowserWindow的配置,devPath是开发时的地址,prodPath是打包后文件地址
function createWindow(winConfig, devPath, prodPath) {
  const win = new BrowserWindow(winConfig)
  if (process.env.WEBPACK_DEV_SERVER_URL) {
    win.loadURL(process.env.WEBPACK_DEV_SERVER_URL + devPath)
  } else {
    win.loadURL(`app://./${prodPath}`) // 这里的地址就是public文件夹下的
  }
  return win
}
// 调用,其实就是开发时咱们载入的文档就是`http://localhost:80`,打包后载入的文档就是打包后的index.html
 win = createWindow({
    height: 810,
    width: 1440,
    webPreferences: {}
  }, '', 'index.html')

多页

上一期咱们说过能够经过设置vue.config.js里的pages打包多页(这里就不放代码了,loader请看上一期注释),当咱们的项目比较大的时候能够尝试打包多页,有些不是主页的页面主线程进行预加载,好比单独作个登陆页,启动比较快,复杂的页面先预加载不显示出来,要显示的时候直接win.show(),算是一个白屏优化吧。  
这里呢咱们简单点,就利用多页打包作loader页,先展现loader页,而后再加载咱们的主页。webpack

渲染进程loader

其实就是多页的建立方式,在src/loader新建多页对应的main.js和App.vueweb

主进程使用

/config/global.js
global.willQuitApp = false
global.tray = null
global.sharedObject = {
  win: ''
}

export default global

/config/index.js
const env = process.env

const config = {
  loading: true,
  winSingle: true,
  devToolsShow: true,
  VUE_APP_ENV: env.VUE_APP_ENV,
  NODE_ENV: env.NODE_ENV,
  VUE_APP_VERSION: env.VUE_APP_VERSION
}

if (config.VUE_APP_ENV === 'development') {
  config.devToolsShow = true
} else if (config.VUE_APP_ENV === 'test') {
  config.devToolsShow = true
} else if (config.VUE_APP_ENV === 'production') {
  config.devToolsShow = false
}

module.exports = config

主进程设置窗口建立,咱们把以前的src/main/index.js的窗口建立修改一下(createWindow部分):vue-cli

import config from './config/index'
import global from './config/global'
let win = null
let loaderWin = null

function initWindow() {
  if (config.loading) {
    loaderWin = createWindow({
      width: 400,
      height: 600,
      frame: false,
      backgroundColor: '#222',
      show: false,
      transparent: true,
      skipTaskbar: true,
      resizable: false,
      webPreferences: {
        experimentalFeatures: true,
        contextIsolation: false
      }
    }, 'loader', 'loader.html')
    loaderWin.once('ready-to-show', () => {
      loaderWin.show()
    })
    loaderWin.on('closed', () => {
      loaderWin = null
    })
  }

  win = createWindow({
    height: 810,
    minHeight: !isMac && process.env.VUE_APP_ENV === 'production' ? 810 - 20 : 810,
    width: 1440,
    minWidth: 1440,
    useContentSize: true,
    show: false,
    webPreferences: {
      contextIsolation: false,
      nodeIntegrationInSubFrames: true,
      webSecurity: false,
      webviewTag: true,
      enableRemoteModule: true,
      scrollBounce: isMac
    }
  }, '', 'index.html')
  win.once('ready-to-show', () => {
    loaderWin && loaderWin.destroy()
    win.show()
    // setTimeout(() => {
    //   loaderWin && loaderWin.destroy()
    //   win.show()
    // }, 2000)
  })
  global.sharedObject.win = win
  win.on('closed', () => {
    win = null
  })
}

async function onAppReady() {
  if (!process.env.WEBPACK_DEV_SERVER_URL) {
    createProtocol('app')
  }

  initWindow()
}

app.on('ready', onAppReady)

大多数属性能够参考官网的文档,这里只介绍有问题的:npm

  • ready-to-show是为了保证窗口加载没有白屏及闪烁。
  • frame是设置无边框窗口,正常状况下咱们的软件顶端是带有一个横条的,缩小,放大及关闭,还有拖动功能,设置为false能够除去掉这个横条(好比你想自定义这个)。
  • useContentSize:正常开发下,咱们的设计稿通常是文档的高度,这个是不包括上面说的那个横条的,若是为false的话,咱们设置的height就是整个软件的高度(也就是说咱们开发的文档高度:html内容=height-横条高度),若是设置为true的话height就是咱们开发的文档高度。
  • minHeight:minHeight这个东西其实是与Menu这个有所关联的,win和mac是有差别的,win的菜单是在横条下方,mac则是位于桌面左上角。通常来讲win的应用都是把Menu去掉的,为了方便调试,咱们区分了几个环境,只有生产包才去除Menu,,经实践去除Menu的需减去20文档才会和height一致(我也比较疑惑?),因此有此处理。
  • win.show()这里因为咱们的体积过小,加载很是快,致使loaderWin一闪而过看不到效果,因此能够在这里加个定时器延时看看效果。
  • 注意全部的BrowserWindow实例都请用全局变量赋值,好比win,loaderWin,这两个若是是局部变量的话,函数执行完毕就会销毁,那么咱们的窗口也会被销毁,同理electron一直存在的控件好比托盘等也是如此。

win菜单
mac菜单

单窗口启动软件

上面建立窗口完成了,可是打包完成后点击图标启动时咱们会发现一个问题,咱们每双击一次启动图标,都会启动一个窗口(这种常见于编辑器,能够试试vscode),可是通常状况呢,应该是双击启动图标,若是软件没有运行,就启动,若是咱们的软件在运行时聚焦到咱们的软件窗口。  
用专业术语来讲就是保证单实例机制,在第二个实例启动时激活主实例窗口的示例,咱们新建一个winSingle.jsapp

import { app } from 'electron'
import global from '../config/global'
const gotTheLock = app.requestSingleInstanceLock()

export default function() {
  // 点击图标启动时检测窗口是否存在,存在则打开
  if (!gotTheLock) {
    app.quit()
  } else {
    app.on('second-instance', () => {
      const win = global.sharedObject.win
      if (win) {
        if (win.isMinimized()) win.restore()
        if (win.isVisible()) {
          win.focus()
        } else {
          win.show()
        }
      }
    })
  }
}
// 在主线程引入使用
import winSingle from './services/winSingle'
if (config.winSingle) {
  winSingle()
}

这里经过app.requestSingleInstanceLock()判断应用程序实例是否成功取得了锁,咱们的程序在第一次启动时该值为true,此时会监听second-instance事件,当第二个实例被执行而且调用app.requestSingleInstanceLock() 时,这个事件将在你的应用程序的首个实例中触发,那么在这个事件里咱们可让咱们的窗口从新展现出来。当第二次启动时,该值为false,也就是直接退出改窗口,而且会触发咱们首个实例的second-instance事件。  
这里可能有同窗会问了,问什么要使用global.sharedObject.win而不直接把咱们的win传入winSingle使用呢,这里就有个坑了,若是使用win传入winSingle的方式只是执行了second-instance的绑定,并不会执行里面的事件,当second-instance触发时win并不存在,拿不到咱们窗口的实例,因此没法操做窗口,故咱们这里使用global.sharedObject.win,固然这个东西后面说窗口间的通讯也会用到。electron