Glide: High heap size after loading images

Created on 3 Apr 2016  路  13Comments  路  Source: bumptech/glide

Glide Version: 3.7

Integration libraries: OkHttp3

Device/Android Version: Redmi 2 API 19

Issue details / Repro steps / Use case background: My app's heap size goes up to about 30mb on the initial load, and after using for quite some time hits about 55-60 MB. This seems really high as there are some devices with memory limits as low as 16 MB. MAT shows that the culprit is likely to be a bunch of Bitmaps (33% usage) however I'm at loss as to what to do next.

I have a swipeable Viewpager (4 tabs). Each tab has a fragment with a recyclerview, which loads a bunch of images. It's very similar to Instagram.

Glide load line / GlideModule (if any) / list Adapter code (if any):

public class HomeFeedAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>  {

    private List<HomeFeedPosts.data>postsList;
    private final int VIEW_TYPE_ITEM = 0;
    private final int VIEW_TYPE_LOADING = 1;


    private final RequestManager glide;

    public HomeFeedAdapter(List<HomeFeedPosts.data> postsList,RequestManager glide){
        this.postsList = postsList;
        this.glide = glide;
    }

    public void refreshRecyclerView(List<HomeFeedPosts.data>postsList){
        this.postsList.clear();
        this.postsList.addAll(postsList);
        notifyDataSetChanged();

    }
    public void addMoreItem(List<HomeFeedPosts.data>postsList){
        this.postsList.addAll(postsList);
        notifyDataSetChanged();
    }

    public void loadProgressBar(){
        postsList.add(null);
        notifyItemInserted(postsList.size() - 1); 
    }

    public void removeProgressBar(){
        postsList.remove(postsList.size()-1);
        notifyItemRemoved(postsList.size());
    }



    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        if (viewType == VIEW_TYPE_ITEM) {
            View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.home_feed_post_photo, parent, false);
            return new PostsViewHolder(itemView);
        }  else if (viewType == VIEW_TYPE_LOADING) {
            View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.progressbar_layout_loadmore, parent, false);
            return new ProgressBarViewHolder(view);
        }
        return null;
    }


    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        if (holder instanceof PostsViewHolder){
            HomeFeedPosts.data postsData = postsList.get(position);

            ((PostsViewHolder) holder).title.setText(postsData.user.username);

            glide.load(postsData.images.standard_resolution.url)
                    .priority(Priority.IMMEDIATE)
                    .placeholder(R.drawable.grey_placeholder)
                    .into(((PostsViewHolder) holder).imagePost);

            glide.load(postsData.user.profile_picture)
                    .priority(Priority.LOW)
                    .placeholder(R.drawable.grey_placeholder)
                    .into(((PostsViewHolder) holder).imageAvatar);


        } else if (holder instanceof ProgressBarViewHolder){
            ((ProgressBarViewHolder) holder).progressBar.setIndeterminate(true);
        }
    }

    @Override
    public int getItemCount() {
        return postsList.size();
    }

    static class PostsViewHolder extends RecyclerView.ViewHolder{
        private TextView title;
        private FixedImageView imagePost;
        private ImageView imageAvatar;
        public PostsViewHolder(View itemView) {
            super(itemView);
            title = (TextView) itemView.findViewById(R.id.username);
            imagePost = (FixedImageView) itemView.findViewById(R.id.imagePost);
            imageAvatar = (ImageView) itemView.findViewById(R.id.imageAvatar);
        }
    }

    static class ProgressBarViewHolder extends RecyclerView.ViewHolder {
        private ProgressBar progressBar;

        public ProgressBarViewHolder(View itemView) {
            super(itemView);
            progressBar = (ProgressBar) itemView.findViewById(R.id.progressBar1);
        }
    }

    @Override
    public void onAttachedToRecyclerView(RecyclerView recyclerView) {
        super.onAttachedToRecyclerView(recyclerView);
    }

    @Override
    public int getItemViewType(int position) {
        if (postsList.get(position) == null) return  VIEW_TYPE_LOADING;
        return VIEW_TYPE_ITEM;
    }

    @Override
    public void onViewRecycled(RecyclerView.ViewHolder holder) {
        super.onViewRecycled(holder);
        if (holder instanceof PostsViewHolder){
            Glide.clear(((PostsViewHolder) holder).imagePost);
            Glide.clear(((PostsViewHolder) holder).imageAvatar);
        }
    }

}

