Glide: how to release memory when using glide together with Html.fromHtml,thanks

Created on 17 Mar 2016  Â·  38Comments  Â·  Source: bumptech/glide

question

All 38 comments

I am trying parse the dynamic emotion to GlideImageGetter by Html.fromHtml, the gif show corretly,but it leak memory out. I wanna release the memory at instant when the listview scrolls or the datachaged,which make some gif off the screen. below is the details code,hope guys help me out.thanks.

1.

Spanned spanned = EmotionParser.parseEmotion(instance, guildchatmsg.getBody(), cache.tv_chatcontent_out_mul);

  cache.tv_chatcontent_out_mul.setText(spanned);

2.

public static Spanned parseEmotion(Context context, String content, TextView tv, int type) {
        Pattern pattern = Pattern.compile(FACE_PATTERN, Pattern.CASE_INSENSITIVE);
        if (null == content || content.length() < 1) return null;
        Matcher matcher = pattern.matcher(content);
        StringBuilder sb = new StringBuilder();
        int start = 0;
        int emotionCount = 0;
        while (matcher.find()) {
            int matchStart = matcher.start();
            int matchEnd = matcher.end();
            String group = matcher.group();
            emotionCount++;
            String resultFile = group.substring(0, group.length());
            String resourcePath = "android.resource://" + context.getPackageName() + "/drawable/" + resultFile;
           String result = `"<img src=\"" + resourcePath + "\"/>"`;
            sb.append(content.substring(start, matchStart)).append(result);
            start = matchEnd;
        }
        String result;
        Spanned spanned = null;
        if (start > 0) {
            int len = content.length();
            if (start == len) result = sb.toString();
            else result = sb.append(content.substring(start, len)).toString();
            int width = 0;
            if (type == 1) {
                width = getRecommendScaleOnBig(context);
                if (emotionCount > 8)
                    spanned = Html.fromHtml(result, new GlideImageGetter(context, tv, false, width), null);
                else
                    spanned = Html.fromHtml(result, new GlideImageGetter(context, tv, true, width), null);
            } else if (type == 2) {
                width = getSpecifiedWidthOnSmall(context);
                spanned = Html.fromHtml(result, new GlideImageGetter(context, tv, false, width), null);
            }
        } else {
            result = content;
            spanned = Html.fromHtml(result, null, null);
        }
        return spanned;
    }

3.

public final class GlideImageGetter implements Html.ImageGetter, Drawable.Callback {

    private static final String TAG = "GlideImageGetter";
    private final Context mContext;

    private final TextView mTextView;
    private static TextView lastTextView;

    private final Set<ImageGetterViewTarget> mTargets;
    private final static Set<GlideDrawable> gifs = new HashSet<>();
    private final static Set<ImageGetterViewTarget> vts = new HashSet<>();
    private int mWidth;
    boolean mNeedAnimate = false;

    public static void clearDrawable() {
        for (GlideDrawable gif : gifs) {
            if (null != gif) {
                gif.stop();
                gif = null;
            }
        }
        gifs.clear();
        for (ImageGetterViewTarget vt : vts) {
            Glide.clear(vt);
            vt = null;
        }
        vts.clear();
        lastTextView = null;
    }

    public static GlideImageGetter get(View view) {
        if (null == view) return null;
        return (GlideImageGetter) view.getTag(R.id.img_tag);
    }

    public void clear() {
        GlideImageGetter prev = get(mTextView);
        if (prev == null) return;
        for (ImageGetterViewTarget target : prev.mTargets) {
            Glide.clear(target);
        }
    }

    public GlideImageGetter(Context context, TextView textView, boolean needAnimate, int width) {
        this.mContext = context;
        this.mTextView = textView;
        this.mNeedAnimate = needAnimate;
        lastTextView = textView;
        this.mWidth = width;
        clear();
        mTargets = new HashSet<>();
        mTextView.setTag(R.id.img_tag, this);
    }

    @Override
    public Drawable getDrawable(String url) {
        final UrlDrawable urlDrawable = new UrlDrawable();
        Log.i(TAG, "source:" + url);
        ImageGetterViewTarget vt = new ImageGetterViewTarget(mTextView, urlDrawable);
        vts.add(vt);
        Glide.with(mContext)
                .load(url)
                .diskCacheStrategy(DiskCacheStrategy.NONE)
                .into(vt);
        return urlDrawable;
    }

    @Override
    public void invalidateDrawable(Drawable who) {
        mTextView.invalidate();
    }

    @Override
    public void scheduleDrawable(Drawable who, Runnable what, long when) {

    }

    @Override
    public void unscheduleDrawable(Drawable who, Runnable what) {

    }

    private class ImageGetterViewTarget extends ViewTarget<TextView, GlideDrawable> {

        private final UrlDrawable mDrawable;

        private ImageGetterViewTarget(TextView view, UrlDrawable drawable) {
            super(view);
            mTargets.add(this);
            this.mDrawable = drawable;
        }

        @Override
        public void onResourceReady(GlideDrawable resource, GlideAnimation<? super GlideDrawable> glideAnimation) {
            Rect rect;
            int width;
            if (mWidth > 0) width = mWidth;
            else width = resource.getIntrinsicWidth();
            int originalWidth = resource.getIntrinsicWidth();
            int originalHeight = resource.getIntrinsicHeight();
            rect = new Rect(0, 0, width, width);
            resource.setBounds(rect);
            mDrawable.setBounds(rect);
            mDrawable.setDrawable(resource);
            boolean needAnimate = mNeedAnimate && resource.isAnimated();
            gifs.add(resource);
            if (needAnimate) {
                mDrawable.setCallback(get(getView()));
                resource.setLoopCount(GlideDrawable.LOOP_FOREVER);
                resource.start();
            }
            getView().setText(getView().getText());
            getView().invalidate();
        }

        private Request request;

        @Override
        public Request getRequest() {
            return request;
        }

        @Override
        public void setRequest(Request request) {
            this.request = request;
        }
    }

}

even I make the gif not animate,the memory still leaks, only when the activity destroied,the memory released.

thanks!

Wow, that's a lot of code. So far I don't exactly see why it could be leaked so I'll just list my observations:

  • Target.clear should be enough if the target is well-behaving
  • no need to null local variables in for loops that won't change anything related to GC
  • you have both static and nonstatic store for targets, but I don't see a call to clearDrawables which may leak in that static var
  • you can use override(w,h) which would spare you some of that magic code in onResourceReady.
  • you can use SimpleTarget as base class since you don't need the textview's size if you use override (which contains get/setRequest)
  • try to override onLoadCleared to clean up when Glide asks you
  • the UrlDrawable holds a reference to GlideDrawable, who is referencing the UrlDrawable?

If none of these help, the next step is to go and take a dump to analyze your memory usage. Reproduce what you think is the leaked state of your app, take the dump, search for instances of GlideDrawable and see who's referencing them.

@TWiStErRob Thanks for your reply.
I willl explain some,and try your advice.

clearDrawables will be call at my activity onDestroy() method to release memory,and it helps when I observe the memory inspector under android studio.

I find the memory rise sharply when I scroll the listview which contain some gif emotion,and the glide does not release the gifs which go off the screen,and still take up the cpu. I try to save all the gif reference, stop mannually(call gifdrawable.stop()),and cpu go down immedially. So I guess something must be down when those gif go off the screen if I wanna keep the memory proper usage.

I will make feedback after try your advice.

As you as you leave the comfortable world of lists with simple ImageViews you become responsible for cleaning up. When Glide reuses an ImageView (list binds the same item again) it automatically clears the previous request/target, something like your clear method. You should probably call clear in onViewRecycled and not the next time the item is bound.

I'm starting to feel more and more like the static collection is keeping the refs. When you clear a single image getter the static is still referencing them and keeping them in memory, which makes sense for in onDestroy the memory is cleared.

@TWiStErRob I prefer using imageview more than ImageSpan. However I have no idea how to make multi gif emotion animate to use imageview,could you please point some idea for me ? thanks.

