2

I'm currently displaying some bitmaps inside a LazyVerticalGrid. To avoid out of memory error, I'm trying to recycle bitmap doing the following:

@Composable
fun ComicsList(covers: List<ComicCover>, onComicClicked: (ComicCover) -> Unit) {
    LazyVerticalGrid(
        columns = GridCells.Fixed(3),
        contentPadding = PaddingValues(16.dp),
        verticalArrangement = spacedBy(8.dp),
        horizontalArrangement = spacedBy(8.dp)
    ) {
        items(
            items = covers,
            key = { it.id }) {
            ComicCoverView(it, onComicClicked)
        }
    }
}

@Composable
fun ComicCoverView(comic: ComicCover, onComicClicked: (ComicCover) -> Unit) {
    Card {
        DisposableEffect(
            Image(
                modifier = Modifier
                    .height(180.dp)
                    .clickable { onComicClicked(comic) },
                bitmap = comic.cover.asImageBitmap(),
                contentDescription = null,
                contentScale = ContentScale.FillHeight,
            )
        ) { onDispose { comic.cover.recycle() } }
    }
}

But I got the following error:

java.lang.RuntimeException: Canvas: trying to use a recycled bitmap android.graphics.Bitmap@eb4ab61

Any idea on how to properly clean up resources?

Benjamin
  • 7,055
  • 6
  • 40
  • 60

2 Answers2

1

There is nothing wrong how you recycle Bitmap but the issue is where you recycle them.

ComicCoverView is enters composition anytime that item is visible or it's next item that is to be visible in scroll direction.

You most likely recycle Bitmaps that you wish to show again, you can verify it by using imageBitmap.asAndroidBitmap().isRecycled before setting imageBitmap.

And where you should recycle Bitmaps is when you are done showing LazyVerticalGrid.

Thracian
  • 43,021
  • 16
  • 133
  • 222
  • What about pagination with a large set of bitmap (1000+)? I cannot wait until LazyVerticalGrid is not used anymore to recycle my 1000+ bitmaps as I'll have an OOM before that... – Benjamin Nov 14 '22 at 06:10
  • If you check out `painterResource` source code you can see it uses `ImageBitmap.imageResource(res, id)` via `loadImageBitmapResource` either. I haven't had any issues memory being unclaimed or going up when you scroll lazy lists. If you experience such an issue you can recycle current imageBitmap and try to create a new Bitmap via copy or other Bitmap methods. – Thracian Nov 14 '22 at 06:13
  • The issue you face is using Bitmaps that are recycled. If you recycle them you will have another issue of creating bitmap on each scroll which might cause performance issues. – Thracian Nov 14 '22 at 06:19
1

Looks like the bitmap is already loaded in memory in the ComicCover object, so when the ComicCover gets loaded again, during scroll, after onDispose triggered, it will just hold a reference to a recycle bitmap.

Should be more easy if you just set the uri||resource id in the ComicCover and load the bitmap inside the DisposableEffect and recycle it onDispose like you are doing already. This way you'll have a fresh bitmap everytime a specific ComicCover gets composed.

When loading the bitmap everytime might be computationally inefficiet during scroll. To optimize little bit you could add a ProgressIndicator or a Placeholder for the bitmap and then add a delay(600) inside DisposableEffect before you load the bitmap in memory. So when the user scroll the list only the last bitmaps on screen will be loaded.

Moreover, if the bitmaps are small and you are using paging I don't think would be much of a problem if they get preloaded in memory and recycled when changing page

zjmo
  • 625
  • 2
  • 8