小程序端海报绘制中图片空白问题

小程序中存在绘制海报需求,大致是请求多张Web图片,合成绘制海报,用户可以保存到手机,然后分享朋友圈等。

当前存在的问题是有时候绘制时发现黑屏。因此这里分析解决下。

黑屏效果如下

https://static.1991421.cn/2022/2022-05-29-105925.jpeg https://static.1991421.cn/2022/2022-05-29-110140.jpeg

正常应该是这样

https://static.1991421.cn/2022/2022-05-29-105718.jpeg

分析

  1. 并不是绘图库Kujiale-Mobile/Painter绘制GIF时使用的帧不同问题,比如当前的GIF,没有一帧是纯黑色。且复现时有可能不是该GIF没绘制,而是其它图片没绘制的问题。
    • 针对Canvas下将动态GIF转静态图片,均会是第一帧。
    • 如果想根据GIF转出指定帧,技术上可行,参考jsgif-包实现,原理就是GIF文件二进制数据获取到指定帧内容即静态图片。
  2. 真机测试抓包发现,黑屏的图片均没有找到对应请求,换一个海报绘制,比如简单图片即大小较小的尝试,发现问题不复现。
    • 所以实际上这些黑屏的图片在组件逻辑上是使用的本地缓存的临时文件

因此粗粒度的结论是绘图组件/小程序临时文件机制大小,这里出现的问题。

修复

基于分析部分,至少可以看出Painter库多少也是有问题的,尝试了下最新版的Painter组件发现问题不复现了,这也验证了推断。

Painter库问题点?

问题得到了解决,但Painter具体是哪里出了问题呢?

painer的绘制逻辑

  1. background,views中的image根据URL下载对应资源到小程序的临时文件位置wx.downloadFile,之后再wx.saveFile保存到本地路径,作为本地缓存文件

  2. 在存储逻辑上使用了LRULeast recently used 最近最少使用算法

    数据结构设计如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    {
    'key': {
    'path': // 文件的存储路径
    'time': // 时间戳,用来记录文件的最后访问时间,当存储不够时,会选择最远未被访问的文件进行删除
    'size': // 文件大小
    }
    ....
    'totalSize': // 所有存储文件的当前总大小
    }

  3. ctx.draw绘制,使用的canvas

  4. 图片保存到临时文件下,同时获取路径canvasToTempFilePath

模拟执行过程

  1. 绘图模版是没有background,有两张图片,第一张为GIF 23.5MB,第二张为PNG 3.6KB

  2. 绘制时因为缓存表savedFiles表中没有数据,所以直接异步下载GIF和PNG,GIF由于体积大比PNG下载慢些。所以会是PNG先下载完成,PNG只有3.6KB,缓存表记录还是空,所以小于6MB限制大小,因此LRU检测没有命中数据,直接更新到存储表savedFiles里,此时当前只有PNG这条记录,totalSize就是3.6KB。GIF下载OK后,进行LRU检测发现加上GIF体积会超过6MB,因此表记录按照时间排序,删除记录直到小于6MB,因为GIF超过6MB,所以所以表里记录及关联的临时文件都会删掉。

    按理说最终绘制的海报应该是GIF OK,PNG不显示。因为PNG在最后一刻因为GIF触发的LRU判断会被删掉。但是实际测试发现会有时出现都显示。目前的解释也只能是删除文件是单独的异步操作,不block绘制海报的流程,所以在绘制海报时,如果文件删除执行慢,文件还没删除,就会出现海报显示OK。当然这种概率高低就取决于微信了。

    该场景对照问题图片正常的样子

    https://static.1991421.cn/2022/2022-05-29-105247.jpeg

  3. 当用户第二次进入海报绘制页面,还是先发起下载GIF,然后紧接着发起下载PNG,跟上述逻辑一样,因为总体积超过了6MB,唯一存在的临时文件GIF会被删除,因为 GIF走的是本地拿资源,且PNG下载特别快,所以 PNG下载后做LRU的logic就会很快,所以会造成GIF直接被删掉,而GIF本身在判断发起是不是要下载时因为本地已经有了GIF,而并不发起下载,所以会使用本地的,而在绘制时因为GIF所需要使用的本地资源并不存在,所以会是空白,注意这时缓存表saveFile中只有PNG,缓存文件占用空间是 3.6KB。

    该场景对照问题图片1

  4. 用户 第三次进入海报绘制,GIF本地没有资源且占用空间小于3.6KB,直接发起请求,PNG本地有所以不需要,GIF下载完成时在座LRU时会造成PNG被干掉,所以绘制海报时GIF OK但PNG看不到

    该场景对照问题图片2

Root Cause

海报在下载多个资源文件时是并行的过程,而下载到完成处理中也会有可能删除需要用到的文件,进而造成在海报绘制时依赖的资源出现not found的情况

解决办法

  1. 关闭LRU,即不存在业务里的单独体积大小限制,也就不会删除某资源文件。但这样做有2个问题

    1. 关闭LRU的情况下,Painter还是会优先用既存资源,但小程序临时文件存储就会一直变大,直到达到200MB时出现下载存储失败

    -

  2. 下载+LRU删除文件必须按照多个下载任务顺序执行,避免出现B造成A删除而A也没有新下载。当然这样做又不好,因为绘制就会更慢,没有多线程执行下载。

新版Painter LRU默认值改为了false,这也正是为什么在升级后问题看着像是”解决了”

关闭LRU还是可能出问题

如果关闭LRU,实际上就是一直去下载,但如果在下载下来了PNG后,达到了200这个限制,GIF下载后超了,所以

相关文档