Fresco: OOM & RuntimeException

Created on 14 Jun 2018  路  5Comments  路  Source: facebook/fresco

Description

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)

Additional Information

For OOM :

  • Android 5 & 6

For RuntimeException:

  • Android 7 & 8

Most helpful comment

I got OOM often before, now I found solution and use him in all my projects:

  1. In Application class:
 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);
  1. 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.

  2. 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.

  3. 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();
  1. Don't use large placeholders images with alpha channel and other in placeholderImage and failureImage attributes, use lite images with right configuration and size. (I lost twitching on weak devices when I change my placeholders).

All 5 comments

I got OOM often before, now I found solution and use him in all my projects:

  1. In Application class:
 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);
  1. 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.

  2. 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.

  3. 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();
  1. Don't use large placeholders images with alpha channel and other in placeholderImage and failureImage attributes, use lite images with right configuration and size. (I lost twitching on weak devices when I change my placeholders).

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
    );
}

}

Was this page helpful?
0 / 5 - 0 ratings

Related issues

satyarths picture satyarths  路  3Comments

goodev picture goodev  路  4Comments

kingty picture kingty  路  4Comments

eldk picture eldk  路  3Comments

rhettor picture rhettor  路  3Comments