Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

electron 截屏功能性能优化 #286

Open
xiongxt opened this issue Jun 16, 2019 · 2 comments
Open

electron 截屏功能性能优化 #286

xiongxt opened this issue Jun 16, 2019 · 2 comments

Comments

@xiongxt
Copy link

xiongxt commented Jun 16, 2019

最近在做一个electron的项目,需要一个截屏的功能,这功能网上已经有很多完成的代码和参考资料,我是参照了:https://juejin.im/post/5bbac5cee51d450e7042ad2c 这个文档做了一个出来,但是发现一个问题,就是点了截屏到真正弹窗渲染出来,大概时间要1秒多,这个效率是很难接受的,所以就针对这问题进行一翻研究。

  • 首先是截屏的原理
    截屏原理就是,先把屏幕大图截出来,然后再打开一个全屏的browserWindow,将截图的内容画到这个window上,然后用canvas进行截取保存发送

  • 截屏的第一步
    第一步就是获取屏幕截图,这个功能是依赖于electron的desktopCapturer模块,由于这个模块只能在渲染线程运行,所以必须在渲染线程调用截图再传递到主线程,经过测试在mac上仅仅是这一步就耗时700ms左右,windows平台性能好点。截图的代码如下:

electron.desktopCapturer.getSources(
    {
        types: ["screen"],
        thumbnailSize: {
            width: 1920,
            height: 1080
        }
    },
    (err, sources) => {}
); 
  • 分析一下
    浏览器本身是没有截屏功能的,那这个desktopCapturer是怎么做到截图的呢,按道理所截屏这个功能应该属于主线程的啊。我们来看看真像:
exports.getSources = function (options, callback) {
  if (!isValid(options)) return callback(new Error('Invalid options'))
  const captureWindow = includes.call(options.types, 'window')
  const captureScreen = includes.call(options.types, 'screen')

  if (options.thumbnailSize == null) {
    options.thumbnailSize = {
      width: 150,
      height: 150
    }
  }

  const id = incrementId()
  ipcRenderer.send('ELECTRON_BROWSER_DESKTOP_CAPTURER_GET_SOURCES', captureWindow, captureScreen, options.thumbnailSize, id)
  return ipcRenderer.once(`ELECTRON_RENDERER_DESKTOP_CAPTURER_RESULT_${id}`, (event, sources) => {
    callback(null, (() => {
      const results = []
      sources.forEach(source => {
        results.push({
          id: source.id,
          name: source.name,
          thumbnail: nativeImage.createFromDataURL(source.thumbnail),
          display_id: source.display_id
        })
      })
      return results
    })())
  })
}

就是这么粗暴的向主线程发送了一个消息,然后主线程把截好的图传递给渲染线程。既然是主线程干的截图的事,为什么又不把这个api开放让用户自由选择呢,现在是按官方的方法做出来的截屏功能,这个图片得在主线程和渲染线程之间来回的传递几次才能最终渲染出来,怪不得等待时间那么长了。

  • 不信邪
    既然本身截屏本身就是在主线程创建的,为什么不能直接在主线程拿到图片呢,为了彻底搞清楚这个问题,只有将electron的源码下载下来,看看源码是怎么搞的,看看是否有什么内置的对象可以完成这个功能。皇天不负有心人还终于被我找到了,其中过程不表,上代码:
const desktopCapture =  process._linkedBinding('atom_browser_desktop_capturer').desktopCapture;

function startscreencut() {
  let emitter = new EventEmitter();
  emitter.once('finished', (event, sources, fetchWindowIcons) => {
  // sources就是获取到的截图信息
  })
  desktopCapture.emit = emitter.emit.bind(emitter);
  desktopCapture.startHandling (false, true, {width:1920, height:1080}, false);
}

这样就能直接在主线程获取到截图的信息啦,然后就是讲图片发送到截图的window就大功告成了,再来测测性能呢,从开始到获取到图片信息的实践缩短到300-400ms了。

另外说一句,process._linkedBinding这里面内置了很多的内部对象,如果大家遇到什么瓶颈或者官方api没有的功能或许可以在这里面找找会有意外的收获。

  • 拿到图片信息这一步优化完了,另外一个耗时的重点就是渲染一个新的window了,我这里采用的方案是预初始化一个browserWindow,用的之后直接显示,关闭的时候就隐藏。这样一套优化下来,基本能保证能在1s内打开截图的弹窗,虽然感觉还是有点慢,不过也能勉强接受了。
@Merry9
Copy link

Merry9 commented Dec 11, 2023

感谢楼主的优化思路!我现在使用的 Electron 11 版本,已经可以直接在主进程使用 desktopCapturer.getSources了,并且官网显示在 Electron 17版本,我们只能在主进程使用 desktopCapturer.getSources 了
https://www.electronjs.org/zh/blog/electron-17-0#%E4%B8%BB%E8%A6%81%E7%89%B9%E6%80%A7
主进程几乎优化了一半的时间

@xpl028
Copy link

xpl028 commented Dec 11, 2023 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants