Can we somehow determine whether hardware acceleration will work on the device?

95 Views Asked by At

Hello I have Android app using Compose and Coil to load images.

I noticed in Crashlytics that there is crash while trying to render bitmap using hardware acceleration

Fatal Exception: java.lang.IllegalStateException Software rendering doesn't support hardware bitmaps
 android.graphics.BaseCanvas.throwIfHwBitmapInSwMode (BaseCanvas.java:532)
 android.graphics.BaseCanvas.throwIfCannotDraw (BaseCanvas.java:62)
 android.graphics.BaseCanvas.drawBitmap (BaseCanvas.java:120)
 android.graphics.Canvas.drawBitmap (Canvas.java:1434)
 androidx.compose.ui.graphics.AndroidCanvas.drawImageRect-HPBpro0 (AndroidCanvas.android.kt:271)
 androidx.compose.ui.graphics.drawscope.CanvasDrawScope.drawImage-AZ2fEMs (CanvasDrawScope.kt:263)
 androidx.compose.ui.node.LayoutNodeDrawScope.drawImage-AZ2fEMs (LayoutNodeDrawScope.kt:1)
 androidx.compose.ui.graphics.drawscope.DrawScope.drawImage-AZ2fEMs$default (DrawScope.java:510)
 androidx.compose.ui.graphics.painter.BitmapPainter.onDraw (BitmapPainter.kt:93)
 androidx.compose.ui.graphics.painter.Painter.draw-x_KDEd0 (Painter.kt:212)
 coil.compose.AsyncImagePainter.onDraw (AsyncImagePainter.kt:208)

For now this problem occur just on few devices, so I guess there is no problem in the code itself, just some devices has problem with hardware acceleration. I know the crash itself should be fixed by disabling hardware acceleration in coil globally

allowHardware(false)

But I dont want to do that when unnecessary to not slow down loading images.

Is there some way to determine in which case I need to disable it to avoid crash, but dont disable it when unnecessary? Like some way to determine wheter it should work on the current device?

(Im using Coil 2.5.0)

1

There are 1 best solutions below

0
Nikola Despotoski On

Maybe the issue happens when draw is called while the Canvas was offscreen, if the error is caused by device limitations then you can retry the drawing if it has failed with a little bit of overriding the Painter.onDraw()

@Composable
fun rememberDelayedAsyncPainter(
    model: Any?,
    imageLoader: ImageLoader,
    transform: (AsyncImagePainter.State) -> AsyncImagePainter.State = AsyncImagePainter.DefaultTransform,
    onState: ((AsyncImagePainter.State) -> Unit)? = null,
    contentScale: ContentScale = ContentScale.Fit,
    filterQuality: FilterQuality = DrawScope.DefaultFilterQuality,
): Painter {
    var il = remember { imageLoader }
    val asyncImagePainter =
        rememberAsyncImagePainter(model, il, transform, onState, contentScale, filterQuality)
    return remember(il) {
        object : Painter() {
            override val intrinsicSize: Size
                get() = asyncImagePainter.intrinsicSize

            override fun DrawScope.onDraw() {
                runCatching {
                    with(asyncImagePainter) {
                        draw(size)
                    }
                }.onFailure {
                    if (it !is IllegalStateException) {
                        throw it
                    }
                    if (it.message != "Software rendering doesn't support hardware bitmaps") {
                        throw it
                    }
                    il = il.newBuilder().allowHardware(false).build()
                }
            }

        }
    }
}

The idea behind this is causing recomposition with a new ImageLoader and allowHardrware(false). New ImageLoader will request new AsyncPainter that will load bitmap without hardware configuration.