Layout XML:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_marginBottom="12dp"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <LinearLayout
        android:background="#ffffff"
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <ImageView
            tools:src="@drawable/img2"
            android:id="@+id/imageAvatar"
            android:layout_margin="5dp"
            android:layout_width="30dp"
            android:layout_height="30dp" />

        <TextView
            android:layout_weight="8"
            fontPath="fonts/GothamRounded-Medium.otf"
            android:singleLine="true"
            android:textColor="@color/AlmostBlack"
            android:layout_gravity="center_vertical"
            android:layout_marginStart="5dp"
            android:layout_marginLeft="5dp"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="14sp"
            android:id="@+id/username" />
        <Button
            fontPath = "fonts/Lato-Bold.ttf"
            android:text="FOLLOW"
            android:layout_marginRight="5dp"
            android:layout_marginLeft="5dp"
            android:layout_weight="1"
            android:layout_gravity="center_vertical"
            android:background="@drawable/roundbuttons"
            android:layout_width="wrap_content"
            android:layout_height="25dp" />

    </LinearLayout>

    <com.saphala.gokilpedia_mobile.util.FixedImageView
        tools:src = "@drawable/img2"
        android:id="@+id/imagePost"
        android:adjustViewBounds="true"
        android:scaleType="fitCenter"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

</LinearLayout>


Stack trace / LogCat:

paste stack trace and/or log here

screenshot 19

screenshot 20

screenshot 21

question

All 13 comments

From the images it looks like you're observing the normal pooling. Try to get a heap dump / memory usage count after calling Glide.get(context).clearMemory(), that should free up space. But it shouldn't be called in production as pooling is why Glide is so fast. If you think about it if an app doesn't use the memory it's given then it's wasting it. It's much easier to grab an already allocated ~1M Bitmap than create a new one. If you try Glide on a lower end phone you should see that the pool is smaller to accommodate for the constraints.

Note that onViewRecycled is only called when you're scrolling. To force freeing resources when the ViewPager page is changed you need to setAdapter(null) on the list so the active Bitmaps are freed.

Where exactly should Glide.get(context).clearMemory() be called?
Also, I noticed that the heap is not reset to zero after quitting the app, is there any way to clear the heap?

I usually add a button on options menu, it doesn't matter where the call is since it's effect is global, the key is when you call it: before taking a dump.

Glide will clear all requests when the component passed into Glide.with is destroyed, so there should be leak.

Again, you might be only seeing the pool. My Galaxy S4's default behaviour is to leak the entry activity, I couldn't find where I found this, it's somewhere in the comments on GitHub. And while the application is alive, Glide.glide.bitmapPool is not garbage collected. The Android system will kill your app if it needs memory though, so it's not a big deal.

Sorry for the late reply, I've been busy lately. After playing with my app for a while I notice that even though I'm not doing anything (the app is idle), memory allocation keeps increasing at a rate of roughly 10kb every few seconds. Is this behavior caused by glide?

AS/IDEA > Android Monitor > Memory > Start Allocation Tracking

The tracker doesn't show anything. It's empty

Try a shorter period: http://b.android.com/204503

Did you managed to get some data out of it?
Glide should only do stuff while idle when there are GIFs playing.

Closing this as there's no response. ViewPagers keep multiple pages in memory, if each is a list then you have a lot of images showing and Glide can't do anything about that. Don't load the images, or clear the images when they're not actually visible.

@TWiStErRob ,I have the similar scene and code like this. I have some problems need to solve.

  1. when I load image in ViewPager ,it is quick to increase memory to 120M or heigher, and cause OOM.
  2. the activity which hold the ViewPagerdestroyed,but the memory did not release. from the MAT shows that GlideDrawableImageViewTarget and Bitmap may cause the memory leak.this is MAT message:
    20160711142248
    how to analyse and solve this? Thank you in advance.

@moooy Please open a new issue with more details. From what you've given it's either that you have a list in the ViewPager, or you're using the application context to load, but could be anything else too...

This helped me remove the leaks when using an adapter:

            Glide.with(context)
                    .load(imageURL))
                    .asBitmap()
                    .dontAnimate()
                    .diskCacheStrategy(DiskCacheStrategy.RESULT)
                    .into(new SimpleTarget<Bitmap>() {
                        @Override
                        public void onResourceReady(Bitmap bitmap, GlideAnimation anim) {

                            if (bitmap != null) {

                                imageView.setImageDrawable(bitmap);

                            }
                        }
                    });

hi in view model for frament
The activity context is kept even when you press the back button and exit the program.
And go back to the program, you do not need to re-value the context because the context is static
`@Override
public void onBindViewHolder(@NonNull final viewholder viewholder, final int i) {
final Model_Post model_post = list.get(i);
Log.e(TAG, "onBindViewHolder: "+model_post.getImageurl());

        Glide.with(MainActivity.activity)
                .load(Uri.parse(model_post.getImageurl()))
                .diskCacheStrategy(DiskCacheStrategy.ALL)
                .into(viewholder.itemsPostBinding.imgvItempost);

    }`

You must do this in main activity :
`public static AppCompatActivity activity;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    activity = MainActivity.this;
}`
Was this page helpful?
0 / 5 - 0 ratings

Related issues

ghost picture ghost  路  3Comments

sant527 picture sant527  路  3Comments

FooBarBacon picture FooBarBacon  路  3Comments

r4m1n picture r4m1n  路  3Comments

mttmllns picture mttmllns  路  3Comments