as for the static collection for those reference, you assumption is right. I choose this way in order to manually stop the gif and release the memory use, maybe there could be some better way. If so,please let me know, I am struggling for that.

I'm not saying you should use ImagrViews, just alerting you to be vigilant when writing custom targets.

As I said onItemRecycled is the callback (in RecyclerView.Adapter) where you should clean up (the doc of that says so too).
For the activity on destroy cleanup: if you use the correct context Glide will handle that for you.

These two together is the same as what you're trying with the the static.

snapshot
when overriding the getDrawable, I find some trouble to make it step further, I have no clue how to invoke the getivew and reture the drawable.

It's async, just return mDrawable as before.

"As I said onItemRecycled is the callback (in RecyclerView.Adapter) where you should clean up (the doc of that says so too)."

my mate use ListView, it could be better modify under it,is there a similar callback?

appreciated!
I am refactoring my code as your advice. thanks for your kind heart!

@TWiStErRob

I refactor the getDrawable method, but the gif not showed.here is the detail code:

 @Override
    public Drawable getDrawable(String url) {
        final UrlDrawable urlDrawable = new UrlDrawable();
        Glide.with(mContext)
                .load(url)
                .diskCacheStrategy(DiskCacheStrategy.NONE)
                .override(mWidth, mWidth)
                .into(new ViewTarget<View, GlideDrawable>(mTextView) {
                    @Override
                    public void onResourceReady(GlideDrawable glideDrawable, GlideAnimation<? super GlideDrawable> glideAnimation) {
                        try {
                            if (null == glideDrawable || null == mContext) {
                                Log.i(TAG, "bitmap or mContext is null");
                                return;
                            }
                            urlDrawable.setDrawable(glideDrawable);
//                            boolean needAnimate = mNeedAnimate && glideDrawable.isAnimated();
//                            if (needAnimate) {
                            glideDrawable.setCallback(get(getView()));
                            glideDrawable.setLoopCount(GlideDrawable.LOOP_FOREVER);
                            glideDrawable.start();
//                            }
                            mTextView.setText(mTextView.getText());
                            mTextView.invalidate();
                        } catch (Exception e) {
                            Log.e(TAG, "onResourceReady occuring exception:" + e.getMessage());
                        }
                    }

                    @Override
                    public void onLoadCleared(Drawable placeholder) {
                        if (null != placeholder) {
                            GifDrawable drawable = (GifDrawable) placeholder;
                            drawable.stop();
                        }
                        super.onLoadCleared(placeholder);
                    }
                });
        return urlDrawable;
    }

I use ViewTarget instead of SimpleTarget for SimpleTarget does not provide getView() method.

You don't need getView(), you can use mTextView like you did 3 lines below... they're the same view.

placeholder is not the GIF (the GIF is inside UrlDrawable), you should urlDrawable.setDrawable(placeholder) in onLoadCleared so the reference to the GIF inside is forgotten. Essentially onLoadCleared should be the opposite of onResourceReady.

Override onException to see why it didn't load, if it failed with an exception. One of onResourceReady or onException must be called, is it called? Verify by debugging or logging.

@TWiStErRob
I try recycle the viewtarget onRecyclerListener,but no use,hope you can show me further solution.here is code:

  1. in the listview,i add the listener:
