electron之remote模块探究

remote 模块为渲染进程(web页面)和主进程通信(IPC)提供了一种简单方法。

通俗讲就是 remote 模块让仅在主进程中可用的一些模块,比如app, 通知dialog,渲染进程也可以调用。举个例子:

 remote.dialog.showMessageBox({
	 type: 'info',
	 message: '提交成功!'
 });

这么看 remote 模块可以说是非常好用啦,渲染进程就不用显式通过 ipcRenderer / ipcMain 与主进程通信。除此之外,可以通过 remote.getGlobal 获取主进程中 的全局变量, 通过 remote.process 拿到主进程的 process 对象信息

remote 模块返回的每个对象 (包括函数) 表示主进程中的一个对象 (我们称它为远程对象或远程函数)。 当调用远程对象的方法时, 调用远程函数, 或者使用远程构造函数 (函数) 创建新对象时, 实际上是在发送同步进程消息。 Electron 确保只要渲染进程中的远程对象一直存在(换句话说,没有被回收),主进程中的相应对象就不会被释放。 当远程对象被垃圾回收后,主进程中的相应对象将被解除引用。

发送同步进程消息,会阻塞页面渲染?当主进程中的相应对象发生改变,渲染进程中的远程对象是否会对应改变?

从现象看:
1、通过计算器从拉取数据开始处于卡顿状态,直到遍历完返回数据继续计数
2、主进程的变量tips/tipObj改变,渲染进程中的变量tips不变,tipObj会对应改变

具体代码逻辑:
渲染进程(页面)

import React, { useEffect, useState, Fragment } from 'react';
import { Button } from 'antd';
import { useIpcRenderer } from '../../hooks/useIpcRenderer';

const { remote } = window.require('electron');

let tips: string;
let tipObj = {};

const Demo: React.FC = () => {
    const [num, setNum] = useState<number>(0);
    
    const loadData = () => {
        tips= remote.getGlobal('tips');
        tipObj = remote.getGlobal('tipObj');
        console.log("Before change:", tips, JSON.stringify(tipObj));

        console.log("load data start");
        console.time("1");
        const loadData = remote.getGlobal('loadData');
        console.log("load data end");
        console.timeEnd("1");

        console.log("literate data start");
        console.time("2");
        const list = loadData();
        list.forEach((item:Object) => {
            console.log(JSON.stringify(item));
        });
        console.log("literate data end");
        console.timeEnd("2");
    }
	//计数器
    useEffect(() => {
        let _val:number = 0;
        setInterval(() => {
            setNum(++_val);
        }, 500);
    }, [])

    useIpcRenderer({
        changeTips: (evt: Event, val: any) => {
            console.log("After changed:", tips, JSON.stringify(tipObj));
        }
    })

    return (
        <Fragment>
            <h1 
                style={{display: 'flex', justifyContent: 'center', height: '100px'}}
            >
                Counter:{num}
            </h1>
            <Button type="primary" onClick={() => loadData()}>Load Data</Button>
        </Fragment>
    )
}

export default Demo;

主进程

...

global.tips = 'It\'s 2019';
global.tipObj = {
    content: 'It\'s 2019'
};
global.loadData = () => {
	let ret = [];
	for (let i = 0; i < 1000; i ++) {
		let obj = {}
		for (let j = 0; j < 15; j ++) {
			obj[j] = j;
		}
		ret.push(obj)
	}

	setTimeout(() => {
		tips = '2020 is coming!';
		 tipObj.content = '2020 is coming!';
		this.win.webContents.send('changeTips');
	}, 8000);

	return ret;
}
...

我们深入 remote 源码找找上边现象的原因:

v7.1.2 electron/lib/renderer/api/remote.js:

...
// Alias to remote.require('electron').xxx.
//发送一个同步信息到主进程,获取模块对象,对返回数据的具体处理见metaToValue函数
exports.getBuiltin = (module) => {
  const command = 'ELECTRON_BROWSER_GET_BUILTIN'
  const meta = ipcRendererInternal.sendSync(command, contextId, module, getCurrentStack())
  return metaToValue(meta)
}

...
// 将仅主进程可用模块browserModules添加给remote模块
const addBuiltinProperty = (name) => {
  Object.defineProperty(exports, name, {
    get: () => exports.getBuiltin(name)
  })
}

const { commonModuleList } = require('@electron/internal/common/api/module-list') //渲染进程和主进程都可用模块
const browserModules = commonModuleList.concat(require('@electron/internal/browser/api/module-keys')) //仅主进程可用模块

// And add a helper receiver for each one.
browserModules
  .filter((m) => !m.private)
  .map((m) => m.name)
  .forEach(addBuiltinProperty)
  

metaToValue函数:

// Convert meta data from browser into real value.
function metaToValue (meta) {
  const types = {
    value: () => meta.value,
    array: () => meta.members.map((member) => metaToValue(member)),
    buffer: () => Buffer.from(meta.value.buffer, meta.value.byteOffset, meta.value.byteLength),
    promise: () => Promise.resolve({ then: metaToValue(meta.then) }),
    error: () => metaToError(meta),
    exception: () => { throw metaToError(meta.value) }
  }
  
	//对基本的对象包括数据只是做值的拷贝操作
  if (meta.type in types) {
    return types[meta.type]()
  } else {
  //对返回对象进行缓存,避免重复获取
    let ret
    if (remoteObjectCache.has(meta.id)) {
      v8Util.addRemoteObjectRef(contextId, meta.id)
      return remoteObjectCache.get(meta.id)
    }
	
	/**对函数类型包了一层函数remoteFunction用于发送同步的进程间信息,并将remoteFunction返回给渲染进程
	**/
    // A shadow class to represent the remote function object.
    if (meta.type === 'function') {
      const remoteFunction = function (...args) {
        let command
        if (this && this.constructor === remoteFunction) {
          command = 'ELECTRON_BROWSER_CONSTRUCTOR'
        } else {
          command = 'ELECTRON_BROWSER_FUNCTION_CALL'
        }
        const obj = ipcRendererInternal.sendSync(command, contextId, meta.id, wrapArgs(args))
        return metaToValue(obj)
      }
      ret = remoteFunction
    } else {
      ret = {}
    }
	
	/**
	对对象类型也做了类似函数的操作,并且对返回对象属性重写 get、set 方法。当调用远程对象上的属性或者给远程对象的属性赋值,
	都会发送同步的进程间消息,所以主进程修改对象的值,渲染进程可以同步到值的改变,详见setObjectMembers函数
	*/
    setObjectMembers(ret, ret, meta.id, meta.members)
	...
	
    remoteObjectCache.set(meta.id, ret)
    return ret
  }
}

而remote.getGlobal实际也是进行一个同步请求通信操作

// Get a global object in browser.
exports.getGlobal = (name) => {
  const command = 'ELECTRON_BROWSER_GLOBAL'
  const meta = ipcRendererInternal.sendSync(command, contextId, name, getCurrentStack())
  return metaToValue(meta)
}

小结

remote 模块实际作用是帮我们做了IPC操作,通过发送同步请求实现主线程/渲染进程的数据传递

  • 10
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值