Android SVG VectorDrawable cache

2020-09-04  本文已影响0人  Edgar_Ng

背景

公司提交代码需要提交Screenshot test(UI test的一种,就是将做好的view截图下来,之后每次提PR都要run一次跟之前的对比是否有影响,有改动.使用的是Facebook的screenshot tools,支持像素级别的对比校验.)小伙伴有一个需求就是将UI库中的iconCardView(自定义的view)中的icon(AppCompatImageView)的layout_widthlayout_height32dp改成36dp,本来只是一个很小的改动,但是在Screenshot test的时候发现一个神奇的事情,没有用到iconCradView的业务,报错了说run出来的截图跟之前保存起来的不一致.经过查看之后发现共同点就是,都一样用了同一个res(同一个SVG xml资源文件,widthheight均为20dp)然后就有了这篇文章的出现,分析Android SVG VectorDrawable cache缓存机制.

猜想

Note: To optimize for the re-drawing performance, one bitmap cache is created for each VectorDrawable. Therefore, referring to the same VectorDrawable means sharing the same bitmap cache. If these references don't agree upon on the same size, the bitmap will be recreated and redrawn every time size is changed. In other words, if a VectorDrawable is used for different sizes, it is more efficient to create multiple VectorDrawables, one for each size.

但是这里就没有特别说明这个缓存机制是怎么样,怎么共用,怎么刷新,我们也无法确定就是这个缓存机制造成了现在的问题.所以我们会通过一系列实验去验证是否这个就是root cause.

实验

  1. 打开normal page,截图命名为P1
  2. 关闭normal page后,再次打开normal page,截图命名为P2
  3. 打开36page
  4. 关闭36page后,再次打开normal page,截图命名为P3
  5. 关闭normal page后,再次打开normal page,截图命名为P4
  6. 重新安装没有36page的版本APP
  7. 打开normal page,截图命名为P5

分析

  1. 根据实验结果可确定,当view的宽高一致时,VectorDrawable会使用已经存在的缓存,没有会重新创建.当view宽高变得更大的时候,VectorDrawable会更新缓存.当需要重新打开宽高较小的其他view时,会使用更新后的缓存.所以会导致P2P3不一致,但是P3P4相同.
  2. 源码分析.
    1. 根据源码VectorDrawable.javadraw() 417行可知会call C++层的Draw()
      VectorDrawable.java draw()
    2. 转而分析VectorDrawable.cppDraw(),主要看注释和447行的outCanvas->drawVectorDrawable(this)
      由注释可知bitmap的大小由bounds和canvas scale决定.
      VectorDrawable.cpp Draw()
    3. 分析447行的outCanvas->drawVectorDrawable(this),根据SkiaCanvas.cpp可知,还是调用vectorDrawable.cppdrawStaging()
      SkiaCanvas.cpp的drawVectorDrawable()
    4. 分析vectorDrawable.cpp->drawStaging()可知,如果redrawNeededmStagingCache.dirty为true则会调用updateBitmapCache()去update cache.
      vectorDrawable.cpp->drawStaging()
    5. 因为我们遇到的问题是跟宽高有关,所以我们就先看这个给redrawNeeded赋值的allocateBitmapIfNeeded(),根据599行canReuseBitmap(cache.bitmap.get(), width, height),我们可以看到612,613行方法return bitmap && width <= bitmap->width() && height <= bitmap->height()敲黑板!终于来了!!!就是这里,当前请求的widthheight均小于等于bitmapCachewidthheight时才可以reuse,否则就需要调用updateBitmapCache()去update cache.
      allocateBitmapIfNeeded

延伸(挖坑)

  1. 图像失真与模糊
    根据vectorDrawable.cppcanReuseBitmap()我们可以知道它的处理逻辑,但是为什么它这么写呢?是人性的扭曲还是道德的沦丧.敬请期待图像失真与模糊

附件

  1. 实验数据 提取码: w5c5

参考

  1. How VectorDrawable works(需翻墙)
  2. Google Android VectorDrawable API
  3. screenshot-tests-for-android
上一篇下一篇

猜你喜欢

热点阅读