View Pager Memory Leak with Bitmaps and Volley - android

I'm using View Pager to show images which are downloaded from the network in my application. The number of images could be from 5 to 20. I'm using Volley library to do the network operations. The app wasn't taking much memory before but now after adding the view pager, the app takes a lot of memory and every time i open this activity, the memory used in heap increase (checked from the log messages). I also used Eclipse Memory analyzer to check where the leak was and it is definitely the bitmaps and the multiple instances of this activity. There is definitely a leak, as this activity isn't getting GC'ed, some references are keeping this from getting garbage collected. I've added my implementation of the view pager here.
public class ViewPagerAdapter extends PagerAdapter {
Context context;
public ViewPagerAdapter(Context context) {
this.context = context;
}
#Override
public int getCount() {
return photoReferences.size();
}
#Override
public boolean isViewFromObject(View view, Object object) {
return view == ((RelativeLayout) object);
}
#Override
public Object instantiateItem(ViewGroup container, int position) {
final ImageView im;
final ProgressBar pb;
View itemView = inflater.inflate(R.layout.place_photos_item, container, false);
im = (ImageView) itemView.findViewById(R.id.placeImage);
attributes = (TextView) itemView.findViewById(R.id.placeAttributes);
pb = (ProgressBar) itemView.findViewById(R.id.progressBarPhoto);
imageLoader.get(url, new ImageListener() {
public void onErrorResponse(VolleyError arg0) {
im.setImageResource(R.drawable.onErrorImage);
}
public void onResponse(ImageContainer response, boolean arg1) {
if (response.getBitmap() != null) {
im.startAnimation(AnimationUtils.loadAnimation(context, android.R.anim.fade_in));
im.setImageBitmap(response.getBitmap());
pb.setVisibility(View.GONE);
}
}
});
((ViewPager) container).addView(itemView);
return itemView;
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
((ViewPager) container).removeView((RelativeLayout) object);
}
}
Also, I'm using the Bitmap Cache of size 3 times the number of screenBytes(screenWidth * screenHeight * 4). I'm testing on Nexus 4 running 4.3 and I never run into a OOM exception cause the heap size is huge on this device but the app can take more than 100 mb of memory(it will crash on most devices) if I open the activity again and again, and before it used to take around 16-20 mbs of memory no matter what. Here's the cache code.
public class BitmapCache extends LruCache<Object, Object> implements ImageCache {
public BitmapCache(int maxSize) {
super(maxSize);
}
#Override
public Bitmap getBitmap(String url) {
return (Bitmap) get(url);
}
#Override
public void putBitmap(String url, Bitmap bitmap) {
put(url, bitmap);
}
}
Could anyone please suggest me what should I do to catch the leak? Is there anything wrong in the View Pager or my Volley usage? I'm not happy with the transition of the Pager as well, lags a bit, is that related?
Update: Here's the screenshot of MAT, possible leak. This is on every activity that uses Volley library. I've been reading a lot but I couldn't solve the problem. Is volley causing leak or am I doing something terribly wrong?

You can find your leak by using MAT. First you run your app and leak a few activity instances. Then you grab a snapshot of the heap and look for those leaked Activity objects... you can use 'Object Query Language' (OQL) to find them by type (e.g. "SELECT * FROM com.foo.FooActivity").
Once you've found a leaked object, right-click on it and ask MAT to trace all its incoming references back to their GC roots. The leaked reference will be one of those.
For a better introduction to the technique you could try this article:
http://android-developers.blogspot.co.uk/2011/03/memory-analysis-for-android.html

