奇怪的位图闪烁

发布于 2025-01-14 01:34:56 字数 1094 浏览 4 评论 0 原文

我想优化我的recyclerview,我正在开发一个pdf阅读器,我发现了一个小函数来显示我的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

谢谢

I'd like to optimize my recyclerview, I'm working on a pdf reader, and I found a small function to display the first page of my pdf files:

    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
}

I execute it in the default coroutine dispatcher, and display it in the Main thread, and it give me this very strange behaviour. The previews are randomly swaping between them, also to i'd like to cache theses bitmap, maybe it can help to fix this issue.

Here's is my bug :

GIF

Thanks

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

忆梦 2025-01-21 01:34:56

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

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

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


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

这是 Android 文档中关于高效位图加载的页面 - 我'我主要链接这个,因为它们链接到一些您可以研究的推荐库。其中一些处理从文件加载,因此如果您正在执行临时磁盘缓存路由,它可能会很有用。您必须了解将它们与您正在使用的渲染器集成是多么容易(因为您正在做的事情比“加载图像”要复杂一些)

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

RecyclerViews work by creating a handful of item layouts, and reusing them to display other items (recycling them, hence the name). When a ViewHolder needs to display a new item, onBindViewHolder gets called, and that's where you set up the views in the item layout - setting text, changing images etc.

So if ViewHolder #4 displays a certain image, and then you scroll down to where ViewHolder #4 is reused, it will still display that image until you change it. At a guess, your image rendering code is quite slow, so it takes a long time for that change to actually happen. You also might get a situation where if you scroll far enough, #4 is used again, and now you have two rendering tasks queued up, so you might see the image change twice, etc.

Your simplest solution is probably to always display your placeholder PDF image, then fire off the renderer task - that way when you scroll to a new item, it'll never have the wrong image displayed temporarily. But you'll still need to cancel the stale coroutine job - it might be worth putting the display function in the ViewHolder itself, so it can fire off the task, keep its own Job reference, and cancel it if a new display call comes in.


You could use a cache, like an LRU cache, or a disk cache - it would definitely help with performance, but it complicates things a little and there are a lot of different solutions out there. You'll have to do some investigation to see what the right approach is for what you're doing, if you want to go that way.

Here's a page from the Android docs about efficient bitmap loading - I'm mainly linking this because they link to some recommended libraries you could look into. Some of them handle loading from files, so if you're doing the temp disk cache route, it could be useful. You'll have to see how easy it is to integrate them with the renderer you're using (since what you're doing is a little more complicated than just "load image")

One suggestion though - it looks like you're creating bitmaps at full PDF page size. I'm not sure if you need to do that for the rendering stage, but you'll definitely want to resize those for display, depending on the actual size of the image view in your list. Those libraries handle intelligent resizing to fit views - but even if you're not using one, your own caching will benefit from handling much smaller bitmaps

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文