Hello,
We experienced some OutOfMemoryError and RuntimeException in production. We identified these crashes with crashlytics and we didn't reach to reproduce it. We think it happens in a RecyclerView with pictures items.
We are using Fresco like this:
val imageRequest = ImageRequestBuilder.newBuilderWithSource(uri)
.setProgressiveRenderingEnabled(true)
.setRotationOptions(RotationOptions.autoRotate())
.setLocalThumbnailPreviewsEnabled(true)
.setResizeOptions(ResizeOptions.forDimensions(width, height))
.build()
val pipelineDraweeControllerBuilder = Fresco.newDraweeControllerBuilder()
.setOldController(frescoImageView.controller)
.setControllerListener(object : BaseControllerListener<ImageInfo>() {
override fun onIntermediateImageSet(id: String?, imageInfo: ImageInfo?) {
super.onIntermediateImageSet(id, imageInfo)
// Call some callbacks
}
override fun onFinalImageSet(id: String?, imageInfo: ImageInfo?, animatable: Animatable?) {
super.onFinalImageSet(id, imageInfo, animatable)
// Call some callbacks
}
override fun onIntermediateImageFailed(id: String?, throwable: Throwable?) {
super.onIntermediateImageFailed(id, throwable)
// Call some callbacks
}
override fun onFailure(id: String?, throwable: Throwable?) {
super.onFailure(id, throwable)
// Call some callbacks
}
})
.setImageRequest(imageRequest)
drawee.hierarchy.actualImageScaleType = ScalingUtils.ScaleType.FIT_CENTER
drawee.controller = pipelineDraweeControllerBuilder.build()
Here is the stacktrace of the crashes:
Fatal Exception: java.lang.OutOfMemoryError: Failed to allocate a 71615252 byte allocation with 16772848 free bytes and 158MB until OOM
at dalvik.system.VMRuntime.newNonMovableArray(VMRuntime.java)
at android.graphics.Bitmap.nativeCreate(Bitmap.java)
at android.graphics.Bitmap.createBitmap(Bitmap.java:939)
at android.graphics.Bitmap.createBitmap(Bitmap.java:912)
at android.graphics.Bitmap.createBitmap(Bitmap.java:879)
at com.facebook.imagepipeline.memory.BitmapPool.alloc(SourceFile:51)
at com.facebook.imagepipeline.memory.BitmapPool.alloc(SourceFile:26)
at com.facebook.imagepipeline.memory.BasePool.get(SourceFile:255)
at com.facebook.imagepipeline.platform.ArtDecoder.decodeStaticImageFromStream(SourceFile:147)
at com.facebook.imagepipeline.platform.ArtDecoder.decodeJPEGFromEncodedImage(SourceFile:127)
at com.facebook.imagepipeline.decoder.DefaultImageDecoder.decodeJpeg(SourceFile:173)
at com.facebook.imagepipeline.decoder.DefaultImageDecoder$1.decode(SourceFile:56)
at com.facebook.imagepipeline.decoder.DefaultImageDecoder.decode(SourceFile:119)
at com.facebook.imagepipeline.producers.DecodeProducer$ProgressiveDecoder.doDecode(SourceFile:254)
at com.facebook.imagepipeline.producers.DecodeProducer$ProgressiveDecoder.access$300(SourceFile:113)
at com.facebook.imagepipeline.producers.DecodeProducer$ProgressiveDecoder$1.run(SourceFile:152)
at com.facebook.imagepipeline.producers.JobScheduler.doJob(SourceFile:202)
at com.facebook.imagepipeline.producers.JobScheduler.access$000(SourceFile:22)
at com.facebook.imagepipeline.producers.JobScheduler$1.run(SourceFile:73)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)
at com.facebook.imagepipeline.core.PriorityThreadFactory$1.run(SourceFile:51)
at java.lang.Thread.run(Thread.java:818)
Fatal Exception: java.lang.RuntimeException: Canvas: trying to draw too large(144609280bytes) bitmap.
at android.view.DisplayListCanvas.throwIfCannotDraw(DisplayListCanvas.java:260)
at android.graphics.Canvas.drawBitmap(Canvas.java:1420)
at android.graphics.drawable.BitmapDrawable.draw(BitmapDrawable.java:545)
at com.facebook.drawee.drawable.ForwardingDrawable.draw(SourceFile:145)
at com.facebook.drawee.drawable.ForwardingDrawable.draw(SourceFile:145)
at com.facebook.drawee.drawable.ScaleTypeDrawable.draw(SourceFile:123)
at com.facebook.drawee.drawable.FadeDrawable.drawDrawableWithAlpha(SourceFile:302)
at com.facebook.drawee.drawable.FadeDrawable.draw(SourceFile:289)
at com.facebook.drawee.drawable.ForwardingDrawable.draw(SourceFile:145)
at com.facebook.drawee.generic.RootDrawable.draw(SourceFile:81)
at android.widget.ImageView.onDraw(ImageView.java:1268)
at android.view.View.draw(View.java:18319)
at android.view.View.updateDisplayListIfDirty(View.java:17297)
at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:3950)
at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:3930)
at android.view.View.updateDisplayListIfDirty(View.java:17260)
at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:3950)
at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:3930)
at android.view.View.updateDisplayListIfDirty(View.java:17260)
at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:3950)
at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:3930)
at android.view.View.updateDisplayListIfDirty(View.java:17260)
at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:3950)
at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:3930)
at android.view.View.updateDisplayListIfDirty(View.java:17260)
at android.view.View.draw(View.java:18081)
at android.view.ViewGroup.drawChild(ViewGroup.java:3966)
at android.support.design.widget.CollapsingToolbarLayout.drawChild(SourceFile:324)
at android.view.ViewGroup.dispatchDraw(ViewGroup.java:3752)
at android.view.View.draw(View.java:18322)
at android.support.design.widget.CollapsingToolbarLayout.draw(SourceFile:286)
at android.view.View.updateDisplayListIfDirty(View.java:17297)
at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:3950)
at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:3930)
at android.view.View.updateDisplayListIfDirty(View.java:17260)
at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:3950)
at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:3930)
at android.view.View.updateDisplayListIfDirty(View.java:17260)
at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:3950)
at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:3930)
at android.view.View.updateDisplayListIfDirty(View.java:17260)
at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:3950)
at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:3930)
at android.view.View.updateDisplayListIfDirty(View.java:17260)
at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:3950)
at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:3930)
at android.view.View.updateDisplayListIfDirty(View.java:17260)
at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:3950)
at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:3930)
at android.view.View.updateDisplayListIfDirty(View.java:17260)
at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:3950)
at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:3930)
at android.view.View.updateDisplayListIfDirty(View.java:17260)
at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:3950)
at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:3930)
at android.view.View.updateDisplayListIfDirty(View.java:17260)
at android.view.ThreadedRenderer.updateViewTreeDisplayList(ThreadedRenderer.java:666)
at android.view.ThreadedRenderer.updateRootDisplayList(ThreadedRenderer.java:672)
at android.view.ThreadedRenderer.draw(ThreadedRenderer.java:780)
at android.view.ViewRootImpl.draw(ViewRootImpl.java:3112)
at android.view.ViewRootImpl.performDraw(ViewRootImpl.java:2908)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2502)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1509)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:7051)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:927)
at android.view.Choreographer.doCallbacks(Choreographer.java:702)
at android.view.Choreographer.doFrame(Choreographer.java:638)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:913)
at android.os.Handler.handleCallback(Handler.java:751)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6692)
at java.lang.reflect.Method.invoke(Method.java)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1468)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1358)
For OOM :
For RuntimeException:
I got OOM often before, now I found solution and use him in all my projects:
private final FrescoMemoryTrimmableRegistry frescoMemoryTrimmableRegistry = new FrescoMemoryTrimmableRegistry();
@Override
public void onTrimMemory(final int level) {
super.onTrimMemory(level);
switch (level) {
case ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN:
frescoMemoryTrimmableRegistry.trim(MemoryTrimType.OnAppBackgrounded);
L.d("OnAppBackgrounded - level = " + level);
break;
case ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE:
case ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW:
case ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL:
frescoMemoryTrimmableRegistry.trim(MemoryTrimType.OnCloseToDalvikHeapLimit);
clearMemoryCaches();
L.d("OnCloseToDalvikHeapLimit - level = " + level);
break;
case ComponentCallbacks2.TRIM_MEMORY_BACKGROUND:
case ComponentCallbacks2.TRIM_MEMORY_MODERATE:
case ComponentCallbacks2.TRIM_MEMORY_COMPLETE:
frescoMemoryTrimmableRegistry.trim(MemoryTrimType.OnSystemLowMemoryWhileAppInForeground);
L.d("OnSystemLowMemoryWhileAppInForeground - level = " + level);
break;
default:
L.d("default - level = " + level);
break;
}
}
Init Fresco:
ImagePipelineConfig config = ImagePipelineConfig.newBuilder(this)
.setDownsampleEnabled(true)
.setResizeAndRotateEnabledForNetwork(true)
.setBitmapsConfig(Bitmap.Config.RGB_565)
.setMainDiskCacheConfig(diskCacheConfig)
.setMemoryTrimmableRegistry(frescoMemoryTrimmableRegistry)
.setPoolFactory(poolFactory)
.build();
Fresco.initialize(this, config);
You have to clean memory when you finish work with some projects screens, resources and when system call to public void onLowMemory() and in other cases which you should know in your project.
If possible, don't store large pictures on the server. Use the cut-down versions on the lists, and use full quality only on full-screen view.
Use ImageRequest in where you can. Especially on avatars and small views. Calculate the optimal size of picture for a particular device on the client.
For example:
ImageRequest request = ImageRequestBuilder.newBuilderWithSource(uri)
.setRotationOptions(RotationOptions.autoRotate())
.setResizeOptions(new ResizeOptions(recommendedImgSize, recommendedImgSize))
.build();
controller = Fresco.newDraweeControllerBuilder()
.setImageRequest(request)
.setTapToRetryEnabled(true)
.setOldController(imageView.getController())
.build();
Ok thanks for the tips, I will try it and keep you in touch :)
Great. I'm going to close this issue for now. There are also many similar (closed) issues that you can take a look at as well.
@jaksab Can u please post the complete code for the application class? How to import FrescoMemoryTrimmableRegistry?
@ashokkumar88
Sorry, I didn't see notification of your question. Just FrescoMemoryTrimmableRegistry implements basic MemoryTrimmableRegistry interface, for example:
public class FrescoMemoryTrimmableRegistry implements MemoryTrimmableRegistry {
private final List<MemoryTrimmable> trimmables = new LinkedList<>();
@Override
public void registerMemoryTrimmable(final MemoryTrimmable trimmable) {
trimmables.add(trimmable);
}
@Override
public void unregisterMemoryTrimmable(final MemoryTrimmable trimmable) {
trimmables.remove(trimmable);
}
public synchronized void trim(final MemoryTrimType trimType) {
for (MemoryTrimmable trimmable : trimmables) {
trimmable.trim(trimType);
}
}
}
If you need Pool config, here is an example:
public class FrescoBitmapPoolParams {
private static final int MAX_SIZE_SOFT_CAP = 0;
private FrescoBitmapPoolParams() {
}
/**
* Our Bitmaps live in ashmem, meaning that they are pinned in androids' shared native memory.
* Therefore, we are not constrained by the max heap size of the dalvik heap, but we want to make
* sure we don't use too much memory on low end devices, so that we don't force other background
* process to be evicted.
*/
private static int getMaxSizeHardCap() {
final int maxMemory = (int) Math.min(Runtime.getRuntime().maxMemory(), Integer.MAX_VALUE);
if (maxMemory > 16 * ByteConstants.MB) {
return maxMemory / 2;
} else {
return maxMemory / 3;
}
}
/**
* This will cause all get/release calls to behave like alloc/free calls i.e. no pooling.
*/
private static final SparseIntArray DEFAULT_BUCKETS = new SparseIntArray(0);
public static PoolParams get() {
return new PoolParams(
MAX_SIZE_SOFT_CAP,
getMaxSizeHardCap(),
DEFAULT_BUCKETS
);
}
}
Most helpful comment
I got OOM often before, now I found solution and use him in all my projects:
Init Fresco:
You have to clean memory when you finish work with some projects screens, resources and when system call to public void onLowMemory() and in other cases which you should know in your project.
If possible, don't store large pictures on the server. Use the cut-down versions on the lists, and use full quality only on full-screen view.
Use ImageRequest in where you can. Especially on avatars and small views. Calculate the optimal size of picture for a particular device on the client.
For example: