20

Edited:

  • In my application, I am loading more than 300 images in home page. I used glide to load images. I'm getting Out of Memory Error.

I have used large heap true in manifest :

android:largeHeap="true"

Glide Version:

compile 'com.github.bumptech.glide:glide:3.7.0'

Device/Android Version:

Nexus Device 6.0 version

Every images I'm getting from Json would be 800kb to 1mb.

activity_layout:

<RelativeLayout
    android:id="@+id/home_layout_bottom"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_below="@+id/home_layout_top_recycler"
    android:layout_margin="5dp">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/rv_list_tab_home_recycler"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:clipToPadding="false"
        android:scrollbars="vertical"
        android:visibility="visible" />

    <TextView
        android:id="@+id/no_user_posts_item_tv_recycler"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/rv_list_tab_home_recycler"
        android:layout_marginTop="80dp"
        android:layout_centerHorizontal="true"
        android:text="@string/txt_no_posts_available"
        android:textColor="@color/txt_common_black"
        android:textSize="@dimen/txt_size" />
</RelativeLayout>

adapter code:

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    ViewHolder holder = null;

    final HomePostItems rowItem = getItem(position);

    LayoutInflater mInflater = (LayoutInflater) context.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
    if (convertView == null) {

        convertView = mInflater.inflate(R.layout.lv_adapter_post_items_layout, null);

      holder = new ViewHolder();

      holder.ivPostedImage = (ImageView) convertView.findViewById(R.id.iv_posted_img);


        convertView.setTag(holder);
    } else {
        holder = (ViewHolder) convertView.getTag();
    }

      ..................

          Glide.with(context).load(rowItem.getPosteduserpostimage())
                        .placeholder(R.drawable.golive_load_image).error(R.drawable.golive_cancel_image)
                        .override(600, 200)
                        .into(holder.ivPostedImage);

adapter_layout.xml:

<RelativeLayout
    android:id="@+id/rl_lv_user_post_adapter_img_holder_home"
    android:layout_width="match_parent"
    android:layout_height="300dp"
    android:layout_marginLeft="1dp"
    android:layout_marginRight="1dp"
    android:layout_below="@+id/tv_user_posted_msg_post_items_home" >

    <ImageView
        android:id="@+id/iv_posted_img_home"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_centerInParent="true"
        android:scaleType="fitXY"
        android:background="#ffffff"
        android:contentDescription="@string/cont_desc"/>
</RelativeLayout>

Logcat:

Request threw uncaught throwable
java.util.concurrent.ExecutionException: java.lang.OutOfMemoryError: Failed to allocate a 6365196 byte allocation with 865912 free bytes and 845KB until OOM
at java.util.concurrent.FutureTask.report(FutureTask.java:94)
at java.util.concurrent.FutureTask.get(FutureTask.java:164)
at com.bumptech.glide.load.engine.executor.FifoPriorityThreadPoolExecutor.afterExecute(FifoPriorityThreadPoolExecutor.java:96)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1121)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:588)
at java.lang.Thread.run(Thread.java:818)
at com.bumptech.glide.load.engine.executor.FifoPriorityThreadPoolExecutor$DefaultThreadFactory$1.run(FifoPriorityThreadPoolExecutor.java:118)
Caused by: java.lang.OutOfMemoryError: Failed to allocate a 6365196 byte allocation with 865912 free bytes and 845KB until OOM
at dalvik.system.VMRuntime.newNonMovableArray(Native Method)
at android.graphics.BitmapFactory.nativeDecodeStream(Native Method)
at android.graphics.BitmapFactory.decodeStreamInternal(BitmapFactory.java:635)
at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:611)
at com.bumptech.glide.load.resource.bitmap.Downsampler.decodeStream(Downsampler.java:329)
at com.bumptech.glide.load.resource.bitmap.Downsampler.downsampleWithSize(Downsampler.java:220)
at com.bumptech.glide.load.resource.bitmap.Downsampler.decode(Downsampler.java:153)
at com.bumptech.glide.load.resource.bitmap.StreamBitmapDecoder.decode(StreamBitmapDecoder.java:50)
at com.bumptech.glide.load.resource.bitmap.StreamBitmapDecoder.decode(StreamBitmapDecoder.java:19)
at com.bumptech.glide.load.resource.bitmap.ImageVideoBitmapDecoder.decode(ImageVideoBitmapDecoder.java:39)
at com.bumptech.glide.load.resource.bitmap.ImageVideoBitmapDecoder.decode(ImageVideoBitmapDecoder.java:20)
at com.bumptech.glide.load.resource.gifbitmap.GifBitmapWrapperResourceDecoder.decodeBitmapWrapper(GifBitmapWrapperResourceDecoder.java:121)
at com.bumptech.glide.load.resource.gifbitmap.GifBitmapWrapperResourceDecoder.decodeStream(GifBitmapWrapperResourceDecoder.java:94)
at com.bumptech.glide.load.resource.gifbitmap.GifBitmapWrapperResourceDecoder.decode(GifBitmapWrapperResourceDecoder.java:71)
at com.bumptech.glide.load.resource.gifbitmap.GifBitmapWrapperResourceDecoder.decode(GifBitmapWrapperResourceDecoder.java:61)
at com.bumptech.glide.load.resource.gifbitmap.GifBitmapWrapperResourceDecoder.decode(GifBitmapWrapperResourceDecoder.java:22)
at com.bumptech.glide.load.engine.DecodeJob.decodeFromSourceData(DecodeJob.java:190)
at com.bumptech.glide.load.engine.DecodeJob.decodeSource(DecodeJob.java:177)
at com.bumptech.glide.load.engine.DecodeJob.decodeFromSource(DecodeJob.java:128)
at com.bumptech.glide.load.engine.EngineRunnable.decodeFromSource(EngineRunnable.java:122)
at com.bumptech.glide.load.engine.EngineRunnable.decode(EngineRunnable.java:101)
at com.bumptech.glide.load.engine.EngineRunnable.run(EngineRunnable.java:58)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:423)
at java.util.concurrent.FutureTask.run(FutureTask.java:237)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1113)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:588) 
at java.lang.Thread.run(Thread.java:818) 
at com.bumptech.glide.load.engine.executor.FifoPriorityThreadPoolExecutor$DefaultThreadFactory$1.run(FifoPriorityThreadPoolExecutor.java:118)

I don't know how to fix this OOM issue. Please share your suggestion, if you have already familiar with this issue.

Reaz Murshed
  • 23,691
  • 13
  • 78
  • 98
Stephen
  • 9,899
  • 16
  • 90
  • 137

11 Answers11

19
  • I solved this issue by removing nested scroll view placed above recyclerview. Why OutOfMemory error occurred means, when loading more than 200 images in home page, it is loading all 200 images because of using nested scroll view above recyclerview.

  • So I can't check the logcat image view width and height one by one in adapter.

  • After removed nested scroll view fixed out of memory error.because it will load only 3 images displayed in device when coming to home activity.

  • Also check this, how to use scroll instead of nested scroll view.

Community
  • 1
  • 1
Stephen
  • 9,899
  • 16
  • 90
  • 137
13

This is not an exact solution to your problem, but you need to keep these things in mind while loading images in a list using Glide.

The main threatening part of your problem is the image size. The image you're getting is almost 1mb each! Which is in fact too large for displaying them into a list having 300+ items. So if you're doing the server side too, its always recommended to have the images in several different sizes.

For example, in case of showing a friend list along with their profile pictures, I would suggest you get the whole list first from the server. Then fetch all of the profile images and store them locally. Then populate the ListView. And the most important part is while uploading a profile picture of an user to the server, after uploading it, the server needs to keep several sizes of it e.g. low, mid and high res version. So that while serving the profile picture urls for the ListView the server might provide the images with low res as they'll be used most likely for thumbnails.

Using RecyclerView instead of ListView is a good call too. But it won't solve the problem you've here when you're in a low-end device.

OMM has nothing to do with you can solve programatically. You need to resize your image to a lower res version.

You can check for the Glide's caching mechanism too. I would suggest you use the caching strategy so that every time you don't have to load the image from server.

Good luck.

Reaz Murshed
  • 23,691
  • 13
  • 78
  • 98
8

Using Glide doesn't guarantee no Out of Memory errors, you need to use several small steps to reduce the probability to not get OOM's.

