奇怪的位图闪烁

Strange bitmap flickering

我想优化我的 recyclerview,我正在处理 pdf reader,我发现了一个小功能来显示我的 pdf 文件的第一页:

    private fun pdfToBitmap(pdfFile: File): Bitmap? {
    var bitmap: Bitmap? = null
    try {
        val renderer = PdfRenderer(ParcelFileDescriptor.open(pdfFile, ParcelFileDescriptor.MODE_READ_ONLY))
        val pageCount = renderer.pageCount
        if (pageCount > 0) {
            val page = renderer.openPage(0)
            bitmap = Bitmap.createBitmap(page.width, page.height, Bitmap.Config.ARGB_8888)

            val canvas = Canvas(bitmap)
            canvas.drawColor(Color.WHITE)
            canvas.drawBitmap(bitmap, page.width.toFloat(), page.height.toFloat(), null)

            page.render(bitmap, null, null, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY)
            page.close()
            renderer.close()
        }
    } catch (ex: Exception) {
        ex.printStackTrace()
    }
    return bitmap
}

我在默认协程调度程序中执行它,并在主线程中显示它,它给了我这个非常奇怪的行为。预览在它们之间随机交换,我也想缓存这些位图,也许它可以帮助解决这个问题。

这是我的错误:

GIF

谢谢

RecyclerViews 通过创建一些项目布局,并重新使用它们来显示其他项目(回收它们,因此得名)来工作。当 ViewHolder 需要显示新项目时,会调用 onBindViewHolder,这就是您在项目布局中设置视图的地方 - 设置文本、更改图像等。

因此,如果 ViewHolder #4 显示某个图像,然后您向下滚动到 ViewHolder #4 被重复使用的位置,它仍会显示该图像,直到您更改它。据推测,您的图像渲染代码非常慢,因此需要很长时间才能真正发生这种变化。您还可能会遇到这样的情况:如果滚动得足够远,#4 会再次使用 ,现在您有两个渲染任务在排队,因此您可能会看到图像发生变化 两次,等等

最简单的解决方案可能是始终显示占位符 PDF 图像,然后启动渲染器任务 - 这样当您滚动到新项目时,它永远不会暂时显示错误的图像。但是你仍然需要取消陈旧的协程作业——将 display 函数放在 ViewHolder 本身中可能是值得的,这样它就可以启动任务,保留它自己的 Job引用,如果有新的 display 调用,则取消它。


您可以使用缓存,例如 LRU cache,或磁盘缓存 - 这肯定有助于提高性能,但它会使事情变得有点复杂,并且有很多不同的解决方案。如果你想那样做,你必须做一些调查,看看你正在做的事情的正确方法是什么。

Here's a page from the Android docs 关于有效的位图加载 - 我主要 link 这样做是因为它们 link 一些你可以查看的推荐库。其中一些处理从文件加载,因此如果您正在执行临时磁盘缓存路由,它可能会有用。您将不得不看到将它们与您正在使用的渲染器集成是多么容易(因为您正在做的事情比仅仅“加载图像”要复杂一些)

但有一个建议 - 看起来您正在创建全 PDF 页面大小的位图。我不确定您是否需要在渲染阶段执行此操作,但您肯定希望根据列表中图像视图的实际大小调整它们的大小以进行显示。这些库处理智能调整大小以适应视图 - 但即使您不使用它们,您自己的缓存也会受益于处理更小的位图