contentList.setRecyclerListener(new AbsListView.RecyclerListener() {
        @Override
        public void onMovedToScrapHeap(View view) {
            Log.i(TAG, "clearing...");
            try {
                Object tag = view.getTag();
                if (null != tag) {
                    ViewCache cache = (ViewCache) tag;
                    MyItemClickTv leftTv = cache.tv_chatcontent_left_mul;
                    clearCacheGif(leftTv);
                    MyItemClickTv rightTv = cache.tv_chatcontent_out_mul;
                    clearCacheGif(rightTv);
                }
            } catch (Exception e) {
                Log.e(TAG, "onMovedToScrapHeap occurs exception:" + e.getMessage());
            }
        }
    }
);
private void clearCacheGif(MyItemClickTv tv) {
    if (null != tv && tv.getVisibility() != View.GONE) {
        SpannableString ss = new SpannableString(tv.getText());
        ImageSpan[] imageSpans = ss.getSpans(0, ss.length(), ImageSpan.class);
        if (null != imageSpans) {
            for(int i =0 ;i <imageSpans.length;i++){
                UrlDrawable drawable = (UrlDrawable) imageSpans[i].getDrawable();
                if (null != drawable) {
                    GlideDrawable gd = drawable.getDrawable();
                    if (null != gd) {
                        Log.i(TAG, "clearing gif...");
                        gd.stop();
                        gd.setCallback(null);
                        gd = null;
                    }
                }
                imageSpans[i] = null;
            }

        }
        Object tvTag = tv.getTag();
        if (null != tvTag) {
            GlideImageGetter glideImageGetter = (GlideImageGetter) tvTag;
            if (null != glideImageGetter) {
                glideImageGetter.clearGif();
            }
            tvTag = null;
        }
    }
}

2.at the GlideImageSetter,

private final Set<ImageGetterViewTarget> vts = new HashSet<>();
private final Set<GlideDrawable> gifs = new HashSet<>();
public void clearGif() {
    Log.i(TAG, "stopping gif..." + gifs.size());
    for (GlideDrawable drawable : gifs) {
        if (null != drawable) {
            drawable.stop();
            drawable.setCallback(null);
        }
    }

    Log.i(TAG, "clearing view target..." + vts.size());
    for (ImageGetterViewTarget vt : vts) {
        Glide.clear(vt);
        if(null != vt.mDrawable){
            vt.mDrawable.setDrawable(null);
        }
        if(null != vt)vt.onDestroy();
    }
    gifs.clear();
    vts.clear();
}

also I override the onLoadCleared method.

@Override
public void onLoadCleared(Drawable placeholder) {
    if (null != placeholder) mDrawable.setDrawable((GlideDrawable) placeholder);
    super.onLoadCleared(placeholder);
}

and I see all the methods are executed by the Log.
I also make sure pass the Activity as context parameter instead of global context.
all static collections are refactored to instance collection.
However the memory are still largely took up( over 100M), any idea?
thanks.

here are the screen shots
1
2

I was curious so I played a little: https://github.com/TWiStErRob/glide-support/commit/c3de297d0daec8d2650a3a640c0404c35b937a30

Clone the project and give it a spin. It should not keep the memory usage high. It keeps growing as you go back and forth, but the GC will take care of it later. I tried to isolate the classes from each other by breaking dependencies and also making them kind of reusable. You should be able to copy those classes and integrate them with your project. They contain minimal code and will clear everything properly, I highly suggest to transfer to using those, as I also managed to get rid of most of the hacks (e.g. setText(getText()) and getSpans()). The only thing you need to change is the diskCacheStrategy in GlideImageGetter, because the sample app uses remote urls in contrast to your local resources.

I noticed something while testing: the memory usage dropped, but only to a certain point and that's because the memory cache and the bitmap pool were filling up. Those are the "good guys" when it comes to memory usage and while they occupy space, if the app is near OOM they'll give up that space. Try to add a button to the UI which calls. Glide.get(context).clearMemory() and then check the memory usage after GC. It should be much better.

@TWiStErRob
I am really appreciated for your help. I will give it a try. As for Glide.get(context).clearMemory(),it does not help, I place it inside the listview @Override onScroll method, it did executed,but no use,the memory still goes up.

A funny thing is when I invoke recycle() method in GifDrawable, Glide.get(context).clearMemory() will cause Null pointer exception for the drawable has been recycled, And Glide try to do something on that without NPE protect. I should have take the stack log,but focus on howto make the solution,ignored the exception and remove the clearMemory invoke.

Glide handles recycling internally, you shouldn't call that, same for onDestroy for example. There are some assumptions Glide makes that must be true, otherwise you get exceptions... (I guess in your case the assumption is that any Target/Drawable in an internal list is live and not recycled, you broke this by calling the method.)

