为什么 UIImageView 比 CGContextDrawImage 更占用内存
在开发 iPad PDF 阅读器时,我们决定准备渲染密集型页面(其中有很多路径)的高分辨率图像,并使用这些图像而不是 pdf 页面来避免性能问题。我们认为 3*768 x 3*1024
是可读性和渲染性能之间的良好折衷方案,可生成 ~1.5 MB jpeg。
然而,我们测试了两种显示图像页面的实现。一个使用 CATiledLayer
子类,该子类还负责处理“普通”PDF 页面(使用 CGContextDrawImage
绘制),另一个使用 UIImageView
。后者的优点是显示和缩放非常快,但内存使用率非常糟糕 - 大约需要 30 MB 内存(这与图像的位图大小一致)。另一种方法 (CATiledLayer
) 需要更多时间来首次显示页面,并且在缩放后需要另外两秒钟重新渲染(类似于 pdf 页面,但快得多),但不会占用比它需要显示更小的图像或 PDF 页面。
有谁知道幕后发生了什么,以及是否可以通过使用 Quartz 框架将 CGContextDrawImage 的低内存使用率与 UIImageView 的高性能结合起来。
Developing an iPad PDF-Reader we decided to prepare high-res images of rendering intensive pages (lots of paths in them) and use those instead of the pdf pages to avoid performance issues. We decided that 3*768 by 3*1024
is a good compromise between readability and rendering performance which results in ~1.5 MB jpegs.
However we tested two implementations for displaying the image pages. One that uses a CATiledLayer
subclass which is also responsible for handling the "normal" PDF pages (drawing with CGContextDrawImage
) and another which uses UIImageView
. The latter has the advantage that displaying and zooming is very quick, but memory usages is really bad - it takes about 30 MB in memory (which is consistent with the image's bitmap size). The other approach (CATiledLayer
) needs more time to first display the page and needs another two seconds to re-render after zooming (similar to pdf pages, but much faster) but doesn't grab more memory than it needs to display a much smaller image or a PDF page.
Does anyone know what's going on behind the scenes and if it's possible to combine low memory usage of CGContextDrawImage
with high performance of UIImageView
by using the Quartz Framework.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
不确定这个问题是否仍然相关,但如果是这样,也许这会有所帮助:
我已经在我的 平铺图像视图也是如此。这个问题有很多潜在的部分:
CATiledLayer
确实仅请求可见图块的内容。它还会丢弃不再可见的图块 - 这就是节省内存的来源。但是,它在后台线程上执行通知和绘图,同时从白色到内容进行动画处理。它似乎是通过私有 API 来做到这一点的,我还没有找到一种方法来重新实现CATiledLayer
的功能作为我自己的CALayer
子类,因为看起来很简单没有任何通知机制可供我们普通人使用。drawRect:
消息。不过,UIKit 似乎很难处理视图下面太多的子视图。对于您来说,我可以看到几个可能的选择:
CATiledLayer
的实现。fadeDuration
默认为 0.25 秒,如果您的加载时间很短,该时间可能会太长。您可以将其直接降低为1.0/60.0
之类的值,即一帧。您的描述中不清楚的一件事是您的图像是覆盖整个页面大小还是仅覆盖每个 256x256 像素图块。对每个图块一遍又一遍地解码整个 JPEG 将比解码单个图块文件慢得多。CATiledLayer
线程执行所有操作的延迟太高,您可以手动创建一堆图块作为空白UIView
的UIImageView
子视图这是滚动视图的主要子视图。每个子视图都分配有自己的平铺图像。如果幸运的话,UIKit 将足够智能,删除这些视图的内容并根据需要重新加载相应的 JPEG。drawRect:
中加载它们的 JPEG。如果它仍然太不稳定,请尝试使用您自己的 CALayers,它将成为单个视图空白层的子层,并再次根据需要加载其图像。这就是我最终选择的平铺图像视图。这应该涵盖滚动,但如果您允许用户直接缩小(缩小),它仍然可能非常慢。在这种情况下,我建议您存储适当的较低分辨率版本并加载/显示最外层缩放级别的版本。
Not sure if this question is still relevant, but if so, maybe this will help:
I've fought the battle of efficient display of large views in my tiled image view as well. There are a bunch of underlying parts to the problem:
UIView
, includingUIImageView
, seems to always be completely backed by memory for its image. Even if you implement thedrawRect:
method, it always seems to pass the entire bounds of the view, not only the area visible within a scroll view. As you've discovered, this quickly takes up a lot of memory as each pixel takes 4 bytes.CATiledLayer
does only request the contents for visible tiles. It also discards tiles that are no longer visible - this is where the memory savings come from. However, it does the notification and the drawing on a background thread, while animating from white to the contents. It seems to do this via a private API, I haven't found a way to re-implementCATiledLayer
's functionality as my own subclass ofCALayer
, as there simply seems to be no notification mechanism that we can use as mere mortals.drawRect:
messages appropriately as they are paged in. UIKit seems to struggle with too many subviews below a view, though.For you, I can see a couple of possible options:
CATiledLayer
-based implementation. ThefadeDuration
defaults to 0.25 seconds, which may be too long if your load times are short. You can drop this right down to something like1.0/60.0
, i.e. one frame. One thing that's not clear from your description is whether your images cover the whole page size or just each 256x256 pixel tile. Decoding the whole JPEG over and over again for each tile will be much slower than decoding individual tile files.CATiledLayer
thread is too high, you can manually create a bunch of tiles asUIImageView
subviews to a blankUIView
which is the main subview to the scroll view. Each of these subviews is assigned its own tile image. If you're lucky, UIKit will be smart enough to drop the contents of these views and re-load the corresponding JPEGs on demand.drawRect:
. If it's still too jerky, try with your own CALayers, which will be sublayers of your single view's blank layer and again load their image on demand. This is what I eventually went with for my tiled image view.That should cover scrolling, but it could still be really slow if you allow the user to zoom right out (minification). In that case I recommend you store appropriate lower-resolution versions and load/show those at those outermost zoom levels.