From d819928eb391aa1bdeec06dc12be9b92af19fdb4 Mon Sep 17 00:00:00 2001 From: Ammar Githam Date: Mon, 3 May 2021 18:30:50 +0900 Subject: [PATCH] Memory efficient bitmap decoding in DownloadWorker. Fixes austinhuang0131/barinsta#930. --- .../awais/instagrabber/utils/BitmapUtils.java | 96 ++++++++++--------- .../instagrabber/workers/DownloadWorker.java | 77 ++++++++------- 2 files changed, 93 insertions(+), 80 deletions(-) diff --git a/app/src/main/java/awais/instagrabber/utils/BitmapUtils.java b/app/src/main/java/awais/instagrabber/utils/BitmapUtils.java index c1241ebd..4e7a300d 100644 --- a/app/src/main/java/awais/instagrabber/utils/BitmapUtils.java +++ b/app/src/main/java/awais/instagrabber/utils/BitmapUtils.java @@ -127,47 +127,9 @@ public final class BitmapUtils { final boolean addToCache, final ThumbnailLoadCallback callback) { if (contentResolver == null || uri == null || callback == null) return; - final ListenableFuture future = appExecutors.tasksThread().submit(() -> { - BitmapFactory.Options bitmapOptions; - float actualReqWidth = reqWidth; - float actualReqHeight = reqHeight; - try (InputStream input = contentResolver.openInputStream(uri)) { - BitmapFactory.Options outBounds = new BitmapFactory.Options(); - outBounds.inJustDecodeBounds = true; - outBounds.inPreferredConfig = Bitmap.Config.ARGB_8888; - BitmapFactory.decodeStream(input, null, outBounds); - if ((outBounds.outWidth == -1) || (outBounds.outHeight == -1)) return null; - bitmapOptions = new BitmapFactory.Options(); - if (maxDimenSize > 0) { - // Raw height and width of image - final int height = outBounds.outHeight; - final int width = outBounds.outWidth; - final float ratio = (float) width / height; - if (height > width) { - actualReqHeight = maxDimenSize; - actualReqWidth = actualReqHeight * ratio; - } else { - actualReqWidth = maxDimenSize; - actualReqHeight = actualReqWidth / ratio; - } - } - bitmapOptions.inSampleSize = calculateInSampleSize(outBounds, actualReqWidth, actualReqHeight); - } catch (Exception e) { - Log.e(TAG, "loadBitmap: ", e); - return null; - } - try (InputStream input = contentResolver.openInputStream(uri)) { - bitmapOptions.inPreferredConfig = Bitmap.Config.ARGB_8888; - Bitmap bitmap = BitmapFactory.decodeStream(input, null, bitmapOptions); - if (addToCache) { - addBitmapToMemoryCache(uri.toString(), bitmap, true); - } - return new BitmapResult(bitmap, (int) actualReqWidth, (int) actualReqHeight); - } catch (Exception e) { - Log.e(TAG, "loadBitmap: ", e); - } - return null; - }); + final ListenableFuture future = appExecutors + .tasksThread() + .submit(() -> getBitmapResult(contentResolver, uri, reqWidth, reqHeight, maxDimenSize, addToCache)); Futures.addCallback(future, new FutureCallback() { @Override public void onSuccess(@Nullable final BitmapResult result) { @@ -185,8 +147,56 @@ public final class BitmapUtils { }, callbackHandlers); } - private static class BitmapResult { - Bitmap bitmap; + @Nullable + public static BitmapResult getBitmapResult(final ContentResolver contentResolver, + final Uri uri, + final float reqWidth, + final float reqHeight, + final float maxDimenSize, + final boolean addToCache) { + BitmapFactory.Options bitmapOptions; + float actualReqWidth = reqWidth; + float actualReqHeight = reqHeight; + try (InputStream input = contentResolver.openInputStream(uri)) { + BitmapFactory.Options outBounds = new BitmapFactory.Options(); + outBounds.inJustDecodeBounds = true; + outBounds.inPreferredConfig = Bitmap.Config.ARGB_8888; + BitmapFactory.decodeStream(input, null, outBounds); + if ((outBounds.outWidth == -1) || (outBounds.outHeight == -1)) return null; + bitmapOptions = new BitmapFactory.Options(); + if (maxDimenSize > 0) { + // Raw height and width of image + final int height = outBounds.outHeight; + final int width = outBounds.outWidth; + final float ratio = (float) width / height; + if (height > width) { + actualReqHeight = maxDimenSize; + actualReqWidth = actualReqHeight * ratio; + } else { + actualReqWidth = maxDimenSize; + actualReqHeight = actualReqWidth / ratio; + } + } + bitmapOptions.inSampleSize = calculateInSampleSize(outBounds, actualReqWidth, actualReqHeight); + } catch (Exception e) { + Log.e(TAG, "loadBitmap: ", e); + return null; + } + try (InputStream input = contentResolver.openInputStream(uri)) { + bitmapOptions.inPreferredConfig = Bitmap.Config.ARGB_8888; + Bitmap bitmap = BitmapFactory.decodeStream(input, null, bitmapOptions); + if (addToCache) { + addBitmapToMemoryCache(uri.toString(), bitmap, true); + } + return new BitmapResult(bitmap, (int) actualReqWidth, (int) actualReqHeight); + } catch (Exception e) { + Log.e(TAG, "loadBitmap: ", e); + } + return null; + } + + public static class BitmapResult { + public Bitmap bitmap; int width; int height; diff --git a/app/src/main/java/awais/instagrabber/workers/DownloadWorker.java b/app/src/main/java/awais/instagrabber/workers/DownloadWorker.java index b0dae625..1648a4ad 100644 --- a/app/src/main/java/awais/instagrabber/workers/DownloadWorker.java +++ b/app/src/main/java/awais/instagrabber/workers/DownloadWorker.java @@ -6,7 +6,6 @@ import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.graphics.Bitmap; -import android.graphics.BitmapFactory; import android.media.MediaMetadataRetriever; import android.media.MediaScannerConnection; import android.net.Uri; @@ -16,6 +15,7 @@ import android.os.Looper; import android.util.Log; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.core.app.NotificationCompat; import androidx.core.app.NotificationManagerCompat; import androidx.core.content.FileProvider; @@ -33,7 +33,6 @@ import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; -import java.io.InputStream; import java.net.URL; import java.net.URLConnection; import java.util.Collection; @@ -48,14 +47,17 @@ import java.util.concurrent.ExecutionException; import awais.instagrabber.BuildConfig; import awais.instagrabber.R; import awais.instagrabber.services.DeleteImageIntentService; +import awais.instagrabber.utils.BitmapUtils; import awais.instagrabber.utils.Constants; import awais.instagrabber.utils.DownloadUtils; import awais.instagrabber.utils.TextUtils; import awais.instagrabber.utils.Utils; -//import awaisomereport.LogCollector; +import static awais.instagrabber.utils.BitmapUtils.THUMBNAIL_SIZE; import static awais.instagrabber.utils.Constants.DOWNLOAD_CHANNEL_ID; import static awais.instagrabber.utils.Constants.NOTIF_GROUP_NAME; + +//import awaisomereport.LogCollector; //import static awais.instagrabber.utils.Utils.logCollector; public class DownloadWorker extends Worker { @@ -253,40 +255,7 @@ public class DownloadWorker extends Worker { MediaScannerConnection.scanFile(context, new String[]{file.getAbsolutePath()}, null, null); final Uri uri = FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".provider", file); final ContentResolver contentResolver = context.getContentResolver(); - Bitmap bitmap = null; - final String mimeType = Utils.getMimeType(uri, contentResolver); - if (!TextUtils.isEmpty(mimeType)) { - if (mimeType.startsWith("image")) { - try (final InputStream inputStream = contentResolver.openInputStream(uri)) { - bitmap = BitmapFactory.decodeStream(inputStream); - } catch (final Exception e) { -// if (logCollector != null) -// logCollector.appendException(e, LogCollector.LogFile.ASYNC_DOWNLOADER, "onPostExecute::bitmap_1"); - if (BuildConfig.DEBUG) Log.e(TAG, "", e); - } - } else if (mimeType.startsWith("video")) { - final MediaMetadataRetriever retriever = new MediaMetadataRetriever(); - try { - try { - retriever.setDataSource(context, uri); - } catch (final Exception e) { - retriever.setDataSource(file.getAbsolutePath()); - } - bitmap = retriever.getFrameAtTime(); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) - try { - retriever.close(); - } catch (final Exception e) { -// if (logCollector != null) -// logCollector.appendException(e, LogCollector.LogFile.ASYNC_DOWNLOADER, "onPostExecute::bitmap_2"); - } - } catch (final Exception e) { - if (BuildConfig.DEBUG) Log.e(TAG, "", e); -// if (logCollector != null) -// logCollector.appendException(e, LogCollector.LogFile.ASYNC_DOWNLOADER, "onPostExecute::bitmap_3"); - } - } - } + final Bitmap bitmap = getThumbnail(context, file, uri, contentResolver); final String downloadComplete = context.getString(R.string.downloader_complete); final Intent intent = new Intent(Intent.ACTION_VIEW, uri) .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK @@ -351,6 +320,40 @@ public class DownloadWorker extends Worker { } } + @Nullable + private Bitmap getThumbnail(final Context context, + final File file, + final Uri uri, + final ContentResolver contentResolver) { + final String mimeType = Utils.getMimeType(uri, contentResolver); + if (TextUtils.isEmpty(mimeType)) return null; + Bitmap bitmap = null; + if (mimeType.startsWith("image")) { + try { + final BitmapUtils.BitmapResult bitmapResult = BitmapUtils + .getBitmapResult(context.getContentResolver(), uri, THUMBNAIL_SIZE, THUMBNAIL_SIZE, -1, true); + if (bitmapResult == null) return null; + bitmap = bitmapResult.bitmap; + } catch (final Exception e) { + Log.e(TAG, "", e); + } + return bitmap; + } + if (mimeType.startsWith("video")) { + try (MediaMetadataRetriever retriever = new MediaMetadataRetriever()) { + try { + retriever.setDataSource(context, uri); + } catch (final Exception e) { + retriever.setDataSource(file.getAbsolutePath()); + } + bitmap = retriever.getFrameAtTime(); + } catch (final Exception e) { + Log.e(TAG, "", e); + } + } + return bitmap; + } + public static class DownloadRequest { private final Map urlToFilePathMap;