I guess you are using using Viewpager and Imageviews
About image views you are using powerful image downloading and caching library like latest Volley Imageloading(really helpful for large size images) to improve the image loading capabilities in a efficient way.
About Viewpager you have to use efficient adapter FragmentStatePagerAdapter:
This version of the pager is more useful when there are a large number of pages, working more like a list view. When pages are not visible to the user, their entire fragment may be destroyed, only keeping the saved state of that fragment. This allows the pager to hold on to much less memory associated with each visited page as compared to FragmentPagerAdapter at the cost of potentially more overhead when switching between pages.
please think before you are using FragmentPagerAdapter becouse it stores the whole fragment in memory, and could increase a memory overhead if a large amount of fragments are used in ViewPager. In contrary its sibling, FragmentStatePagerAdapter only stores the savedInstanceState of fragments, and destroys all the fragments when they lose focus. Therefore FragmentStatePagerAdapter should be used when we have to use dynamic fragments, like fragments with widgets, as their data could be stored in the savedInstanceState. Also it wont affect the performance even if there are large number of fragments. In contrary its sibling FragmentPagerAdapter should be used when we need to store the whole fragment in memory. When I say the whole fragment is kept in memory it means, its instances wont be destroyed and would create a memory overhead. Therefore it is advised to use FragmentPagerAdapter only when there are low number of fragments for ViewPager. It would be even better if the fragments are static, since they would not be having large amount of objects whose instances would be stored. Hope this clears out the difference between Android FragmentPagerAdapter and FragmentStatePagerAdapter.
Try to learn Google android gallary app example, use image view loading animations to make a great user experience.
I hope this will solves your grow heap problems.
Credits:FragmentPagerAdapter vs FragmentStatePagerAdapter

You forget to recycle your downloaded Bitmaps as they become unneeded.
Basically, every Bitmap you handle manually, you have to recyle().
That being said, your destroyItem() method should look something like this:
public void destroyItem(ViewGroup container, int position, Object object) {
RelativeLayout rl = (RelativeLayout) object;
ImageView im = rl.findViewById(R.id.image_view);
bitmapDrawable = (BitmapDrawable) im.getDrawable();
if (bitmapDrawable != null && bitmapDrawable.getBitmap() != null) {
bitmap = bitmapDrawable.getBitmap();
bitmap.recycle();
}
container.removeView(rl);
}

You should check out the new version of Volley , old version did cause the leak problem.
In old version ,Volley has 4 thread do request , And each of them will keep a request , and request keep strong reference of listener , and your response listener do something with the ImageView , ImageView keep the Activity context. so all of your View is leaked.
In MAT use select * from instanceof android.app.Activity you will see your Activity is leaked.
New Version of Volley has fixed this problem . please check out here
And use this will help your find out your leaked Activity , leakcanary

Related

Release all memory from Picasso's cache

I have a FragmentStatePagerAdapter with a bunch of Fragments that are loaded and destroyed as the user swipes around. The Fragments each contain some text, and some very large images. I'm using Picasso to load and cache the images, and each Fragment has its own single instance of Picasso, which is shutdown in onDestroy().
When onDestroy() is called for each Fragment, I'd also like to totally clear the memory cache associated with the Picasso instance. I've tried creating a PicassoTools class like this answer says, and while that empties the cache (according to the debugger,) it doesn't seem to release the memory associated with the cache, according to the memory monitor. Here's my code for onDestroy():
#Override
public void onDestroy() {
PicassoTools.clearCache(picasso);
//release cache memory here somehow
picasso.shutdown();
super.onDestroy();
}
After I clear the cache, how can I totally release all the memory associated with it?
UPDATE: Here's my PicassoTools.clearCache() method, which is called onDestroy(). I added Bitmap recycle()ing, but that didn't seem to make a difference.
public static void clearCache(Picasso p) {
ArrayList<String> keys = new ArrayList<>();
keys.addAll(((LoopableLruCache) p.cache).keySet()); //LoopableLruCache is an extension of Picasso's LruCache, just with a keySet() method for looping through it easier
for (String key : keys) {
Bitmap bmp = p.cache.get(key);
if (!bmp.isRecycled()) {
bmp.recycle();
}
bmp = null;
p.invalidate(key);
}
p.cache.clear();
}

Creating bitmap from xml layouts, off the ui thread

