前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >chromium与markdown极简笔记多线程文本渲染

chromium与markdown极简笔记多线程文本渲染

作者头像
ACM算法日常
发布2021-06-16 16:25:23
7240
发布2021-06-16 16:25:23
举报
文章被收录于专栏:ACM算法日常

最近我的markdown笔记软件做了一次升级,升级内容主要是将单线程的文本渲染做成了多线程的,这样避免了笔记打开时候卡顿的情况。本篇聊一下如何做多线程的文本渲染,以及如何使用chromium的基础模块进行跨平台开发,对于做App客户端、游戏客户端的同学还是比较有实际意义的。

对于一个App来说,用户操作优先级是最高的,也就是说,理想状态下,用户的任何操作都需要立即得到反馈,特别是对于耗时比较久的操作,比如下载文件、渲染大型场景,一般会增加一个loading动画或者进度条之类的元素。

如果这种耗时操作在主线程(一般是UI线程)执行,程序会发生假死的情况,任何点击都不会响应,对于用户来说这种体验非常糟糕,这是最严重的情况,其次是卡顿现象,比如我的笔记软件,在加载一篇一万字左右的笔记的时候会有几百毫秒的卡顿现象,这种体验虽然能够接受,但是总给人一种卡卡的感觉。这个并不是程序性能慢,而是没有用多线程,没有将加载和显示进行拆解。

单线程渲染

单线程渲染是指从加载文本开始,一直到文本显示在屏幕上,都是主线程来处理所有逻辑。

这个过程中可能耗时较长的操作主要是

  • 加载大型文本
  • 解析文本为树形结构
  • 插入到底层富文本,根据字体大小等样式计算文本宽高
  • 渲染引擎根据layout进行文本图片表格的绘制

对于单线程的富文本的渲染,一般分为3个步骤:

  1. 解析markdown,生成一棵dom节点树;
  2. 通过dom节点,依次插入到富文本接口中,比如文本调用insertText方法,图片和公式调用insertImage方法,表格调用insertTable方法。
  3. 渲染文本。

显然,一旦文本结构复杂且很大,文本的渲染就会卡顿。

多线程渲染

多线程的思路就是将可能卡顿的地方放到其他线程中处理。

对于多线程的流程,可以分为如下几步

  • 主线程准备进行文本加载,将文本内容和发送给另外一个线程b
  • 线程b开始解析文本
  • b线程中生成一个文档对象d,插入解析后的数据结构
  • b线程将生成好的文档对象d传给主线程,主线程通过这个对象进行渲染工作

这个过程可以细化,做成文本分段传给主线程,这样主线程能够即时渲染开头部分的文本内容,即使几百mb的文本也不会体验很差。

通过异步操作,原来单线程中需要一秒钟加载完的笔记,现在只会卡顿20多毫秒。另外这种做法还使得逻辑解耦,因为每一步的数据都是独立的相互之间没有影响。另外单线程文本插入过程中会产生大量的layout重算和UI回调以及渲染节点的修改,导致性能非常差,就相当修改一个已经在线产品,会影响很多用户一样,而多线程是在独立线程进行文本插入,这种操作不涉及UI回调,渲染进程不需要更新还没计算完的layout,因此性能会好很多。

多线程看起来是复杂了,但是对于整个流程的控制却相当清晰,而且性能提高了20到30倍。

chromium多线程模型讨论

上面说的多线程处理是使用chromium的base库做的,chromium用重锤砸核桃的方式写了一个多线程模型,这个模型的主要功能是线程间通信,每一个线程交互都是一个task,这个task是一个对象,可以带参数,传递到别的线程队列中,执行的时候可以带参数。

base库比较高效的原因主要是使用了系统接口作为队列,比如Windows下使用纯消息窗口进行消息循环(HWND_MESSAGE不需要UI显示),

在mac、安卓、ios都是使用类似的方式创建消息循环,这种方式作为事件驱动有一个好处是由操作系统控制队列的性能,这样对于系统更加友好,也会更加高效。如果自己在线程内部写一个死循环,看起来不费性能,但是这就像操作系统是一个管家,每个进程的线程都是一群孩子,如果每个孩子都一起向管家要糖吃,管家就不知道要给哪个孩子糖吃,但是这群孩子如果排队,那么系统运行就会很顺畅。

base库另外一个出彩的地方是task的封装,这个封装简直完美,用起来非常方便。

如下图所示:

代码语言:javascript
复制
// 插入图片的事情交给底层统一处理
auto task = base::Bind(&MRendererImageNode::QueueTaskInsertImage, base::Unretained(this), tree->getTextEdit(), src);

if(tree->IsRenderWithThread()){
    addDocumentQueueTask(tree->getTextId(), task);
}else{
    task.Run();
}

base::Bind函数的第一个参数是类成员函数,第二个参数是对象,后面2个参数是函数参数。函数创建一个对象task,我们可以在别的线程中调用task.Run()方法,Run方法可以带要运行的函数参数。

这个实现是使用C++的模板来实现的,实现细节非常复杂,需要对模板技术非常熟悉才能写得好这样的接口。

结语

本篇是极简笔记多线程文本渲染的开发总结,如果你也对富文本编辑器感兴趣,可以持续关注ACM算法日常,我打算把富文本的开发细节做成一个系列,以便后来人能够非常轻松的解决富文本编辑器问题。

后续引子

App程序的开发可以复杂如chrome浏览器,富文本是其中比较复杂的一种App,对于我这种强迫症患者来说,开发一个笔记App必须跨平台、秒速启动、运行流畅、用户操作符合系统习惯,目前极简笔记还是差了很多,也因此我一直在探索如何让程序更好的运行。当前版本的极简笔记采用QT框架开发,然而QT的技术很难做到极致,也因此我产生了一个新的思路:

富文本的核心部分可以采用QT现有的数据结构,然而渲染层最好能够嵌入到各个平台的本地接口中,比如Windows下面可以使用duilib作为窗口和控件管理,自定义一个文本渲染层,对接到duilib控件中,IOS和安卓用系统本地语言开发界面,自定义渲染层对接到view中,这样能够做到App本地高效运行,又能跨平台使用同一套富文本底层框架。

这就像一套组合拳,组合了chromium的base库、QT的富文本数据结构和layout接口、duilib的窗口管理、各平台本地开发接口等,渲染层抽象出来可以使用各平台本地渲染,也可以切换为跨平台的skia图形引擎。

让我们一起拭目以待吧。

ps:公众号输入note获取下载地址哦。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-06-02,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 ACM算法日常 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 单线程渲染
  • 多线程渲染
  • chromium多线程模型讨论
  • 结语
  • 后续引子
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档