從零開始的electron開發-更新-增量更新(二)

更新-增量更新(二)

很差意思,鴿了挺久了,上一期咱們使用app.asar.unpacked完成了electron的增量更新,本期咱們介紹的是如何使用exe替換asar來實現增量更新。
本期內容是基於上一期的內容來說解的,且主要針對於Windows系統(mac系統能夠對app.asar修改)vue

替換難點

  1. 在使用了asar後,Windows系統的Electron應用啓動後其app.asar會被佔用,不能對其進行修改和刪除,必須結束Electron應用的進程後才能夠對其進行修改。
  2. 因爲Windows的uac限制,若是安裝在C盤的話,修改app.asar會有權限問題。

解決思路

其實呢子線程也能夠跑node,可是呢因爲主進程結束了咱們並無node環境運行node命令,因此此方法不通,固然你也可添加一個編譯好的node運行子線程js,可是體積問題得不償失。
在Windows下咱們能夠用批處理文件bat來處理文件,可是bat仍是有uac以及執行時會有cmd窗口,咱們能夠把寫好的bat文件轉換爲exe文件來解決這些問題。node

  1. 咱們可使用node衍生獨立存在的子進程,把子進程和主進程分離開,結束掉Electron應用的進程,用這個子進程進行替換。
  2. uac限制可使用exe文件來獲取管理員權限進行處理。

解決方案

1. 打包修改

咱們這裏去除以前的app.asar.unpacked打包,把以前vue.config的註釋掉,這樣咱們打包就只有asar文件了git

// extraResources: [{
//   from: "dist_electron/bundled",
//   to: "app.asar.unpacked",
//   filter: [
//     "!**/icons",
//     "!**/preload.js",
//     "!**/node_modules",
//     "!**/background.js"
//   ]
// }],
// files: [
//   "**/icons/*",
//   "**/preload.js",
//   "**/node_modules/**/*",
//   "**/background.js"
// ],

2. 構建增量zip

上一期構建增量zip時使用了afterPack鉤子,這裏咱們對其修改,把新版本的app.asar重命名爲update.asar,放入增量的app.zip包裏,不瞭解的能夠看看上一期的內容github

const path = require('path')
const AdmZip = require('adm-zip')
const fse = require('fs-extra')

exports.default = async function(context) {
  let targetPath
  if(context.packager.platform.nodeName === 'darwin') {
    targetPath = path.join(context.appOutDir, `${context.packager.appInfo.productName}.app/Contents/Resources`)
  } else {
    targetPath = path.join(context.appOutDir, './resources')
  }
  const asar = path.join(targetPath, './app.asar')
  fse.copySync(asar, path.join(context.outDir, './update.asar'))
  var zip = new AdmZip()
  zip.addLocalFile(path.join(context.outDir, './update.asar'))
  zip.writeZip(path.join(context.outDir, 'app.zip'))
  fse.removeSync(path.join(context.outDir, './update.asar'))
}

3. 模擬接口

同上一期,咱們這裏修改了upDateUrl和upDateExe,upDateUrl是增量zip,upDateExe是咱們用來替換asar的exe文件。vue-cli

{
  "code": 200,
  "success": true,
  "data": {
    "forceUpdate": false,
    "fullUpdate": false,
    "upDateUrl": "http://127.0.0.1:4000/app.zip",
    "upDateExe": "http://127.0.0.1:4000/update.exe",
    "restart": false,
    "message": "我要升級成0.0.2",
    "version": "0.0.2"
  }
}

4. 加載策略修改

這裏咱們再把加載策略修改成加載app.asar裏的文件shell

// createProtocol('app', path.join(resources, './app.asar.unpacked'))
createProtocol('app')

5. 渲染進程增量更新

這個沒變更,同上一期app

6. 主進程處理

咱們把以前的主進程下載修改一下,先判斷update.exe是否存在,不存在的話先下載update.exe放入app.getPath('userData')裏:electron

win:C:\Users\Administrator(你的用戶)\AppData\Roaming\<app name>\
mac:/Users/(你的用戶)/Library/Application Support/<app name>

app.getPath('userData')這個路徑呢,比較特殊,安裝以後就存在了,是數據文件,你的indexDB,localStorage等都存在這裏面,軟件的全量更新,卸載都不會改動這個文件裏的東西,
咱們把update.exe放入這裏,避免全量更新後從新安裝。以後下載增量更新包解壓到resourcesh中(update.asar),也就是和app.asar同級目錄,刪除zip包,運行app.exit(0)關閉主進程async

