Android : trying to generate a memory leak - android

I am using the following post to generate a memory leak in a test application
http://android-developers.blogspot.com/2009/01/avoiding-memory-leaks.html
I am using android studio memory profiler and allocation tracker to track the object allocation. I am able to see activity instances created while rotating the screen multiple times. But when I click on "Initiate GC" on android studio all these instances are garbage collected though they hold a static reference to the drawable object. I was expecting these activity objects to be retained and will cause an "Out of memory" exception. Below is the code I have used :
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
TextView textView = new TextView(this);
textView.setText("Hello View");
if(sBackground == null) {
sBackground = ContextCompat.getDrawable(this,R.drawable.back1mb);
}
mTextView = (TextView) findViewById(R.id.txtView);
textView.setBackgroundDrawable(sBackground);
setContentView(textView);
}

The problem is, the blog post you've referenced is very old, and the Android SDK has changed a lot since it was written. In the early days, as the tutorial says:
When a Drawable is attached to a view, the view is set as a callback on the drawable.
However, this isn't true for more recent versions of the Android SDK.
The code for the early version of Drawable.setCallback was (see link):
public final void setCallback(Callback cb) {
mCallback = cb;
}
But it now uses a WeakReference (see link), so won't leak any more:
public final void setCallback(Callback cb) {
mCallback = new WeakReference<Callback>(cb);
}
You could build against an old version of Android to see the leak behaviour in the blog, or use a different means of creating a leak.

Related

Xamarin.Forms leak on Android doing simple push/pop of page?

I'm investigating a memory leak (or leaks) which seem to be core to our Forms app.
We're using FreshMvvm, which previously had a pretty bad leak due to Pages/PageModels not being garbage collected. Michael Ridland (FreshMvvm) author) fixed that, which is great.
However, we're still seeing the figure returned by GC.GetTotalMemory() steadily increase. It does decrease, but the general trend is upwards.
I've stripped things right back so that I've just got a plain Page which pushes another page and we can then pop back to the first.
Doing this, I'm seeing GC.GetTotalMemory() generally increase by 2K - 4K every cycle. This is after forcibly calling GC.Collect();
We're running Xamarin.Forms 2.3.4.270 - I know this is quite old, but we really don't want to upgrade as we don't want to introduce 'new Forms' bugs! However, I have tried experimentally upgrading to 2.5.0.121934, and I see the same behaviour (as well as aspects of the app stop working with this version).
Short of using Profiler, are there any investigative techniques I can use to find the culprit?
My next step will be to strip more and more out!
I've encountered a similar issue in the past with Xamarin.Forms memory leaks and to help figure what views/pages were the culprit this class was created:
public static class Refs
{
private static readonly List<WeakReference> _refs = new List<WeakReference>();
public static readonly BindableProperty IsWatchedProperty = BindableProperty.CreateAttached("IsWatched", typeof(bool), typeof(Refs), false, propertyChanged: OnIsWatchedChanged);
public static bool GetIsWatched(BindableObject obj)
{
return (bool)obj.GetValue(IsWatchedProperty);
}
public static void SetIsWatched(BindableObject obj, bool value)
{
obj.SetValue(IsWatchedProperty, value);
}
private static void OnIsWatchedChanged(BindableObject bindable, object oldValue, object newValue)
{
AddRef(bindable);
}
public async static void AddRef(object p)
{
GC.Collect();
await Task.Delay(100);
GC.Collect();
_refs.Add(new WeakReference(p));
foreach (var #ref in _refs)
{
if (#ref.IsAlive)
{
var obj = #ref.Target;
Debug.WriteLine("IsAlive: " + obj.GetType().Name);
}
else
{
Debug.WriteLine("IsAlive: False");
}
}
Debug.WriteLine("---------------");
}
}
Then to use it you can use the IsWatched property in your pages xaml or in code behind you can just do Refs.AddRef(this); after InitializeComponent(); and just navigate to and from the page several times. If it is being gc'ed the console should print IsAlive: False if not it will print out the type.
Most often the leaks we had were fixed by clearing the BindingContext and/or setting some things to null when leaving the page.
protected override void OnParentSet()
{
base.OnParentSet();
if (Parent == null)
{
BindingContext = null;
}
}

Android Best Practise to nullify unused objects ? Is it Good or it have disadvantages?

I am new to Android,
Is it good to null all objects in ondestroy() method ?
example :
public class HelloAndroid extends Activity {
TextView tv = null;
private static int mValue; // a static member here
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
tv = new TextView(this);
tv.setText((mValue != 0) ?
("Left-over value = " + mValue) : "This is a new instance");
setContentView(tv);
}
public void onDestroy() {
super.onDestroy();
tv = null;
}
}
In theory, it shouldn't have any benefit. However, nulling references in an activity can help mitigate the effects of memory leaks that you otherwise have no control over, such as this one in the Google Maps API.
Whether you should always do this to protect against future memory leaks of this sort is a matter of opinion. You have to weigh the cost of maintaining the extra code against the probably minimal benefit.
It is completely unnecessary. The objects will be garbage collected automatically.

Unbinding drawables from view on onDestroy()

I read a lot about memory leaks in the last few days, and came across some interesting stuff.
I saw this answer to a basic Android bitmap-related memory leak question (the answer is from 2011) and I was wondering if this is still the case.
If I'm using views that contain bitmaps in my activity (ImageViews, TextViews...), do I really need to unbind their drawables when destroying the activity?
Is this only in some cases or always?
It's no longer necessary as of 4.0, as the callback is now stored in a WeakReference.
From 2.3.7:
public final void setCallback(Callback cb) {
mCallback = cb;
}
and in 4.0.1:
public final void setCallback(Callback cb) {
mCallback = new WeakReference<Callback>(cb);
}

View Pager Memory Leak with Bitmaps and Volley

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

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