Step 1: Understand the caching mechanism in Glide

Step 2: I prefer to load thumbnails into recyclerview

Glide  
    .with( context )
    .load( UsageExampleGifAndVideos.gifUrl )
    .thumbnail( 0.1f )
    .into( imageView2 );

Remember to always request small size image if bigger or HD images are not required.

Murtaza Khursheed Hussain
  • 15,176
  • 7
  • 58
  • 83
7
  1. Make sure the ImageView has match_parent or fixed dp as dimensions wrap_content makes Glide load full resolution Bitmaps.
  2. .placeholder() shows an image instead of empty space while loading large bitmap
  3. .thumbnail(float) loads a downsampled version fast while the bigger image is loading in the background
  4. Also look around the Glide issues, maybe you find something helpful.
Jamil Hasnine Tamim
  • 4,389
  • 27
  • 43
  • For more information http://stackoverflow.com/questions/30718303/outofmemoryexception-while-loading-large-size-images-using-glide – Sreehari Oct 03 '16 at 10:30
5

Use recyclerView instead of ListView. It reusable single item for rendering items. I am using glide with recyclerView where i am loading wallpapers with 100+ items.

In ListView every time you are creating view, if you have 100+ view and it will create 100+ views where as in recyclerview it creates how many visible items are there in screen +2.

subrahmanyam boyapati
  • 2,836
  • 1
  • 18
  • 28
  • A ListView too would not cause any OOMEs – Sakchham Oct 01 '16 at 09:06
  • 5
    ListView do not create 100+ views, but reuses old view - `convertView`. So this is no case there. @Naruto what is Android version on your test device? – Roman_D Oct 03 '16 at 05:27
  • Ok, then check image size (raw Bitmap size, not JPEG size), how it is large? Does OOM happen even if you set `skipMemoryCache( true )` and `.diskCacheStrategy(DiskCacheStrategy.NONE)` ? – Roman_D Oct 03 '16 at 05:46
3

The reason is while scrolling, glide keep doing image process even related views removes from list. Add this code in your listview's onScrollStateChanged.

if (view.getContext() != null) {
        switch (scrollState) {
            case SCROLL_STATE_IDLE:
                Glide.with(view.getContext()).resumeRequests();
                break;
            case SCROLL_STATE_TOUCH_SCROLL:
            case SCROLL_STATE_FLING:
                Glide.with(view.getContext()).pauseRequests();
                break;
        }
    }
invisbo
  • 3,399
  • 1
  • 16
  • 20
3

I faced the similar problem . I am sharing the way I solved it . Create a folder named drawable-nodpi put your golive_load_image and golive_cancel_im‌​age file into that folder , and remove those two image file from other place like drawable-ldpi,drawable-hdpi etc (if you have there ) . And add skipMemoryCache( true )

     Glide.with(context).load(rowItem.getPosteduserpostimage())
                            .skipMemoryCache( true )
                            .placeholder(R.drawable.golive_load_image).error(R.drawable.golive_cancel_image)
                            .override(600, 200)
                            .into(holder.ivPostedImage);
Mithun Sarker Shuvro
  • 3,902
  • 6
  • 32
  • 64
  • 4
    According to Glide's official documentation https://bumptech.github.io/glide/doc/caching.html it is not recommendable to skipMemoryCache --> *In general you want to try to avoid skipping caches. It’s vastly faster to load an image from cache than it is to retrieve, decode, and transform it to create a new thumbnail.* – Carlos Daniel Mar 25 '19 at 16:22
2
  • your images should not be too large ( if they are, use .thumbnail(...f) )
  • use .skipMemoryCache(true) if you are not force to keep images in cache
  • you can use .diskCacheStrategy(DiskCacheStrategy.NONE) to deactivate the disk cache
Misagh
  • 3,403
  • 1
  • 20
  • 17
2

In order to prevent Out of Memory error one can just make precautions to make sure that it will not occurs. So the answer of this question is actually a bunch of suggestions one can suggest. So do I.

As suggested by @Reaz Murshed I am also recommending to have the images in several different sizes. Apart from this I would like to add few more things that might help you analyze this issue and solve it.

