一、优化原因

在书写笔记时,如果白板上的笔记量特别大,频繁刷新白板就会造成卡顿的情况。

二、优化思路

卡顿原因:
卡顿原因是每写一笔都需要重新绘制白板上所有的笔记,比较耗时的地方有两个,一个是生成贝塞尔曲线的计算过程,另一个是屏幕渲染的过程。
优化思路:
1、如何在笔记书写过程中避免一直刷新白板?
2、如果短时间内书写多条笔记,如何减少白板刷新次数?

三、笔记书写优化

3.1 如何实现只绘制正在书写的笔记?

做一个临时的绘制层,覆盖到白板上,在书写时,只绘制这个临时层,在书写结束时将临时绘制层移除,并刷新白板,这样就达到了只绘制正在书写部分的需求,并且用户是感知不到的。
临时层的生命周期由收到书写消息类型管理,消息类型分为开始书写、正在书写、书写结束,分别在这三个状态对临时绘制层进行创建、绘制、移除操作。
正在书写:
笔记性能优化记录 - 图1
书写完成:
笔记性能优化记录 - 图2
白板绘制的数据来源于笔记缓存,添加临时层不会影响当前的笔记缓存、信令、白板绘制这些逻辑,只是调整了白板的刷新时机。

3.2 如和解决多人同时书写的问题?

如果多人同时书写时,只要接收到新的书写笔记消息,就创建一个新的临时绘制层,用于绘制正在书写的笔记,例如1v6教室7个人同时书写,会有7个临时层覆盖在白板上同时绘制。
笔记性能优化记录 - 图3
在创建新的绘制层时,会根据该条笔记的shapeId进行缓存,接收到后续消息时根据消息的shapeId读取对应的绘制层进行绘制,当绘制结束时,再根据shapeId移除对应的绘制层。这样就保证了每一临时绘制层都有自己的生命周期,且互不影响。

3.3 如何保证临时层绘制时层级显示和白板最终绘制的层级一致?

造成书写中和书写结束笔记层级显示不一致的原因如下:
A和B两个人在写笔记,A先写,B在A书写过程中开始书写,正常层级关系是:B绘制内容在A绘制层上面
笔记性能优化记录 - 图4
如果B先结束绘制,B的绘制层会先移除,白板刷新,导致B绘制的内容显示在A的下面:
笔记性能优化记录 - 图5
等A结束后白板刷新,在白板上B绘制的内容在A的上面。
笔记性能优化记录 - 图6
为了避免造成这样的层级错乱的问题,需要等A和B都书写结束后再移除所有的层级并刷新白板,这样就不会造成书写中和书写完成笔记层级不统一问题。

四、问题处理

4.1 如何保证没有接收到书写结束消息导致临时层没有隐藏造成笔记错乱问题?

用户在大黑板上书写笔记,这时会创建临时绘制层,当来电话、点击home键、端闪退导致书写被打断,会导致接收不到书写结束的信令,也就不会调用临时绘制层移除和大黑板刷新,这时打开课件,临时绘制层会显示在课件上。
解决办法是每次大黑板刷新时,都要将临时绘制层移除,比如打开课件时会拉去历史笔记,然后刷新大黑板,这时将临时绘制层移除就不会有问题了

4.2 如何处理进入教室时对端正在书写的问题?

如果计入教室时,对端正在书写,首先接受到的消息类型时“正在书写”的类型,因为没有收到“开始书写”类型的消息,所有没有创建临时绘制层,这样的情况会在接收到“正在书写”消息时进行判断,判断是否有对应的临时层被创建,如果没有的话,则执行setNeedsDisplay方法,刷新白板,保证白板内容显示正确。

4.3 优化单笔绘制

由每次读取所有缓存点,计算生成一个完整的曲线,调整为每次只由缓存中最后一个点和新绘制的点,增量绘制。减少画笔绘制时的性能消耗。
比如下图中的曲线是有8个点绘制而成,全量计算是,绘制到第8个点时,通过八个点计算出这条曲线,然后刷新临时层绘制。增量绘制,比如绘制到第2个点时,生成第1个和第2个点区间的曲线,然后缓存,移动到第三个点时,生成第1个和第2个点区间的曲线,然后拼接到之前已生成的曲线上,然后刷新临时层绘制。
笔记性能优化记录 - 图7

4.4 如何处理短时间内多次书写笔记

没次手指在屏幕抬起时,延时0.5秒后再执行书写结束方法,刷新白板并移除临时绘制层。
如果0.5秒之内再次书写笔记,则取消执行书写结束方法,等手指抬起后再次延时执行书写结束方法。

4.5 性能对比

测试条件:魔法课堂正常运行、笔记画满屏幕、单笔绘制较长

|

iPad 4 1G内存 A6X iPad Air 2 2G内存 A8X
整体绘制 CPU:117%.
内存:105.1MB
CPU:109%.
内存:124.3.1MB
单笔全量绘制 CPU:63%.
内存:105.1MB
CPU:62%.
内存:105.1MB
单笔增量绘制 CPU:47%.
内存:105.1MB
CPU:39%.
内存:105.1MB

五、移动笔记优化

5.1 移动笔记卡顿原因

手指拖拽过程中,每移动一个点的距离,都会刷新一次白板。

5.2 优化方案