Good,i will avoid the invoke.However if Glide can do some check, it will be much more robust,right? For not all of the user know to avoid invoking the onDestroy or recycle(),maybe it will be a trap for newbie.

@TWiStErRob
after clone the project,I find that DrawableWrapper.java is missing.

After take the reference from your code, the memory release has been improved, it takes about 60M ,maybe it would be much less.

here is the result code that take effects:

public final class GlideImageGetter implements Html.ImageGetter, Drawable.Callback {
    private static final String TAG = "GlideImageGetter";
    private final Context mContext;
    private final TextView mTextView;
    private final Set<SimpleTarget> mTargets;
    private int mWidth;
    boolean mNeedAnimate = false;
    private final GenericRequestBuilder<String, ?, ?, GlideDrawable> glide;
    public static void clearDrawable() {

    }

    public static GlideImageGetter get(View view) {
        if (null == view) return null;
        Object tag = view.getTag(R.id.img_tag);
        if (null != tag) return (GlideImageGetter) tag;
        else return null;
    }

    public void clear() {
        GlideImageGetter prev = get(mTextView);
        if (prev == null) return;
        mTextView.setText(null);
        stopGifs(prev.gifs);
        clearTarget(prev.mTargets);
        mTextView.setTag(null);
    }

    private static void stopGifs(List<GlideDrawable> gifs) {
        if (null == gifs) return;
        for (GlideDrawable glideDrawable : gifs) {
            if (null != glideDrawable) {
                glideDrawable.stop();
                if (null != glideDrawable) glideDrawable.setCallback(null);
            }
        }
        gifs.clear();
    }

    private final List<GlideDrawable> gifs = new ArrayList<>();

    public GlideImageGetter(Context context, TextView textView, boolean needAnimate, int width) {
        this.mContext = context;
        this.mTextView = textView;
        this.mNeedAnimate = needAnimate;
        this.mWidth = width;
        this.glide = createGlideRequest(needAnimate);
        clear();
        mTargets = new HashSet<>();
        mTextView.setTag(R.id.img_tag, this);
    }

    @Override
    public Drawable getDrawable(String url) {
        final UrlDrawable urlDrawable = new UrlDrawable();
        SimpleTarget vt = new SimpleTarget<GlideDrawable>(mWidth, mWidth) {
            @Override
            public void onResourceReady(GlideDrawable resource, GlideAnimation glideAnimation) {
                int width;
                if (mWidth > 0) width = mWidth;
                else width = resource.getIntrinsicWidth();
              **#   //I don't know why SimpleTarget don't take the mWidth parameter and Set for me,
                // if not setBounds,the drawable does not show.**
                Rect rect = new Rect(0, 0, width, width);
                resource.setBounds(rect);
                urlDrawable.setBounds(rect);
                urlDrawable.setDrawable(resource);
                gifs.add(resource);
                boolean needAnimate = mNeedAnimate && resource.isAnimated();
                if (needAnimate) {
                    urlDrawable.setCallback(get(mTextView));
                    resource.setLoopCount(GlideDrawable.LOOP_FOREVER);
                    resource.start();
                }
                mTextView.setText(mTextView.getText());
                mTextView.invalidate();
            }
        };
        mTargets.add(vt);
        glide.load(url).into(vt);
        return urlDrawable;
    }

    @Override
    public void invalidateDrawable(Drawable who) {
        mTextView.invalidate();
    }

    @Override
    public void scheduleDrawable(Drawable who, Runnable what, long when) {
    }

    @Override
    public void unscheduleDrawable(Drawable who, Runnable what) {
    }

    public static void clearGifInsideView(View tv) {
        GlideImageGetter currentGlideImage = get(tv);
        ((TextView) tv).setText(null);
        if (currentGlideImage == null) return;
        Log.i(TAG, "stopping gifs:" + currentGlideImage.gifs.size());
        stopGifs(currentGlideImage.gifs);
        clearTarget(currentGlideImage.mTargets);
        tv.setTag(null);
    }