I'm trying to create multiple bitmaps, one for each map marker.
Since it happens on the ui thread, the ui freezes for a moment..
Is there a way to create a bitmap using a layout xml on a background/worker thread?
I know that It's not recommended, but I'm not sure how to tackle this issue.
If there's a way to create a designed bitmap not using my current method,
i'll be glad to hear..
Thanks
#Override
public View onCreateView(LayoutInflater inflater,
#Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
...
mMarkerContainer = (ViewGroup) LayoutInflater.from(
getActivity()).inflate(R.layout.map_text_marker, null);
mMarkerNameTv = (TextView) mMarkerContainer
.findViewById(R.id.map_marker_name);
...
return super.onCreateView(inflater, container, savedInstanceState);
}
public Bitmap createMarkerBitmap(int markerRes, String markerName) {
mMarkerNameTv.setText(markerName);
mMarkerContainer.setBackground(mBitmapDrawables.get(markerRes));
int measureSpec = View.MeasureSpec.makeMeasureSpec(0,
View.MeasureSpec.UNSPECIFIED);
mMarkerContainer.measure(measureSpec, measureSpec);
int measuredWidth = mMarkerContainer.getMeasuredWidth();
int measuredHeight = mMarkerContainer.getMeasuredHeight();
mMarkerContainer.layout(0, 0, measuredWidth, measuredHeight);
Bitmap resultBitmap = Bitmap.createBitmap(measuredWidth,
measuredHeight, Bitmap.Config.ARGB_8888);
resultBitmap.eraseColor(Color.TRANSPARENT);
Canvas canvas = new Canvas(resultBitmap);
mMarkerContainer.draw(canvas);
return resultBitmap;
}
Is there a way to create a bitmap using a layout xml on a background/worker thread?
AFAIK, working with widgets on a background thread is not a problem, so long as they are not connected to any window (e.g., they are not part of an activity or dialog). Inflating a layout might be a problem -- I seem to recall running into that with instrumentation tests, which do not run on the main application thread. But you're welcome to try putting your inflate() call and all of the createMarkerBitmap() logic into a background thread.
However:
Creating and populating widgets normally does not take much time. You may be better served using Traceview to determine why yours is taking so long.
If the issue isn't that an individual bitmap is slow, but that you are creating two tons of bitmaps, that will be a problem regardless of how you do it. Just because the work is done on a background thread does not make it "free" from a CPU standpoint, plus there are memory pressures to consider.
If there's a way to create a designed bitmap not using my current method, i'll be glad to hear.
You could draw directly to the Canvas using the methods available on Canvas.
Generally, you can use an extension of AsyncTask, like this:
private class MyAsyncTask extends AsyncTask<Integer, Void, Bitmap> {
protected Long doInBackground(Integer... bitmapID) {
return loadYourBitmap(bitmapID[0]); //<-happens in background
}
protected void onPostExecute(Bitmap yourBitmap) {
setSomethingTo(yourBitmap); //<- happens in foreground when doInBackground is done
}
}
One possible solution to this problem is to cache the bitmaps. When you want to display multiple markers as bitmaps. Simple [LruCache][1] - based in-memory cache would work just fine. The only thing that is needed to be taken care of is how much memory you are using for caching. Have a look at this official Google docs to know more about caching in bitmaps.
You can cache BitmapDescriptor for every unique bitmap you have. This way you can get some extra performance by avoiding making calls to BitmapDescriptorFactory every time you need to create a marker.
Here is the sample code:
LruCache<String, BitmapDescriptor> cache;
private void initCache()
{
//Use 1/8 of available memory
cache = new LruCache<String, BitmapDescriptor>((int)(Runtime.getRuntime().maxMemory() / 1024 / 8));
}
private void addMarker(LatLng position, String assetPath)
{
MarkerOptions opts = new MarkerOptions();
opts.icon(getBitmapDescriptor(assetPath));
opts.position(position);
mMap.addMarker(opts);
}
private BitmapDescriptor getBitmapDescriptor(String path) {
BitmapDescriptor result = cache.get(path);
if (result == null) {
result = BitmapDescriptorFactory.fromAsset(path);
cache.put(path, result);
}
return result;
}

