Fix OOM during image browsing

Signed-off-by: tobiasKaminsky <tobias@kaminsky.me>
This commit is contained in:
tobiasKaminsky 2020-03-23 17:48:35 +01:00
parent 2c9b0758e4
commit 45315f1486
No known key found for this signature in database
GPG key ID: 0E00D4D47D0C5AF7
6 changed files with 149 additions and 31 deletions

View file

@ -1 +1 @@
381
383

View file

@ -211,6 +211,23 @@ public final class ThumbnailsCacheManager {
return mThumbnailCache.containsKey(key);
}
public static Bitmap getScaledBitmapFromDiskCache(String key, int width, int height) {
synchronized (mThumbnailsDiskCacheLock) {
// Wait while disk cache is started from background thread
while (mThumbnailCacheStarting) {
try {
mThumbnailsDiskCacheLock.wait();
} catch (InterruptedException e) {
Log_OC.e(TAG, "Wait in mThumbnailsDiskCacheLock was interrupted", e);
}
}
if (mThumbnailCache != null) {
return mThumbnailCache.getScaledBitmap(key, width, height);
}
}
return null;
}
public static Bitmap getBitmapFromDiskCache(String key) {
synchronized (mThumbnailsDiskCacheLock) {
// Wait while disk cache is started from background thread

View file

@ -133,7 +133,7 @@ public class FileMenuFilter {
List<Integer> toShow = new ArrayList<>();
List<Integer> toHide = new ArrayList<>();
filter(toShow, toHide, inSingleFileFragment, isMediaSupported, menu);
filter(toShow, toHide, inSingleFileFragment, isMediaSupported);
for (int i : toShow) {
showMenuItem(menu.findItem(i));
@ -193,8 +193,7 @@ public class FileMenuFilter {
private void filter(List<Integer> toShow,
List<Integer> toHide,
boolean inSingleFileFragment,
boolean isMediaSupported,
Menu menu) {
boolean isMediaSupported) {
boolean synchronizing = anyFileSynchronizing();
OCCapability capability = componentsGetter.getStorageManager().getCapability(account.name);
boolean endToEndEncryptionEnabled = capability.getEndToEndEncryption().isTrue();

View file

@ -28,6 +28,7 @@ import android.graphics.BitmapFactory;
import com.jakewharton.disklrucache.DiskLruCache;
import com.owncloud.android.BuildConfig;
import com.owncloud.android.lib.common.utils.Log_OC;
import com.owncloud.android.utils.BitmapUtils;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
@ -49,7 +50,7 @@ public class DiskLruImageCache {
private static final String TAG = DiskLruImageCache.class.getSimpleName();
public DiskLruImageCache(File diskCacheDir, int diskCacheSize, CompressFormat compressFormat, int quality)
throws IOException {
throws IOException {
mDiskCache = DiskLruCache.open(diskCacheDir, CACHE_VERSION, VALUE_COUNT, diskCacheSize);
mCompressFormat = compressFormat;
mCompressQuality = quality;
@ -103,10 +104,11 @@ public class DiskLruImageCache {
}
}
public Bitmap getBitmap(String key) {
public Bitmap getScaledBitmap(String key, int width, int height) {
Bitmap bitmap = null;
DiskLruCache.Snapshot snapshot = null;
InputStream inputStream = null;
BufferedInputStream buffIn = null;
String validKey = convertToValidKey(key);
try {
@ -114,18 +116,49 @@ public class DiskLruImageCache {
if (snapshot == null) {
return null;
}
final InputStream in = snapshot.getInputStream(0);
if (in != null) {
final BufferedInputStream buffIn =
new BufferedInputStream(in, IO_BUFFER_SIZE);
bitmap = BitmapFactory.decodeStream(buffIn);
inputStream = snapshot.getInputStream(0);
if (inputStream != null) {
buffIn = new BufferedInputStream(inputStream, IO_BUFFER_SIZE);
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inScaled = true;
options.inPurgeable = true;
options.inPreferQualityOverSpeed = false;
options.inMutable = false;
options.inJustDecodeBounds = true;
bitmap = BitmapFactory.decodeStream(buffIn, null, options);
// Calculate inSampleSize
options.inSampleSize = BitmapUtils.calculateSampleFactor(options, width, height);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
bitmap = BitmapFactory.decodeStream(buffIn, null, options);
}
} catch (IOException e) {
} catch (Exception e) {
Log_OC.e(TAG, e.getMessage(), e);
} finally {
if (snapshot != null) {
snapshot.close();
}
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
// nothing to do
}
}
if (buffIn != null) {
try {
buffIn.close();
} catch (IOException e) {
// nothing to do
}
}
}
if (BuildConfig.DEBUG) {
@ -133,7 +166,52 @@ public class DiskLruImageCache {
}
return bitmap;
}
public Bitmap getBitmap(String key) {
Bitmap bitmap = null;
DiskLruCache.Snapshot snapshot = null;
InputStream in = null;
BufferedInputStream buffIn = null;
String validKey = convertToValidKey(key);
try {
snapshot = mDiskCache.get(validKey);
if (snapshot == null) {
return null;
}
in = snapshot.getInputStream(0);
if (in != null) {
buffIn = new BufferedInputStream(in, IO_BUFFER_SIZE);
bitmap = BitmapFactory.decodeStream(buffIn);
}
} catch (IOException e) {
Log_OC.e(TAG, e.getMessage(), e);
} finally {
if (snapshot != null) {
snapshot.close();
}
if (buffIn != null) {
try {
buffIn.close();
} catch (IOException e) {
Log_OC.e(TAG, e.getMessage(), e);
}
}
if (in != null) {
try {
in.close();
} catch (IOException e) {
Log_OC.e(TAG, e.getMessage(), e);
}
}
}
if (BuildConfig.DEBUG) {
Log_OC.d(CACHE_TEST_DISK, bitmap == null ? "not found" : "image read from disk " + validKey);
}
return bitmap;
}
public boolean containsKey(String key) {

View file

@ -81,6 +81,7 @@ import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.ref.WeakReference;
import javax.annotation.Nullable;
import javax.inject.Inject;
import androidx.annotation.DrawableRes;
@ -149,7 +150,8 @@ public class PreviewImageFragment extends FileFragment implements Injectable {
* {@link FragmentStatePagerAdapter}
* ; TODO better solution
*/
public static PreviewImageFragment newInstance(@NonNull OCFile imageFile, boolean ignoreFirstSavedState,
public static PreviewImageFragment newInstance(@NonNull OCFile imageFile,
boolean ignoreFirstSavedState,
boolean showResizedImage) {
PreviewImageFragment frag = new PreviewImageFragment();
frag.mShowResizedImage = showResizedImage;
@ -250,9 +252,12 @@ public class PreviewImageFragment extends FileFragment implements Injectable {
if (getFile() != null) {
mImageView.setTag(getFile().getFileId());
Point screenSize = DisplayUtils.getScreenSize(getActivity());
int width = screenSize.x;
int height = screenSize.y;
if (mShowResizedImage) {
Bitmap resizedImage = ThumbnailsCacheManager.getBitmapFromDiskCache(
String.valueOf(ThumbnailsCacheManager.PREFIX_RESIZED_IMAGE + getFile().getRemoteId()));
Bitmap resizedImage = getResizedBitmap(getFile(), width, height);
if (resizedImage != null && !getFile().isUpdateThumbnailNeeded()) {
mImageView.setImageBitmap(resizedImage);
@ -260,8 +265,7 @@ public class PreviewImageFragment extends FileFragment implements Injectable {
mBitmap = resizedImage;
} else {
// show thumbnail while loading resized image
Bitmap thumbnail = ThumbnailsCacheManager.getBitmapFromDiskCache(
String.valueOf(ThumbnailsCacheManager.PREFIX_THUMBNAIL + getFile().getRemoteId()));
Bitmap thumbnail = getResizedBitmap(getFile(), width, height);
if (thumbnail != null) {
mImageView.setImageBitmap(thumbnail);
@ -273,22 +277,22 @@ public class PreviewImageFragment extends FileFragment implements Injectable {
// generate new resized image
if (ThumbnailsCacheManager.cancelPotentialThumbnailWork(getFile(), mImageView) &&
containerActivity.getStorageManager() != null) {
containerActivity.getStorageManager() != null) {
final ThumbnailsCacheManager.ResizedImageGenerationTask task =
new ThumbnailsCacheManager.ResizedImageGenerationTask(this,
mImageView,
containerActivity.getStorageManager(),
connectivityService,
containerActivity.getStorageManager().getAccount());
new ThumbnailsCacheManager.ResizedImageGenerationTask(this,
mImageView,
containerActivity.getStorageManager(),
connectivityService,
containerActivity.getStorageManager().getAccount());
if (resizedImage == null) {
resizedImage = thumbnail;
}
final ThumbnailsCacheManager.AsyncResizedImageDrawable asyncDrawable =
new ThumbnailsCacheManager.AsyncResizedImageDrawable(
MainApp.getAppContext().getResources(),
resizedImage,
task
);
new ThumbnailsCacheManager.AsyncResizedImageDrawable(
MainApp.getAppContext().getResources(),
resizedImage,
task
);
mImageView.setImageDrawable(asyncDrawable);
task.execute(getFile());
}
@ -306,6 +310,27 @@ public class PreviewImageFragment extends FileFragment implements Injectable {
}
}
private @Nullable
Bitmap getResizedBitmap(OCFile file, int width, int height) {
Bitmap cachedImage = null;
int scaledWidth = width;
int scaledHeight = height;
for (int i = 0; i < 3 && cachedImage == null; i++) {
try {
cachedImage = ThumbnailsCacheManager.getScaledBitmapFromDiskCache(
ThumbnailsCacheManager.PREFIX_RESIZED_IMAGE + file.getRemoteId(),
scaledWidth,
scaledHeight);
} catch (OutOfMemoryError e) {
scaledWidth = scaledWidth / 2;
scaledHeight = scaledHeight / 2;
}
}
return cachedImage;
}
@Override
public void onStop() {
Log_OC.d(TAG, "onStop starts");
@ -494,7 +519,6 @@ public class PreviewImageFragment extends FileFragment implements Injectable {
OCFile ocFile = params[0];
String storagePath = ocFile.getStoragePath();
try {
int maxDownScale = 3; // could be a parameter passed to doInBackground(...)
Point screenSize = DisplayUtils.getScreenSize(getActivity());
int minWidth = screenSize.x;

View file

@ -104,7 +104,7 @@ public final class BitmapUtils {
* @return The largest inSampleSize value that is a power of 2 and keeps both
* height and width larger than reqWidth and reqHeight.
*/
private static int calculateSampleFactor(Options options, int reqWidth, int reqHeight) {
public static int calculateSampleFactor(Options options, int reqWidth, int reqHeight) {
final int height = options.outHeight;
final int width = options.outWidth;