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?

2 Answers 2

2

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

Sign up to request clarification or add additional context in comments.

Comments

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.

3 Comments

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...
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.
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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.