    private static void clearTarget(Set<SimpleTarget> mTargets) {
        if (null == mTargets) return;
        Log.i(TAG, "clearing targets:" + mTargets.size());
        for (SimpleTarget target : mTargets) {
            Glide.clear(target);
        }
    }

    private GenericRequestBuilder<String, ?, ?, GlideDrawable> createGlideRequest(boolean animated) {
        GenericRequestBuilder<String, ?, ?, GlideDrawable> load;
        if (animated) {
            load = Glide.with(mContext).fromString()
                    //".asDrawable()" default loading handles animated GIFs and still images as well
                    .diskCacheStrategy(DiskCacheStrategy.SOURCE); // animated GIFs need source cache

        } else {
            load = Glide.with(mContext).fromString()// force still images
                    .asBitmap().transcode(new BitmapToGlideDrawableTranscoder(mContext), GlideDrawable.class)
                            // cache resized images (RESULT), and re-use SOURCE cached GIFs if any
                    .diskCacheStrategy(DiskCacheStrategy.ALL);
        }
        return load;
    }
}

in the listview:

contentList.setRecyclerListener(new AbsListView.RecyclerListener() {
    @Override
    public void onMovedToScrapHeap(View view) {
        Log.i(TAG, "clearing...");
        clearGifViewCache(view);
        ImageManager.clearMemory(instance);
    }
});
private void clearGifViewCache(View view) {
    try {
        if (null != view) {
            Object tag = view.getTag();
            if (null != tag) {
                ViewCache cache = (ViewCache) tag;

                TextView leftTv = cache.tv_chatcontent_left_mul;
                if (null != leftTv) clearCacheGif(leftTv);

                TextView rightTv = cache.tv_chatcontent_out_mul;
                if (null != rightTv) clearCacheGif(rightTv);
            }
        }
    } catch (Exception e) {
        Log.e(TAG, "clearGifViewCache occuring exception:" + e.getMessage());
    }
}
private void clearCacheGif(View view) {
    if (null != view) {
        GlideImageGetter.clearGifInsideView(view);
    }
}

and the activity onDestroy method,invoke clearListView();

private void clearListView() {
    if (null != myAdapter) {
        for (int i = 0; i < myAdapter.getCount(); i++) {
            View view = contentList.getChildAt(i);
            clearGifViewCache(view);
        }
    }
    ImageManager.clearMemory(instance);
}

however I am expecting release much more memory for better user-experience,thanks

DrawableWrapper is in appcompat-v7 (as you can see in the import), I'm using the latest 23.1 I think, I think it was added in 23.x because the class it mirrors was added in API 23. On a chance that it doesn't work even after adding/updating that dependency (e.g. because it has a @hide on it), just find the source code and copy it to your project as well.

You're still clearing/stopping/destroying much more things than you need to. The version I gave you clears everything in the right places, there's no need to clear more. When I tried it destroyed everything when I left the activity properly, it went back to baseline memory usage (after clearMemory and GC). Clearing memory shouldn't be part of your production code, the whole point of memory cache and bitmap pool is to optimise memory usage, you have take into account the whole runtime of the application. Yes, at one point it may use up 60MB (assuming you're not just leaking the activity), but that means that if you open the same conversation the emoticons and other images will show up immediately. When a user leaves an activity and then opens the same one again, they expect it to be "just there", because "it was there a moment ago", intolerant to spinners and loading times. It also saves battery. All this is bettering user experience.

If you don't use the code I wrote for you don't expect it to work... for example the bounds setting is nicely isolated in the target in a dynamic way. SimpleTarget doesn't set it because it's the ImageView's responsibility which we don't have here, also the TextView doesn't know how big you want your Drawable to be, so it needs to be set in the getDrawable callback (target constrcutor in my code). If you pass in the RequestManager it'll take care of destroying everything on leaving the activity. Every single field and line has a purpose in the code sample.

To see what's holding the memory just change to the memory graph and press the GC, then Dump button and click "Check for leaked activities" in the upper right after you exited the activity. This is how I found #1070 for example when writing the sample code.

