Memory efficient bitmap decoding in DownloadWorker. Fixes austinhuang0131/barinsta#930.

This commit is contained in:
Ammar Githam 2021-05-03 18:30:50 +09:00
parent b2822b27a1
commit d819928eb3
2 changed files with 93 additions and 80 deletions

View File

@ -127,47 +127,9 @@ public final class BitmapUtils {
final boolean addToCache,
final ThumbnailLoadCallback callback) {
if (contentResolver == null || uri == null || callback == null) return;
final ListenableFuture<BitmapResult> 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<BitmapResult> future = appExecutors
.tasksThread()
.submit(() -> getBitmapResult(contentResolver, uri, reqWidth, reqHeight, maxDimenSize, addToCache));
Futures.addCallback(future, new FutureCallback<BitmapResult>() {
@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;

View File

@ -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<String, String> urlToFilePathMap;