Use of Target in Picasso on Adapter

Im having big troubles using a Target inside an adapter. Im confused about the documentation on the code
Objects implementing this class must have a working implementation of
{#link #equals(Object)} and {#link #hashCode()} for proper storage internally. Instances of this
interface will also be compared to determine if view recycling is occurring. It is recommended
that you add this interface directly on to a custom view type when using in an adapter to ensure
correct recycling behavior.
Im trying to use the Target in this way:
class CustomTarget implements Target {
private ImageView imageView;
public CustomTarget(ImageView imageView) {
this.imageView = imageView;
}
#Override
public void onBitmapLoaded(final Bitmap bitmap, Picasso.LoadedFrom from) {
imageView.setImageDrawable(new RoundedAvatarDrawable(bitmap));
}
#Override
public void onBitmapFailed(Drawable errorDrawable) {
imageView.setImageDrawable(errorDrawable);
}
#Override
public void onPrepareLoad(Drawable placeHolderDrawable) {
imageView.setImageDrawable(placeHolderDrawable);
}
#Override
public boolean equals(Object o) {
return imageView.equals(o);
}
#Override
public int hashCode() {
return imageView.hashCode();
}
}
#Override
public View getView(int position, View v, ViewGroup parent) {
....
RoundedAvatarDrawable r = new RoundedAvatarDrawable(BitmapFactory.decodeResource(mContext.getResources(), R.drawable.ic_avatar_seahorse));
ImageCacheController.with(mContext).getPicasso().load(member.getPicture_url()).resize(100, 100).centerCrop().placeholder(r).error(r).into(new CustomTarget(viewHolder.ivAvatar));
....
}
It's doesn't work and the images change between each others randomly
You don't show your whole getView function, so without knowing how you use the viewHandler, here's my take on what's going on:
Your problem is that you're creating a new CustomTarget every time getView gets called. You are going against the point of having a Target object. Let me elaborate.
When a new download request is made, previous requests to the same target get stopped or don't result in a call to the Target's callbacks. (so if the Target gets reused for a different row in a list it doesn't get both rows' images).
You are using a new object for each request, effectively hinting Picasso that each request is for a different row so to speak. The doc says "Instances of this interface will also be compared to determine if view recycling is occurring", so since each request has a newly created CustomTarget object, no two requests will have the same object and a row recycle won't be detected.
You're also using viewHolder. In this case I think the viewHolder should be extending the Target interface (if you only have 1 image per row). This way everytime you request a download you can use the same object and not create a new one.
You're also delegating the implementation of your CustomTarget to the ImageView's implementation. Make sure that ImageView's equals and hashCode functions fullfill the requirements Picasso asks for.
Some info on how to implement equals and hashCode: What issues should be considered when overriding equals and hashCode in Java?
It seems your equals method is broken. You are comparing an imageview to a custom target. This might fix it:
public boolean equals(Object o) {
if(o instanceof CustomTarget) {
return ((CustomTarget) o).imageView.equals(this.imageView);
}
return super.equals(o);
}

Will Looping a Runnable Cause a Memory Leak

Hi so I have a fairly large memory leak in my app and I think it's being caused by my Runnables.
Here is an example of the skeleton of the Runnables i use:
private Runnable randomAlienFire = new Runnable() {
public void run() {
/*A Bunch
of computations
*/
mainHandler.removeCallbacks(randomAlienFire);
mainHandler.postDelayed(randomAlienFire, number );
}
When I switch activities I call mainHandler.removeCallbacksAndMessages(null); and thread.randomAlienFire = null; yet I am still leaking the entire Activity. So my question is, is there something in this basic skeleton that is causing a memory leak? Could it be the fact that the handler is calling to itself?
Yes, your implementation will definitely cause a memory leak (I just ran into this myself).
The problem is that you have created a circular reference. You have declared your runnable as a non-static inner class, which means that it will automatically maintain a reference to the activity. The runnable itself is a member variable of your activity, which closes the circle. The garbage collector will never be able to free these objects since there will always be a living reference.
Using a static inner class with a weak reference to the activity is the safest way to fix the problem. You can see a great code example here. If mainHandler is another non-static inner class, it will create a second circular reference for the same reasons so you will have to do the same thing there.
Setting mainHandler.removeCallbacksAndMessages(null); and thread.randomAlienFire = null; could also work, but you have to be very careful where you put that code. Perhaps the code is taking a different path than you expect in some cases and missing those calls? This blog post describes someone else's very similar experience with that approach.
In my case, I was using a runnable to sequence animations on ImageViews. to get rid of the memory leaks, I created a static runnable class to avoid the circular reference. That alone was not enough for me, I also found that the drawable was still retaining a reference to my fragment. calling myImageView.removeCallbacksAndMessages(arrowAnimationRunnable); in onDestroy() in my fragment finally solved the leak. here was my solution:
public class MyFragment extends SherlockFragment {
public static class SafeRunnable implements Runnable {
private final WeakReference<MyFragment> parentReference;
public SafeRunnable(MyFragment parent) {
parentReference = new WeakReference<MyFragment>(parent);
}
#Override
public void run() {
if (parentReference != null) {
final MyFragment parent = parentReference.get();
if (parent != null) {
runWithParent(parent);
}
}
}
public void runWithParent(MyFragment parent) {
}
}
// This anonymous instance of the new runnable class does not retain a
reference to the fragment
private Runnable arrowAnimationRunnable = new SafeRunnable(this) {
#Override
public void runWithParent(MyFragment parent) {
// ... animation code
// repeat the animation in 1 second
parent.myImageView.postDelayed(this, 1000);
}
};
private ImageView myImageView;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.my_layout, container, false);
// find the image view and kick off the animation after 1 second
myImageView = (ImageView) view.findViewById(R.id.iv_arrow);
myImageView.postDelayed(arrowAnimationRunnable, 1000);
return view;
}
#Override
public void onDestroyView() {
super.onDestroyView();
// It's necessary to remove the callbacks here, otherwise a message will
// be sitting in the queue and will outlive the fragment. Because a
// reference in that message will still be pointing to the fragment, the
// fragment (and everything else) will not be garbage collected
myImageView.removeCallbacks(arrowAnimationRunnable);
}
}
By mainHandler.postDelayed(randomAlienFire, number );
you are queueing a task which may have a memory reference. But the activity may become destroyed before the actual works done. That is causing a memory leak for you.
To get rid of this leak, you must call mainHandler.removeCallbacks(randomAlienFire); in an appropriate place before destroying activity. For example if your runnable runs from onStart(), you must call mainHandler.removeCallbacks(randomAlienFire); in onStop();

