mirror of
https://github.com/nextcloud/android.git
synced 2024-11-22 05:05:31 +03:00
Fix OOM during image browsing
Signed-off-by: tobiasKaminsky <tobias@kaminsky.me>
This commit is contained in:
parent
2c9b0758e4
commit
45315f1486
6 changed files with 149 additions and 31 deletions
|
@ -1 +1 @@
|
||||||
381
|
383
|
||||||
|
|
|
@ -211,6 +211,23 @@ public final class ThumbnailsCacheManager {
|
||||||
return mThumbnailCache.containsKey(key);
|
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) {
|
public static Bitmap getBitmapFromDiskCache(String key) {
|
||||||
synchronized (mThumbnailsDiskCacheLock) {
|
synchronized (mThumbnailsDiskCacheLock) {
|
||||||
// Wait while disk cache is started from background thread
|
// Wait while disk cache is started from background thread
|
||||||
|
|
|
@ -133,7 +133,7 @@ public class FileMenuFilter {
|
||||||
List<Integer> toShow = new ArrayList<>();
|
List<Integer> toShow = new ArrayList<>();
|
||||||
List<Integer> toHide = new ArrayList<>();
|
List<Integer> toHide = new ArrayList<>();
|
||||||
|
|
||||||
filter(toShow, toHide, inSingleFileFragment, isMediaSupported, menu);
|
filter(toShow, toHide, inSingleFileFragment, isMediaSupported);
|
||||||
|
|
||||||
for (int i : toShow) {
|
for (int i : toShow) {
|
||||||
showMenuItem(menu.findItem(i));
|
showMenuItem(menu.findItem(i));
|
||||||
|
@ -193,8 +193,7 @@ public class FileMenuFilter {
|
||||||
private void filter(List<Integer> toShow,
|
private void filter(List<Integer> toShow,
|
||||||
List<Integer> toHide,
|
List<Integer> toHide,
|
||||||
boolean inSingleFileFragment,
|
boolean inSingleFileFragment,
|
||||||
boolean isMediaSupported,
|
boolean isMediaSupported) {
|
||||||
Menu menu) {
|
|
||||||
boolean synchronizing = anyFileSynchronizing();
|
boolean synchronizing = anyFileSynchronizing();
|
||||||
OCCapability capability = componentsGetter.getStorageManager().getCapability(account.name);
|
OCCapability capability = componentsGetter.getStorageManager().getCapability(account.name);
|
||||||
boolean endToEndEncryptionEnabled = capability.getEndToEndEncryption().isTrue();
|
boolean endToEndEncryptionEnabled = capability.getEndToEndEncryption().isTrue();
|
||||||
|
|
|
@ -28,6 +28,7 @@ import android.graphics.BitmapFactory;
|
||||||
import com.jakewharton.disklrucache.DiskLruCache;
|
import com.jakewharton.disklrucache.DiskLruCache;
|
||||||
import com.owncloud.android.BuildConfig;
|
import com.owncloud.android.BuildConfig;
|
||||||
import com.owncloud.android.lib.common.utils.Log_OC;
|
import com.owncloud.android.lib.common.utils.Log_OC;
|
||||||
|
import com.owncloud.android.utils.BitmapUtils;
|
||||||
|
|
||||||
import java.io.BufferedInputStream;
|
import java.io.BufferedInputStream;
|
||||||
import java.io.BufferedOutputStream;
|
import java.io.BufferedOutputStream;
|
||||||
|
@ -49,7 +50,7 @@ public class DiskLruImageCache {
|
||||||
private static final String TAG = DiskLruImageCache.class.getSimpleName();
|
private static final String TAG = DiskLruImageCache.class.getSimpleName();
|
||||||
|
|
||||||
public DiskLruImageCache(File diskCacheDir, int diskCacheSize, CompressFormat compressFormat, int quality)
|
public DiskLruImageCache(File diskCacheDir, int diskCacheSize, CompressFormat compressFormat, int quality)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
mDiskCache = DiskLruCache.open(diskCacheDir, CACHE_VERSION, VALUE_COUNT, diskCacheSize);
|
mDiskCache = DiskLruCache.open(diskCacheDir, CACHE_VERSION, VALUE_COUNT, diskCacheSize);
|
||||||
mCompressFormat = compressFormat;
|
mCompressFormat = compressFormat;
|
||||||
mCompressQuality = quality;
|
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;
|
Bitmap bitmap = null;
|
||||||
DiskLruCache.Snapshot snapshot = null;
|
DiskLruCache.Snapshot snapshot = null;
|
||||||
|
InputStream inputStream = null;
|
||||||
|
BufferedInputStream buffIn = null;
|
||||||
String validKey = convertToValidKey(key);
|
String validKey = convertToValidKey(key);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -114,18 +116,49 @@ public class DiskLruImageCache {
|
||||||
if (snapshot == null) {
|
if (snapshot == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
final InputStream in = snapshot.getInputStream(0);
|
inputStream = snapshot.getInputStream(0);
|
||||||
if (in != null) {
|
if (inputStream != null) {
|
||||||
final BufferedInputStream buffIn =
|
buffIn = new BufferedInputStream(inputStream, IO_BUFFER_SIZE);
|
||||||
new BufferedInputStream(in, IO_BUFFER_SIZE);
|
|
||||||
bitmap = BitmapFactory.decodeStream(buffIn);
|
// 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);
|
Log_OC.e(TAG, e.getMessage(), e);
|
||||||
} finally {
|
} finally {
|
||||||
if (snapshot != null) {
|
if (snapshot != null) {
|
||||||
snapshot.close();
|
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) {
|
if (BuildConfig.DEBUG) {
|
||||||
|
@ -133,7 +166,52 @@ public class DiskLruImageCache {
|
||||||
}
|
}
|
||||||
|
|
||||||
return bitmap;
|
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) {
|
public boolean containsKey(String key) {
|
||||||
|
|
|
@ -81,6 +81,7 @@ import java.io.FileNotFoundException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.lang.ref.WeakReference;
|
import java.lang.ref.WeakReference;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import androidx.annotation.DrawableRes;
|
import androidx.annotation.DrawableRes;
|
||||||
|
@ -149,7 +150,8 @@ public class PreviewImageFragment extends FileFragment implements Injectable {
|
||||||
* {@link FragmentStatePagerAdapter}
|
* {@link FragmentStatePagerAdapter}
|
||||||
* ; TODO better solution
|
* ; TODO better solution
|
||||||
*/
|
*/
|
||||||
public static PreviewImageFragment newInstance(@NonNull OCFile imageFile, boolean ignoreFirstSavedState,
|
public static PreviewImageFragment newInstance(@NonNull OCFile imageFile,
|
||||||
|
boolean ignoreFirstSavedState,
|
||||||
boolean showResizedImage) {
|
boolean showResizedImage) {
|
||||||
PreviewImageFragment frag = new PreviewImageFragment();
|
PreviewImageFragment frag = new PreviewImageFragment();
|
||||||
frag.mShowResizedImage = showResizedImage;
|
frag.mShowResizedImage = showResizedImage;
|
||||||
|
@ -250,9 +252,12 @@ public class PreviewImageFragment extends FileFragment implements Injectable {
|
||||||
if (getFile() != null) {
|
if (getFile() != null) {
|
||||||
mImageView.setTag(getFile().getFileId());
|
mImageView.setTag(getFile().getFileId());
|
||||||
|
|
||||||
|
Point screenSize = DisplayUtils.getScreenSize(getActivity());
|
||||||
|
int width = screenSize.x;
|
||||||
|
int height = screenSize.y;
|
||||||
|
|
||||||
if (mShowResizedImage) {
|
if (mShowResizedImage) {
|
||||||
Bitmap resizedImage = ThumbnailsCacheManager.getBitmapFromDiskCache(
|
Bitmap resizedImage = getResizedBitmap(getFile(), width, height);
|
||||||
String.valueOf(ThumbnailsCacheManager.PREFIX_RESIZED_IMAGE + getFile().getRemoteId()));
|
|
||||||
|
|
||||||
if (resizedImage != null && !getFile().isUpdateThumbnailNeeded()) {
|
if (resizedImage != null && !getFile().isUpdateThumbnailNeeded()) {
|
||||||
mImageView.setImageBitmap(resizedImage);
|
mImageView.setImageBitmap(resizedImage);
|
||||||
|
@ -260,8 +265,7 @@ public class PreviewImageFragment extends FileFragment implements Injectable {
|
||||||
mBitmap = resizedImage;
|
mBitmap = resizedImage;
|
||||||
} else {
|
} else {
|
||||||
// show thumbnail while loading resized image
|
// show thumbnail while loading resized image
|
||||||
Bitmap thumbnail = ThumbnailsCacheManager.getBitmapFromDiskCache(
|
Bitmap thumbnail = getResizedBitmap(getFile(), width, height);
|
||||||
String.valueOf(ThumbnailsCacheManager.PREFIX_THUMBNAIL + getFile().getRemoteId()));
|
|
||||||
|
|
||||||
if (thumbnail != null) {
|
if (thumbnail != null) {
|
||||||
mImageView.setImageBitmap(thumbnail);
|
mImageView.setImageBitmap(thumbnail);
|
||||||
|
@ -273,22 +277,22 @@ public class PreviewImageFragment extends FileFragment implements Injectable {
|
||||||
|
|
||||||
// generate new resized image
|
// generate new resized image
|
||||||
if (ThumbnailsCacheManager.cancelPotentialThumbnailWork(getFile(), mImageView) &&
|
if (ThumbnailsCacheManager.cancelPotentialThumbnailWork(getFile(), mImageView) &&
|
||||||
containerActivity.getStorageManager() != null) {
|
containerActivity.getStorageManager() != null) {
|
||||||
final ThumbnailsCacheManager.ResizedImageGenerationTask task =
|
final ThumbnailsCacheManager.ResizedImageGenerationTask task =
|
||||||
new ThumbnailsCacheManager.ResizedImageGenerationTask(this,
|
new ThumbnailsCacheManager.ResizedImageGenerationTask(this,
|
||||||
mImageView,
|
mImageView,
|
||||||
containerActivity.getStorageManager(),
|
containerActivity.getStorageManager(),
|
||||||
connectivityService,
|
connectivityService,
|
||||||
containerActivity.getStorageManager().getAccount());
|
containerActivity.getStorageManager().getAccount());
|
||||||
if (resizedImage == null) {
|
if (resizedImage == null) {
|
||||||
resizedImage = thumbnail;
|
resizedImage = thumbnail;
|
||||||
}
|
}
|
||||||
final ThumbnailsCacheManager.AsyncResizedImageDrawable asyncDrawable =
|
final ThumbnailsCacheManager.AsyncResizedImageDrawable asyncDrawable =
|
||||||
new ThumbnailsCacheManager.AsyncResizedImageDrawable(
|
new ThumbnailsCacheManager.AsyncResizedImageDrawable(
|
||||||
MainApp.getAppContext().getResources(),
|
MainApp.getAppContext().getResources(),
|
||||||
resizedImage,
|
resizedImage,
|
||||||
task
|
task
|
||||||
);
|
);
|
||||||
mImageView.setImageDrawable(asyncDrawable);
|
mImageView.setImageDrawable(asyncDrawable);
|
||||||
task.execute(getFile());
|
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
|
@Override
|
||||||
public void onStop() {
|
public void onStop() {
|
||||||
Log_OC.d(TAG, "onStop starts");
|
Log_OC.d(TAG, "onStop starts");
|
||||||
|
@ -494,7 +519,6 @@ public class PreviewImageFragment extends FileFragment implements Injectable {
|
||||||
OCFile ocFile = params[0];
|
OCFile ocFile = params[0];
|
||||||
String storagePath = ocFile.getStoragePath();
|
String storagePath = ocFile.getStoragePath();
|
||||||
try {
|
try {
|
||||||
|
|
||||||
int maxDownScale = 3; // could be a parameter passed to doInBackground(...)
|
int maxDownScale = 3; // could be a parameter passed to doInBackground(...)
|
||||||
Point screenSize = DisplayUtils.getScreenSize(getActivity());
|
Point screenSize = DisplayUtils.getScreenSize(getActivity());
|
||||||
int minWidth = screenSize.x;
|
int minWidth = screenSize.x;
|
||||||
|
|
|
@ -104,7 +104,7 @@ public final class BitmapUtils {
|
||||||
* @return The largest inSampleSize value that is a power of 2 and keeps both
|
* @return The largest inSampleSize value that is a power of 2 and keeps both
|
||||||
* height and width larger than reqWidth and reqHeight.
|
* 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 height = options.outHeight;
|
||||||
final int width = options.outWidth;
|
final int width = options.outWidth;
|
||||||
|
|
Loading…
Reference in a new issue