美文网首页
将electron桌面应用改造为Android和IOS平台可运行

将electron桌面应用改造为Android和IOS平台可运行

作者: 虎牙工务员刘波 | 来源:发表于2021-07-28 13:28 被阅读0次

    最近搞了一个项目改造工作,把electron桌面应用改造为在安卓或者ios上面可以运行的需求,学习到不少东西,同时网上几乎搜不到这种改造方案,就写下总结,给后来有需要的同学提供相关参考经验。

    原electron桌面应用技术栈背景:

    1、electron:跨端桌面应用支持,调用底层api等
    2、vue:web界面
    3、better-sqlite3:缓存支持
    4、脚手架:electron-vue
    改造可行性: electron应用也是在web项目上套了一层应用壳而已,所以移植到混合应用hybird上面,通过webview支持web显示也是可行的,所以!改造只要在浏览器上面跑得通就行!但是有些功能可能会稍有不同,需要做依据需求删除。

    一、改造前第一步要做的事就是根据需求,整理需要的改造点:

    为了做到最小改动量,我们第一步想到的就是只改造出入口逻辑,也就是修改底层调用,遵循业务逻辑一概不动的原则。
    需要改造功能点:
    1、下载功能:原本的文件下载,改为直接浏览器原生下载
    2、缓存存储功能:sqlite数据库缓存改为浏览器的indexedDB
    3、请求功能改造:原有在主进程里面的请求改为web请求
    4、新开窗口:electron新开弹窗改为web的弹窗

    二、进程通信方法改造:

    electron的请求是可以写在主进程,也可以写在渲染进程,由于我们之前写在渲染进程,所以需要在做改造,至于请求库使用fetch还是axios跟我们无关,因为都可以。
    我们先来看下,electron中的渲染进程和主进程请求通信:

    1、electron的同步通信
    //在主进程中
    ipcMain.on('synchronous-message', (event, arg) => {
      console.log(arg) // prints "ping"
      event.returnValue = 'pong'
    })
    
    //---------------------------------------------------------------------------//
    
    //在渲染器进程 (网页) 中
    const { ipcRenderer } = require('electron')
    cosnt data =  ipcRenderer.sendSync('synchronous-message', 'ping')
    console.log(data ) // prints "pong"
    
    改造为:
    //在主进程中
    import EventEmitter from 'events'
    const ee = new EventEmitter()
    ee.emit('synchronous-message', (arg, callback) => {
      console.log(arg) // prints "ping"
      event.returnValue = 'pong'
    })
    
    //---------------------------------------------------------------------------//
    
    //在渲染器进程 (网页) 中
    import EventEmitter from 'events'
    const ee = new EventEmitter()
    ee.on('synchronous-message', payload, callback)  
    

    注意:事实上electron的通信就是基于node的events原理

    2、electron的异步通信
    //在主进程中.
    const { ipcMain } = require('electron')
    ipcMain.on('asynchronous-message', (event, arg) => {
      console.log(arg) // prints "ping"
      event.reply('asynchronous-reply', 'pong')
    })
    
    //---------------------------------------------------------------------------//
    
    //在渲染器进程 (网页) 中。
    const { ipcRenderer } = require('electron')
    
    ipcRenderer.on('asynchronous-reply', (event, arg) => {
      console.log(arg) // prints "pong"
    })
    ipcRenderer.send('asynchronous-message', 'ping')
    
    改造为:
    //在主进程中.
    import EventEmitter from 'events'
    const ee = new EventEmitter()
    ee.on('asynchronous-message', (arg,callback) => {
      console.log(arg) // prints "ping"
      callback && callback('pong')
    })
    
    //---------------------------------------------------------------------------//
    
    //在渲染器进程 (网页) 中。
    import EventEmitter from 'events'
    const ee = new EventEmitter()
    
    ee.emit('asynchronous-message', 'ping', callback)
    
    //这里的函数回调函数来拿到监听的结果
    const callback = (data)=>{
    //dosomething
    }
    

    注意:异步的改造我们已经去掉了on的监听,改用回调函数去替代这个on方法,这是关键

    三、sqlite3改造为浏览器的indexedDB:

    这是idnexedDB的技术文档:https://wangdoc.com/javascript/bom/indexeddb.html
    贴一下参考代码:
    以下class类是数据库增删改查的对应4种办法,值得注意的是,因为indexedDB是异步操作所以需要使用promise,来等待异步完成,以及将对应的sql语句替换为indexdb的写法,这需要根据实际业务情况来。

    let IDBOpenRequest = null   //打开数据库连接对象
    let IDBDatabase = null   //连接的数据库,后面对数据库操作都通过这个对象完成
    const indexPrimaryKeys = 'KEY_TYPE'
    
    /**
     * 在创建好的数据库上新建仓库(即新建表)
     */
    const createTable = ({}) => {
      if (!IDBDatabase.objectStoreNames.contains('cache_records')) {
        let objectStore = IDBDatabase.createObjectStore('cache_records', { autoIncrement: true })
        //新建索引
        // objectStore.createIndex(indexName, keyPath, objectParameters)  语法
        objectStore.createIndex('key', 'key', { unique: false })
        objectStore.createIndex('type', 'type', { unique: false })
        objectStore.createIndex('last_modify', 'last_modify', { unique: false })
        objectStore.createIndex(indexPrimaryKeys, ['key', 'type'], { unique: false })
      }
      if (!IDBDatabase.objectStoreNames.contains('common_configs')) {
        const objectStore = IDBDatabase.createObjectStore('common_configs', { autoIncrement: true })
        objectStore.createIndex('setting_value', 'setting_value', { unique: false })
        objectStore.createIndex('setting_key', 'setting_key', { unique: true })
      }
    }
    
    /**
     * 打开数据库连接,并创建一个数据库对象
     * @param databaseName  数据库名称
     * @param version 版本号
     */
    function initIDB({ databaseName = 'myDatabase', version = 1 }) {
      console.log('|||||||||||---数据库初始化程序开始---||||||||||||')
      if (!indexedDB) {
        alert('indexdb does not support in this browser!')
        return
      }
      IDBOpenRequest = indexedDB.open(databaseName, version)
      //数据库的success触发事件
      IDBOpenRequest.onsuccess = function (event) {
        IDBDatabase = IDBOpenRequest.result //拿到数据库实例
        console.log('数据库打开成功')
      }
      //错误时候事件
      IDBOpenRequest.onerror = function (event) {
        console.log('数据库打开出错:', event)
      }
      //指定版本号大于实际版本号时候触发,它会优先执行
      IDBOpenRequest.onupgradeneeded = function (event) {
        IDBDatabase = event.target.result //若升级,此时通过这里拿到数据库实例
        createTable({})
      }
    }
    
    
    initIDB({})
    
    
    class Sqlite {
    
      /**
       * 处理不同字段情况下查询数据返回IDBRequest
       * @param objectStore
       * @param search
       * @returns {IDBRequest<IDBCursorWithValue | null>}
       */
      static returnIDBCursorForGetPrimaryKey({ objectStore, search }) {
        try {
          const getIndexKeyBySearchKey = Object.keys(search)
          const getIndexValuesBySearchValues = Object.values(search) || ['']
          const singleParams = getIndexKeyBySearchKey.length === 1
          const index = objectStore.index(singleParams ? getIndexKeyBySearchKey[ 0 ] : indexPrimaryKeys)
          return index.openCursor(IDBKeyRange.only(singleParams ? getIndexValuesBySearchValues[ 0 ] : [search.key, search.type]))
        } catch (e) {
          throw `openCursor查找方式出错!!!${e}`
        }
      }
    
    
      /**
       * 新增
       * @param {String} tableName 表名
       * @param {Object} value 插入的值 例如: {字段名: 值}
       */
      insert({ tableName, value = {} }) {
        const objectStore = IDBDatabase.transaction([tableName], 'readwrite').objectStore(tableName)
        let valueEdit = value.key ? value : { ...value, key: null }
        // objectStore.add(value, key)  语法
        const objectStoreRequest = objectStore.add(valueEdit)
        objectStoreRequest.onsuccess = function (event) {
          console.log('=====insert=====:数据写入成功', { tableName, value })
        }
        objectStoreRequest.onerror = function (event) {
          console.warn('数据写入失败!!!', event.target.error.message)
        }
      }
    
      /**
       * 查询表=>查询索引
       * @param {String} tableName 表名
       * @param {Object} search 查询条件 例如: {字段名1: 值, 字段名2: 值}
       */
      queryOne({ tableName, search }) {
        if (!search) return
        try {
          const objectStore = IDBDatabase.transaction([tableName]).objectStore(tableName)
          return new Promise(((resolve, reject) => {
            const request = Sqlite.returnIDBCursorForGetPrimaryKey({ objectStore, search })
            request.onsuccess = function (event) {
              console.log('=====queryOne=====:成功', { tableName, search }, request.result && request.result.value)
              resolve(request.result && request.result.value)
            }
            request.onerror = function (event) {
              console.warn('查询事务失败!', event.target)
              reject({})
            }
          }))
        } catch (e) {
          throw `查询出错了:${e}`
        }
      }
    
      /**
       * 查询多条
       * @param {String} tableName 表名
       * @param {Array} field 查询的字段 例如: [字段名1, 字段名2]
       * @param {Object} search 查询条件 例如: {字段名1: 值, 字段名2: 值}
       */
      query({ tableName, field, search }) {
        const data = []
        const list = []
        const objectStore = IDBDatabase.transaction([tableName]).objectStore(tableName)
        if (field && field.length > 0) {  //向上兼容,保持调用方法不变
          const index = objectStore.index('type')
          return new Promise((resolve) => {
            index.openCursor().onsuccess = function (event) {
              let cursor = event.target.result;
              if (cursor) {
                list.push(cursor.value)
                cursor.continue();
              } else {
                list.filter(item => item.type === search.type).forEach(item => {
                  if (item && item.key) data.push({ key: item.key, last_modify: item.last_modify })
                })
                console.log('======query=====:=查询所有=', { tableName, field, search }, data)
                resolve(data)
              }
            }
          })
        } else {
          return new Promise((resolve, reject) => {
            objectStore.openCursor().onsuccess = function (event) {
              const cursor = event.target.result;
              if (cursor) {
                data.push(cursor.value)
                cursor.continue();
              } else {
                console.log('======query=====:=查询所有=', { tableName, field, search }, data)
                resolve(data)
              }
            };
          })
        }
      }
    
      /**
       * 删除
       * @param {String} tableName 表名
       * @param {Object} search 删除条件 例如: {字段名: 值}
       */
      async delete({ tableName, search }) {
        const objectStore = IDBDatabase.transaction([tableName], 'readwrite').objectStore(tableName)
        const request = Sqlite.returnIDBCursorForGetPrimaryKey({ objectStore, search })
        request.onsuccess = function (event) {
          const objectStoreRequest = objectStore.delete(event.target.result && event.target.result.primaryKey)
          objectStoreRequest.onsuccess = function (event) {
            console.log('=====delete=====:删除成功🙂', { tableName, search }, event.target.result)
          }
        }
      }
    
      /**
       * 修改
       * @param {String} tableName 表名
       * @param {Object} field 更新的字段 例如: {字段名: 值}
       * @param {Object} search 查询条件 例如: {字段名: 值}
       */
      async update({ tableName, field, search }) {
        try {
          const objectStore = IDBDatabase.transaction([tableName], 'readwrite').objectStore(tableName)
          const request = Sqlite.returnIDBCursorForGetPrimaryKey({ objectStore, search })
          request.onsuccess = function (event) {
            // objectStore.put(item, key) 语法
            const objectStoreRequest = objectStore.put(field, event.target.result && event.target.result.primaryKey)
            objectStoreRequest.onsuccess = function (event) {
              console.log('=====update=====:成功', { tableName, search })
            }
          }
        } catch (e) {
          console.warn('update失败!')
        }
      }
    
    
    }
    
    export default Sqlite
    
    

    相关文章

      网友评论

          本文标题:将electron桌面应用改造为Android和IOS平台可运行

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