Avoid memory leaks on Android

I just read a blogpost by Romain Guy on how to avoid memory leaks in Android.
In the article he gives this example:
private static Drawable sBackground;
#Override
protected void onCreate(Bundle state) {
super.onCreate(state);
TextView label = new TextView(this);
label.setText("Leaks are bad");
if (sBackground == null) {
sBackground = getDrawable(R.drawable.large_bitmap);
}
label.setBackgroundDrawable(sBackground);
setContentView(label);
}
Romain said:
This example is one of the simplest cases of leaking the Context.
My question is, how do you modify it correctly?
Just like this?
TextView label = new TextView(Context.getApplicationContext());
I tested both ways and the results are the same. I can't locate the difference. And I think that this is more correct than the Application context. Because this is a reference to Activity, that is to say, the TextView belongs to that Activity.
Could someone give me an explanation for this?
The actual problem with that code isn't the context passed to create the drawable, but private static Drawable sBackground;
The static Drawable is created with the Activity as the context, so in THAT case, there's a static reference to a Drawable that references the Activity, and that's why there's a leak. As long as that reference exists, the Activity will be kept in memory, leaking all of its views.
So it's the Drawable which should be created using the application context, not the TextView. Creating the TextView with "this" is perfectly fine.
edit : Actually, that might not make a big difference, the problem is that once the drawable is binded to a view, there's a reference to the view, which references the activity. So you need to "unbind" the drawable when you exit the activity.
I'm not sure if Romain had updated his blog entry since you read it, but he's pretty clear on how to avoid the leaks, even pointing you to an example in the Android OS. Note that I fixed the broken link in Romain's blog entry via archive.org.
This example is one of the simplest cases of leaking the Context and
you can see how we worked around it in the Home screen's source
code (look for the unbindDrawables() method) by setting the stored
drawables' callbacks to null when the activity is destroyed.
Interestingly enough, there are cases where you can create a chain of
leaked contexts, and they are bad. They make you run out of memory
rather quickly.
There are two easy ways to avoid context-related memory leaks. The
most obvious one is to avoid escaping the context outside of its own
scope. The example above showed the case of a static reference but
inner classes and their implicit reference to the outer class can be
equally dangerous. The second solution is to use the Application
context. This context will live as long as your application is alive
and does not depend on the activities life cycle. If you plan on
keeping long-lived objects that need a context, remember the
application object. You can obtain it easily by calling
Context.getApplicationContext() or Activity.getApplication().
In summary, to avoid context-related memory leaks, remember the
following:
Do not keep long-lived references to a context-activity (a reference to an activity should have the same life cycle as the
activity itself)
Try using the context-application instead of a context-activity
Avoid non-static inner classes in an activity if you don't control their life cycle, use a static inner class and make a weak reference to the activity inside. The solution to this issue is to use a static inner class with a WeakReference to the outer class, as done in ViewRoot and its W inner class for instance
A garbage collector is not an insurance against memory leaks
Memory leaks at that code mostly happen when you rotate your screen (that is, changing the orientation state) so your activity was destroyed and created again for the new orientation. There's a lot of explanation about memory leaks.
You can take a look at one of the Google I/O 2011 video about Memory Management here. In the video, you can also use the memory management tools like Memory Analyzer available to download here.
I don't know if you are having any trouble with this in your app, but I have created a drop in solution that fixes all the android memory leak issues with standard android classes: http://code.google.com/p/android/issues/detail?id=8488#c51
public abstract class BetterActivity extends Activity
{
#Override
protected void onResume()
{
System.gc();
super.onResume();
}
#Override
protected void onPause()
{
super.onPause();
System.gc();
}
#Override
public void setContentView(int layoutResID)
{
ViewGroup mainView = (ViewGroup)
LayoutInflater.from(this).inflate(layoutResID, null);
setContentView(mainView);
}
#Override
public void setContentView(View view)
{
super.setContentView(view);
m_contentView = (ViewGroup)view;
}
#Override
public void setContentView(View view, LayoutParams params)
{
super.setContentView(view, params);
m_contentView = (ViewGroup)view;
}
#Override
protected void onDestroy()
{
super.onDestroy();
// Fixes android memory issue 8488 :
// http://code.google.com/p/android/issues/detail?id=8488
nullViewDrawablesRecursive(m_contentView);
m_contentView = null;
System.gc();
}
private void nullViewDrawablesRecursive(View view)
{
if(view != null)
{
try
{
ViewGroup viewGroup = (ViewGroup)view;
int childCount = viewGroup.getChildCount();
for(int index = 0; index < childCount; index++)
{
View child = viewGroup.getChildAt(index);
nullViewDrawablesRecursive(child);
}
}
catch(Exception e)
{
}
nullViewDrawable(view);
}
}
private void nullViewDrawable(View view)
{
try
{
view.setBackgroundDrawable(null);
}
catch(Exception e)
{
}
try
{
ImageView imageView = (ImageView)view;
imageView.setImageDrawable(null);
imageView.setBackgroundDrawable(null);
}
catch(Exception e)
{
}
}
// The top level content view.
private ViewGroup m_contentView = null;
}

Categories

Resources