As far as I remember OOM was always a usage error, largeHeap will just delay it; or if it's a large load then maybe it's not possible. So I am suggesting you to follow this link to diagnose for memory leaks.

Stack traces of OutOfMemoryErrors don't help at all for diagnosing them. It just tells you it's broken and something filled up the memory. This filling happens much before the actual exception was thrown. This also implies that usually whatever throws the OOM is not actually the culprit. The only exception to this is when the amount of wannabe allocated memory is simply too big, for example: an array to be allocated is bigger than the maximum memory, then you know that some calculation went really wrong, like a 32000x32000@4 image would take around 4GB of memory.

If you can reproduce: get a heap dump and analyze your app's usage. Normal OOM diagnostic steps:

  • Reproduce exception (wait till you see it in LogCat)

  • Take a heap dump(To analyze memory leaks)

  • Analyze it for big objects and leaks

In above shared link there is few another links regarding how to take heap dump? and issues that are identical with this one.

So I suggest you to analyze for memory leaks and take necessary steps to prevent OOM.

Hope this will help you.

Pravin Divraniya
  • 4,223
  • 2
  • 32
  • 49
1

Probably a different approach can be taken to resolve this. To achieve this you can use a different ImageAdapter with

Glide.with(mActivity).loadFromMediaStore(_imageInfo.getmUri())

this does not crash being using MediaStoreThumbFetcher

To have more control over the load do the following using Glide v4

// usage:
Glide.with(mActivity).load(_imageInfo)....

// in GlideModule.registerComponents
registry.prepend(ImageInfo.class, ImageInfo.class, new UnitModelLoader.Factory<ImageInfo>());
registry.prepend(ImageInfo.class, Bitmap.class, new ImageInfoBitmapDecoder(context));

class ImageInfoBitmapDecoder implements ResourceDecoder<ImageInfo, Bitmap> {
    private final ContentResolver contentResolver;
    private final BitmapPool pool;
    public ImageInfoBitmapDecoder(Context context) {
        this.contentResolver = context.getContentResolver();
        this.pool = Glide.get(context).getBitmapPool();
    }
    @Override public boolean handles(ImageInfo source, Options options) { return true; }
    @Override public @Nullable Resource<Bitmap> decode(ImageInfo source, int width, int height, Options options) {
        Bitmap thumb = Thumbnails.getThumbnail(contentResolver, source.getmId(), Thumbnails.MINI_KIND, null);
        return BitmapResource.obtain(thumb, pool);
    }
}

Using following API's we can figure out free memory left out and size of the bitmap

You can check available memory and bitmap details (if needed) as a pre-check

Check the amount of free memory left

public static final float BYTES_IN_MB = 1024.0f * 1024.0f;

    public static float megabytesFree() {
        final Runtime rt = Runtime.getRuntime();
        final float bytesUsed = rt.totalMemory();
        final float mbUsed = bytesUsed/BYTES_IN_MB;
        final float mbFree = megabytesAvailable() - mbUsed;
        return mbFree;
    }

    public static float megabytesAvailable() {
        final Runtime rt = Runtime.getRuntime();
        final float bytesAvailable = rt.maxMemory();
        return bytesAvailable/BYTES_IN_MB;
}

Check how big is the bitmap we want to load

private void readBitmapInfo() {
        final Resources res = getActivity().getResources();
        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeResource(res, R.drawable.brasil, options);
        final float imageHeight = options.outHeight;
        final float imageWidth = options.outWidth;
        final String imageMimeType = options.outMimeType;
        Log.d(TAG, "w,h, type:"+imageWidth+", "+imageHeight+", "+imageMimeType);
        Log.d(TAG, "estimated memory required in MB: "+imageWidth * imageHeight * BYTES_PER_PX/MemUtils.BYTES_IN_MB);
}

For more details go through Java methods to check memory and bitmap and github discussion

Sreehari
  • 5,621
  • 2
  • 25
  • 59
0

I faced the same issue and solved it by adding android:largeHeap="true" in the application tag of my manifest file like below.

<manifest>
   ...
   <application
    .....
    android:largeHeap="true"
    ....
   >
   ....
   </application>
</manifest>

NB: this should be your last option, as using largeHeap:true is not recommended for solving simple OOM problems.

Hagos Alema
  • 321
  • 2
  • 10