Home > Enterprise >  Strange bitmap flickering
Strange bitmap flickering

Time:03-13

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

CodePudding user response:

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

  • Related