Regarding calling the lifecycle methods: even if we put a check we would just turn the NPE into an IllegalStateException or something similar, that is the way of signalling you're doing something wrong. If you search for the exception message you should find several places where we say "don't manually recycle stuff". What you're doing is advanced Glide/Android programming, newbies hopefully wouldn't run into this.

@TWiStErRob
thanks for your detail explanation. release memory could be a burden for developer, it is welcome for Glide auto handle the memory ,that is really nice.

I will drop my way and try your sample code. Thank you.

@TWiStErRob

I integrated your sample code to my project,replace all of previous code,however,the issue still exists,take too much memory,black square ,and sometimes white splash appear and disappear quickly.

and the curious thing is when I quit the activity, the memory does not release. In other words,before entering the activity,it take about 20M memory, and then start the activity,it take about 60M memory,but after exit the activity,it still takes 60M memory. you know that's not acceptable.

When I test in other device(samsung A5 with android 4.4.4),it did release the memory when quit,only take about 30M after quit,but black square , white splash still appear now and then. I feel puzzled now...

Did you clear the memory cache and pool before checking? (as I said that's normal to take up memory, and it's good kind of memory usage)
Did you check for leaked activity (it's 2 clicks in AS)?

I did invoke the clearMemory(), however even remove the invoke, it takes the same result.

I have check the leaked activity.

Did you GC a few times after clearMemory()?

no.

contentList.setRecyclerListener(new AbsListView.RecyclerListener() {
    @Override
    public void onMovedToScrapHeap(View view) {
        Log.i(TAG, "clearing...");
        clearGifViewCache(view);
        ImageManager.clearMemory(instance);
    }
});
public static void clearMemory(Context context) {
    if (null == context) return;
    getGlide(context).clearMemory();
}

above is the place invoke clearMemory

Hmm, I'm not sure recycler listener is called for each item when the view is being destroyed. But Glide should clear them via the onDestroy lifecycle. Clearing the memory only has effect after after that. Try calling it in Activity.onDestroy() and put a System.gc() there too.

I have try your advice,and no use.

Then you should take a deeper look at the heap dump to see what is taking up the memory and what is keeping in from being GC'd. For pointers see https://github.com/bumptech/glide/issues/679#issuecomment-148194654

Did you manage to find out what's holding them?

@TWiStErRob is it possible to use this approach if A. There are more than one image in the HTML and B. You don't know the size of the image beforehand?

A: obviously, as you can see there are multiple animated smileys in most of the messages (TestFragment). Check out the videos in #1068.
B: only of you keep re-setting the HTML text on every image ready. ImageGetter/TextView can't handle changing sizes, and requires sync image loading on the UI thread, that's why we need WrapperTarget.wrapper, so it just re-draws when the image is ready. If you re-set the HTML the ImageGetter will find the image sync in memory cache.

@TWiStErRob Sorry for the obvious question. I just gave that a shot and it worked like a champ. Thanks!

@ourgit if you find out what's your leak and it might be an issue with Glide, please reopen.

@TWiStErRob on the 23rd when you said to re-set the HTML text on every image ready did you mean to call something like targetTextView.setText(targetTextView.getText()) right after this line https://github.com/TWiStErRob/glide-support/blob/c3de297d0daec8d2650a3a640c0404c35b937a30/src/glide3/java/com/bumptech/glide/supportapp/github/_1062_html_gif_spans/WrapperTarget.java#L54 I cant for the life of me get the images to resize. I thought it was working before but it wasn't. Thanks

Yes, something like that. Make sure the change goes through, check TextView's code, because you may need to temp = getText; setText(null); setText(temp). And pay attention to how many targets you have you may need GlideImageGetter.clear() (not sure, it was long ago). You can also try just invalidating the list item so the list rebinds, but that may do more than needed.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

technoir42 picture technoir42  Â·  3Comments

r4m1n picture r4m1n  Â·  3Comments

sergeyfitis picture sergeyfitis  Â·  3Comments

PatrickMA picture PatrickMA  Â·  3Comments

ersen-osman picture ersen-osman  Â·  3Comments