import downloadFile from './downloadFile'
import { app } from 'electron'
const fse = require('fs-extra')
const path = require('path')
const AdmZip = require('adm-zip')

export default async (data) => {
  const resourcesPath = process.resourcesPath
    if (!fse.pathExistsSync(path.join(app.getPath('userData'), './update.exe'))) {
      await downloadFile({ url: data.upDateExe, targetPath: app.getPath('userData') })
    }
    downloadFile({ url: data.upDateUrl, targetPath: resourcesPath }).then(async (filePath) => {
      const zip = new AdmZip(filePath)
      zip.extractAllToAsync(resourcesPath, true, (err) => {
        if (err) {
          console.error(err)
          return
        }
        fse.removeSync(filePath)
        app.exit(0)
      })
    }).catch(err => {
      console.log(err)
    })
}

7. 子進程處理

主進程關閉後會觸發quit事件,咱們在這個事件裏檢測update.exe以及update.asar是否同時存在,
同時存在的話咱們用spawn開啓一個子進程運行咱們的update.exe,而且傳入resourcesPath(app.asar所在目錄路徑),app.getPath('exe')(咱們軟件的啓動路徑),
使用child.unref()讓子進程和父進程分離,能夠不退出子進程的狀況下退出父進程。ui

const { spawn } = require('child_process')
const fse = require('fs-extra')
const fs = require('fs')
const resourcesPath = process.resourcesPath

app.on('quit', () => {
  console.log('quit')
  if (fse.pathExistsSync(path.join(app.getPath('userData'), './')) && fse.pathExistsSync(path.join(resourcesPath, './update.asar'))) {
    const logPath = app.getPath('logs')
    const out = fs.openSync(path.join(logPath, './out.log'), 'a')
    const err = fs.openSync(path.join(logPath, './err.log'), 'a')
    const child = spawn(`"${path.join(app.getPath('userData'), './update.exe')}"`, [`"${resourcesPath}"`, `"${app.getPath('exe')}"`], {
      detached: true,
      shell: true,
      stdio: ['ignore', out, err]
    })
    child.unref()
  }
})

也就是說這裏是父進程退出後,子進程執行咱們的exe,替換app.asar,out和err是將子進程執行的日誌重定向到app.getPath('logs')中,這個路徑和electron-log不同(你也能夠本身設置爲electron-log路徑同樣)

win:C:\Users\Administrator(你的用戶)\AppData\Roaming\<app name>\<app productName>\logs
mac: ~/Library/Logs/<app name> ?應該是這個下面的,這個我沒驗證

8. 構建exe

準備工做完成了,這裏咱們編寫exe,其實這個沒啥難度的,咱們使用bat腳本打包成exe就行。
update.bat

@echo off
timeout /T 1 /NOBREAK
del /f /q /a %1\app.asar
ren %1\update.asar app.asar
start "" %2

簡單解釋一下吧,%1和%2爲運行腳本傳入的參數,好比update.bat aaa bbb,那麼%1爲aaa,%2爲bbb,上面咱們用spawn運行exe時傳入的,
也就是%1爲resourcesPath,%2爲軟件的啓動exe,咱們運行bat腳本,先暫停1秒鐘保證主進程退出了,而後刪除app.asar,將update.asar重命名爲app.asar,啓動軟件exe。
一個簡單的bat替換就完成了,咱們下載Bat To Exe Converter這個軟件,將update.bat轉換爲update.exe,而後將update.exe放入咱們的http-server目錄中。運行軟件檢測更新,看看更新是否完成。
exe

補充說明

spawn(`"${path.join(app.getPath('userData'), './update.exe')}"`, [`"${resourcesPath}"`, `"${app.getPath('exe')}"`], {
  detached: true,
  shell: true,
  stdio: ['ignore', out, err]
})

這裏有同窗可能會有疑問,爲何要在幾個路徑外加一個"",這是因爲node運行腳本的路徑名中包含空格的話,須要加上引號,bat處理也同樣,好比咱們的軟件安裝在c盤,
C:\Program Files\electronVueDEV,最多見的問題就是Program Files這裏有個空格,這會致使bat命令裏有這樣的路徑的話會處理失敗,因此咱們的路徑都加了引號的。

本系列更新只有利用週末和下班時間整理,比較多的內容的話更新會比較慢,但願能對你有所幫助,請多多star或點贊收藏支持一下

本文地址:連接
本文github地址:連接

相關文章
相關標籤/搜索