diff --git a/coil-base/src/main/java/coil/decode/BitmapFactoryDecoder.kt b/coil-base/src/main/java/coil/decode/BitmapFactoryDecoder.kt index c8ea8cd5b8..a9cacd122c 100644 --- a/coil-base/src/main/java/coil/decode/BitmapFactoryDecoder.kt +++ b/coil-base/src/main/java/coil/decode/BitmapFactoryDecoder.kt @@ -14,6 +14,7 @@ import coil.size.PixelSize import coil.size.Size import coil.util.toDrawable import coil.util.toSoftware +import kotlinx.coroutines.runInterruptible import okio.Buffer import okio.BufferedSource import okio.ForwardingSource @@ -47,6 +48,13 @@ internal class BitmapFactoryDecoder(private val context: Context) : Decoder { source: BufferedSource, size: Size, options: Options + ): DecodeResult = runInterruptible { decodeInterruptible(pool, source, size, options) } + + private fun decodeInterruptible( + pool: BitmapPool, + source: BufferedSource, + size: Size, + options: Options ): DecodeResult = BitmapFactory.Options().run { val safeSource = ExceptionCatchingSource(source) val safeBufferedSource = safeSource.buffer() @@ -154,21 +162,29 @@ internal class BitmapFactoryDecoder(private val context: Context) : Decoder { } } + // Keep a reference to the input bitmap so it can be returned to + // the pool if the decode doesn't complete successfully. + val inBitmap: Bitmap? = inBitmap + // Decode the bitmap. - val rawBitmap: Bitmap? = safeBufferedSource.use { - BitmapFactory.decodeStream(it.inputStream(), null, this) - } - safeSource.exception?.let { exception -> - rawBitmap?.let(pool::put) - throw exception + var outBitmap: Bitmap? = null + try { + outBitmap = safeBufferedSource.use { + BitmapFactory.decodeStream(it.inputStream(), null, this) + } + safeSource.exception?.let { throw it } + } catch (throwable: Throwable) { + inBitmap?.let(pool::put) + outBitmap?.let(pool::put) + throw throwable } // Apply any EXIF transformations. - checkNotNull(rawBitmap) { + checkNotNull(outBitmap) { "BitmapFactory returned a null Bitmap. Often this means BitmapFactory could not decode the image data " + "read from the input source (e.g. network or disk) as it's not encoded as a valid image format." } - val bitmap = applyExifTransformations(pool, rawBitmap, inPreferredConfig, isFlipped, rotationDegrees) + val bitmap = applyExifTransformations(pool, outBitmap, inPreferredConfig, isFlipped, rotationDegrees) bitmap.density = Bitmap.DENSITY_NONE DecodeResult( diff --git a/coil-gif/src/main/java/coil/decode/GifDecoder.kt b/coil-gif/src/main/java/coil/decode/GifDecoder.kt index 237708873b..198cab86cb 100644 --- a/coil-gif/src/main/java/coil/decode/GifDecoder.kt +++ b/coil-gif/src/main/java/coil/decode/GifDecoder.kt @@ -9,6 +9,7 @@ import coil.bitmappool.BitmapPool import coil.drawable.MovieDrawable import coil.extension.repeatCount import coil.size.Size +import kotlinx.coroutines.runInterruptible import okio.BufferedSource /** @@ -31,7 +32,7 @@ class GifDecoder : Decoder { source: BufferedSource, size: Size, options: Options - ): DecodeResult { + ): DecodeResult = runInterruptible { // Movie requires an InputStream to resettable on API 18 and below. // Read the data as a ByteArray to work around this. val movie = if (SDK_INT <= 18) { @@ -58,7 +59,7 @@ class GifDecoder : Decoder { drawable.setRepeatCount(options.parameters.repeatCount() ?: MovieDrawable.REPEAT_INFINITE) - return DecodeResult( + DecodeResult( drawable = drawable, isSampled = false ) diff --git a/coil-gif/src/main/java/coil/decode/ImageDecoderDecoder.kt b/coil-gif/src/main/java/coil/decode/ImageDecoderDecoder.kt index ed6209ec6d..34b14ec51b 100644 --- a/coil-gif/src/main/java/coil/decode/ImageDecoderDecoder.kt +++ b/coil-gif/src/main/java/coil/decode/ImageDecoderDecoder.kt @@ -15,6 +15,7 @@ import coil.drawable.ScaleDrawable import coil.extension.repeatCount import coil.size.PixelSize import coil.size.Size +import kotlinx.coroutines.runInterruptible import okio.BufferedSource import okio.sink import java.io.File @@ -44,7 +45,7 @@ class ImageDecoderDecoder : Decoder { source: BufferedSource, size: Size, options: Options - ): DecodeResult { + ): DecodeResult = runInterruptible { var tempFile: File? = null try { @@ -110,7 +111,7 @@ class ImageDecoderDecoder : Decoder { baseDrawable } - return DecodeResult( + DecodeResult( drawable = drawable, isSampled = isSampled ) diff --git a/coil-svg/src/main/java/coil/decode/SvgDecoder.kt b/coil-svg/src/main/java/coil/decode/SvgDecoder.kt index c0a8f50518..6beb8480b0 100644 --- a/coil-svg/src/main/java/coil/decode/SvgDecoder.kt +++ b/coil-svg/src/main/java/coil/decode/SvgDecoder.kt @@ -12,6 +12,7 @@ import coil.size.OriginalSize import coil.size.PixelSize import coil.size.Size import com.caverock.androidsvg.SVG +import kotlinx.coroutines.runInterruptible import okio.BufferedSource /** @@ -31,7 +32,7 @@ class SvgDecoder(private val context: Context) : Decoder { source: BufferedSource, size: Size, options: Options - ): DecodeResult { + ): DecodeResult = runInterruptible { val svg = source.use { SVG.getFromInputStream(it.inputStream()) } val svgWidth = svg.documentWidth @@ -82,7 +83,7 @@ class SvgDecoder(private val context: Context) : Decoder { val bitmap = pool.get(bitmapWidth, bitmapHeight, config) svg.renderToCanvas(Canvas(bitmap)) - return DecodeResult( + DecodeResult( drawable = bitmap.toDrawable(context.resources), isSampled = true // SVGs can always be re-decoded at a higher resolution. ) diff --git a/coil-video/src/main/java/coil/fetch/VideoFrameFetcher.kt b/coil-video/src/main/java/coil/fetch/VideoFrameFetcher.kt index ae181f3f98..f3788b7c68 100644 --- a/coil-video/src/main/java/coil/fetch/VideoFrameFetcher.kt +++ b/coil-video/src/main/java/coil/fetch/VideoFrameFetcher.kt @@ -93,6 +93,8 @@ abstract class VideoFrameFetcher(private val context: Context) : Fetche size: Size, options: Options ): FetchResult { + // NOTE: we don't use 'runInterruptible' as MediaMetadataRetriever will + // continue its work even if its thread is interrupted. val retriever = MediaMetadataRetriever() try {