From 7a020f25a2890a277edcbd048359ac894372cf7a Mon Sep 17 00:00:00 2001 From: wonseok Date: Sat, 30 Dec 2023 23:34:40 +0900 Subject: [PATCH] Optimized memory caching for image picking in Android using LruCache (#35) --- .../picker/ImagePickerLauncher.android.kt | 18 +++++---- .../image/picker/PeekabooBitmapCache.kt | 39 +++++++++++++++++++ 2 files changed, 50 insertions(+), 7 deletions(-) create mode 100644 peekaboo-image-picker/src/androidMain/kotlin/com/preat/peekaboo/image/picker/PeekabooBitmapCache.kt diff --git a/peekaboo-image-picker/src/androidMain/kotlin/com/preat/peekaboo/image/picker/ImagePickerLauncher.android.kt b/peekaboo-image-picker/src/androidMain/kotlin/com/preat/peekaboo/image/picker/ImagePickerLauncher.android.kt index c8117f1..2d0ed0a 100644 --- a/peekaboo-image-picker/src/androidMain/kotlin/com/preat/peekaboo/image/picker/ImagePickerLauncher.android.kt +++ b/peekaboo-image-picker/src/androidMain/kotlin/com/preat/peekaboo/image/picker/ImagePickerLauncher.android.kt @@ -25,6 +25,7 @@ import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.platform.LocalContext +import com.preat.peekaboo.image.picker.PeekabooBitmapCache.bitmapToByteArray import com.preat.peekaboo.image.picker.SelectionMode.Companion.INFINITY import kotlinx.coroutines.CoroutineScope import java.io.ByteArrayOutputStream @@ -166,11 +167,13 @@ private fun resizeImage( width: Int, height: Int, ): ByteArray? { + val cacheKey = "${uri}_w${width}_h$height" + PeekabooBitmapCache.instance.get(cacheKey)?.let { cachedBitmap -> + return bitmapToByteArray(cachedBitmap) + } + context.contentResolver.openInputStream(uri)?.use { inputStream -> - val options = - BitmapFactory.Options().apply { - inJustDecodeBounds = true - } + val options = BitmapFactory.Options().apply { inJustDecodeBounds = true } BitmapFactory.decodeStream(inputStream, null, options) var inSampleSize = 1 @@ -182,11 +185,12 @@ private fun resizeImage( options.inSampleSize = inSampleSize context.contentResolver.openInputStream(uri)?.use { scaledInputStream -> - val scaledBitmap = BitmapFactory.decodeStream(scaledInputStream, null, options) - if (scaledBitmap != null) { + BitmapFactory.decodeStream(scaledInputStream, null, options)?.let { scaledBitmap -> ByteArrayOutputStream().use { byteArrayOutputStream -> scaledBitmap.compress(Bitmap.CompressFormat.JPEG, 100, byteArrayOutputStream) - return byteArrayOutputStream.toByteArray() + val byteArray = byteArrayOutputStream.toByteArray() + PeekabooBitmapCache.instance.put(cacheKey, scaledBitmap) + return byteArray } } } diff --git a/peekaboo-image-picker/src/androidMain/kotlin/com/preat/peekaboo/image/picker/PeekabooBitmapCache.kt b/peekaboo-image-picker/src/androidMain/kotlin/com/preat/peekaboo/image/picker/PeekabooBitmapCache.kt new file mode 100644 index 0000000..9d4a708 --- /dev/null +++ b/peekaboo-image-picker/src/androidMain/kotlin/com/preat/peekaboo/image/picker/PeekabooBitmapCache.kt @@ -0,0 +1,39 @@ +/* + * Copyright 2023 onseok + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.preat.peekaboo.image.picker + +import android.graphics.Bitmap +import android.util.LruCache +import java.io.ByteArrayOutputStream + +internal object PeekabooBitmapCache { + internal val instance: LruCache by lazy { + LruCache(calculateMemoryCacheSize()) + } + + private fun calculateMemoryCacheSize(): Int { + val maxMemory = Runtime.getRuntime().maxMemory() / 1024 + val cacheSize = (maxMemory * 0.25).toInt() + return cacheSize.coerceAtLeast(1024 * 1024) + } + + internal fun bitmapToByteArray(bitmap: Bitmap): ByteArray { + ByteArrayOutputStream().use { stream -> + bitmap.compress(Bitmap.CompressFormat.JPEG, 100, stream) + return stream.toByteArray() + } + } +}