减少白板刷新次数,用临时层展示移动过程。
笔记性能优化记录 - 图8
移动开始:
1、将被移动的笔记颜色设置成透明(仅本地展示,不会发送给对端和服务端),刷新白板,隐藏被移动的笔记。
2、创建临时层,绘制被移动的笔记,添加到白板上
移动中:
数据层保持不变(实时更新数据并发送给对端),更新临时层的位置,展示移动过程
移动结束:
1、将被移动笔记颜色还原,刷新白板,显示真实笔记移动后的位置。
2、移除临时层

六、删除笔记优化

6.1 删除笔记卡顿原因

1、生成外接四边形计算时机问题,手指在屏幕上每移动一个点,都去重新创建了一遍外接四边形,需要减少计算次数
2、当前通过遍历笔记上所有点和手指在屏幕上的相对位置找到待删除笔记,如果一条笔记上的点特别多,需要循环很多次,造成CPU计算压力。
3、橡皮擦大小调整方案优化,当前方案是调大橡皮擦时,除手指在屏幕上的触摸点外增加额的外辅助点计算,造成了额外的计算量。
说明:使用系统方法判断贝塞尔曲线是否和点接触的前提条件必须是一条闭合的曲线才可以,但是笔记曲线是非闭合的,所以需要加入外接四边形来辅助判断。

6.2 优化方案

1、外接四边形计算优化
在手指接触到屏幕上时,创建所有曲线的外接四边形并缓存,在手指移动过程中通过缓存中的外接四边形进行判断,在手指移开屏幕时删除缓存数据。从而减少生成外接四边形的计算量。
2、优化遍历笔记所有点的计算
只有在笔记量特别小,比如只有几个点组成,在屏幕上只是一个点的情况,外接四边形无法使用时,才会通过遍历曲线上的点来找到待删除笔记。
3、橡皮擦大小调整优化
不去增加额外的辅助点,而是调整外接四边形的大小,从而减少寻找待删除笔记的计算量。
橡皮擦调整到最大效果:
笔记性能优化记录 - 图9
橡皮擦调整到最小效果:
笔记性能优化记录 - 图10
2.4.0版本优化
笔记性能优化记录 - 图11
优化点:
1、每条笔记有且只有一个外接图形,减少删除笔记时橡皮擦计算时循环次数,之前每条笔记添加多个平行四边行,需要循环多次,现在每条笔记计算时只需要循环1次,减少CPU压力。
2、笔记末端删除优化,之前版本没有对笔记末端添加扩展区域,橡皮擦调大时,在笔记末端删除会出现不灵敏问题,当前版本针对笔记末端添加扩展区域,保证橡皮擦碰撞的精度。
3、点少的笔记增加圆形扩展区域,删除通过点间距计算待删除笔记的算法。
优化方案:
获取两个点的笔顺,定义笔顺有上下左右四个方向,添加扩展点时,将笔顺方向左侧和右侧的点分别放在两个集合里,绘制出左侧和右侧扩展曲线,在笔记开始位置和结束位置,用二次贝塞尔曲线绘制弧线拼接左右两条曲线,完成一个封闭的扩展曲线。

6.3 抛点和补点

笔记性能优化记录 - 图12线是由点构成的,点是通过手指在屏幕上滑动捕获的,以iPad为例,普通iPad屏幕刷新率是60Hz,每秒最多捕获60个点,iPad Pro屏幕刷新绿是120Hz,每秒钟最多捕获120个点,为了手指以不同速度下在屏幕上移动捕获点的密度极本相同,做了抛点和补点的处理。
抛点
如果手指在屏幕上移动速度较慢,取点较多,这些无用点将会被抛弃掉,抛弃规则为间距小于M的点会被过滤掉,避免无用点消耗性能。
补点
如果手指在屏幕上移动速度较快,取点较少,会在两点间补充一些点,补点原则为两点间距内每M个距离补充一个点,避免快速滑动导致删除笔记失败。

七、整体替换绘制方案

image.png

7.1 为什么整体替换绘制方案?

原因一:2.4.0版本新增了图片笔记,展示图片有两种方案
方案一:通过常用的UI控件的方式展示图片,优势是使用灵活,无论移动、缩放、添加删除都比较方便,不影响其它笔记,但是必须使用分层显绘制方案,否则无法保证和其它笔记的相对层级。
方案二:使用DrawRect方法绘制。优势是不用修改当前的绘制方案,但是无论移动、缩放、添加、删除都需要重写DrawRect方法,会导致本页白板上的所有笔记被重绘,会造成卡顿。
原因二:调整方案风险与工作量分析
在前两个版本的基础上,以分层方式绘制笔记、图形、文本功能都以实现,但都是以临时绘制层出现的,如果替换绘制方案需保证临时绘制层在书写结束之后不被删除、移除整体绘制逻辑,并增加图片笔记类型的绘制。其次修改绘制方案对数据层没有影响,风险在可控范围内,且工作量不会很大。
综合以上两个原因,2.4.0采用分层绘制方案来绘制笔记。

7.2 DrawRect渲染方案和CAShapeLayer+UIBezierPath方案对比

DrawRect方案依赖CPU进行计算绘制,会增加CPU和内存压力。
可以将DrawRect的过程理解为一个生成图像的过程,内容越多,区域越大所消耗的性能也就越高。笔记都是增量添加到白板上的,但每次DrawRect都是重新绘制整个白板区域,不能增量的绘制。
CAShapeLayer+UIBezierPath方案使用GPU进行渲染,不会增加CPU压力。
每写一条新笔记,就添加一个新的绘制层到屏幕上,绘制该条笔记的区域和内容,不会重新绘制整个白板,形成一个增量绘制的过程。
笔记性能优化记录 - 图14