TeamChillshot
TeamChillshot

Reputation: 157

Android Memory Leak - Cannot Deallocate Bitmaps Correctly, Out Of Memory Error - Bitmap Size Exceeds VM Budget [Mono Android]

I cannot seem to figure out why my activity keeps running out of memory.

When I load my activity, I have a bunch of images that are loaded into a custom listview adapter. When I start the activity, the screen loads fine. But when I change orientation or go back and start it again, the activity generates an Out of Memory Exception.

Here's what my activity looks like:

Fish Species Activity that Crashes

Here's the code for my activity:

    [Activity(Label = "FishinTales: Fish Species")]
public class Activity_View_FishSpecies : Activity
{
    #region Components
    private Model n_model;
    private ListView n_fishSpeciesListView;
    #endregion

    protected override void OnCreate(Bundle bundle)
    {
        base.OnCreate(bundle);

        // Get Application Global Model
        this.n_model = ((MyApp) this.ApplicationContext).FishingData;

        // Set our view from the "View_FishSpecies" layout resource
        SetContentView(Resource.Layout.View_FishSpecies);

        this.n_fishSpeciesListView = FindViewById<ListView> (Resource.Id.xml_fishSpeciesListView);
        this.n_fishSpeciesListView.Adapter = new FishSpeciesListAdapter (this.ApplicationContext, this.n_model.SpecieManager.Species);
    }

    protected override void OnDestroy()
    {
        base.OnDestroy();
        this.unbindDrawables(FindViewById(Resource.Id.xml_root));
        this.n_fishSpeciesListView.Adapter = null;
        this.n_fishSpeciesListView = null;
    }

    private void unbindDrawables(View view) {
        if (view.Background != null) {
            view.Background.SetCallback(null);
        }
        if (view.GetType() == typeof(ViewGroup)) {
            for (int i = 0; i < ((ViewGroup) view).ChildCount; i++) {
                unbindDrawables(((ViewGroup) view).GetChildAt(i));
            }
            ((ViewGroup) view).RemoveAllViews();
        }
    }
}

public class FishSpeciesListAdapter : BaseAdapter
{
    Context n_context;
    List<AppCode.Specie> n_specieData;
    List<Bitmap> n_bitmapCache;

    public FishSpeciesListAdapter (Context context, List<AppCode.Specie> specieData)
    {
        this.n_context = context;
        this.n_specieData = specieData;
        this.n_bitmapCache = new List<Bitmap>();
        this.LoadBitmapsIntoCache();
    }

    private void LoadBitmapsIntoCache()
    {
        foreach(AppCode.Specie specie in this.n_specieData)
        {
            if (specie.RelatedMedia.AttachedPhotos.Count < 1)
            {
                this.n_bitmapCache.Add(BitmapFactory.DecodeResource(this.n_context.Resources, Resource.Drawable.Icon)); 
            }
            else
            {
                this.n_bitmapCache.Add(BitmapFactory.DecodeByteArray(specie.RelatedMedia.AttachedPhotos[0], 0, specie.RelatedMedia.AttachedPhotos[0].Length));  
            }
        }
    }

    public override int Count {
        get { return this.n_specieData.Count; }
    }

    public override Java.Lang.Object GetItem (int position)
    {
        return null;
    }

    public override long GetItemId (int position)
    {
        return 0;
    }

    // create a new ImageView for each item referenced by the Adapter
    public override View GetView (int position, View convertView, ViewGroup parent)
    {
        if(convertView==null){

            LayoutInflater li = LayoutInflater.FromContext(parent.Context);
            convertView = li.Inflate(Resource.Layout.Adapter_FishSpeciesIcon, null);
        }

        ImageView iconImage = (ImageView)convertView.FindViewById(Resource.Id.xml_adapter_fishSpeciesIconImage);
        TextView nameText = (TextView)convertView.FindViewById(Resource.Id.xml_adapter_fishSpeciesNameText);
        TextView scientificNameText = (TextView)convertView.FindViewById(Resource.Id.xml_adapter_fishSpeciesScientificNameText);

        nameText.Text = this.n_specieData[position].Name;
        scientificNameText.Text = this.n_specieData[position].ScientificName;
        iconImage.SetImageBitmap(this.n_bitmapCache[position]); 

        return convertView;
    }
}

Here's my log of all processes until the OOM error is generated.

    Loading Defaults - Successfully Loaded Defaults
Grow heap (frag case) to 6.474MB for 314670-byte allocation
Clamp target GC heap from 32.512MB to 32.000MB
796824-byte external allocation too large for this process.
VM won't let us allocate 796824 bytes
Clamp target GC heap from 33.447MB to 32.000MB
--- decoder->decode returned false
UNHANDLED EXCEPTION: Java.Lang.OutOfMemoryError: Exception of type 'Java.Lang.OutOfMemoryError' was thrown.
at Android.Runtime.JNIEnv.CallStaticObjectMethod (intptr,intptr,Android.Runtime.JValue[]) <0x00080>
at Android.Graphics.BitmapFactory.DecodeByteArray (byte[],int,int) <0x001bb>
at FishinTales.FishSpeciesListAdapter.LoadBitmapsIntoCache () <0x00187>
at FishinTales.FishSpeciesListAdapter..ctor (Android.Content.Context,System.Collections.Generic.List`1<FishinTales.AppCode.Specie>) <0x000a3>
at FishinTales.Activity_View_FishSpecies.OnCreate (Android.OS.Bundle) <0x0012f>
at Android.App.Activity.n_OnCreate_Landroid_os_Bundle_ (intptr,intptr,intptr) <0x00057>
at (wrapper dynamic-method) object.99ebf5db-74ad-4da9-b431-612691e1f213 (intptr,intptr,intptr) <0x00033>

  --- End of managed exception stack trace ---
java.lang.OutOfMemoryError: bitmap size exceeds VM budget(Heap Size=6855KB, Allocated=3089KB, Bitmap Size=26666KB)
    at android.graphics.BitmapFactory.nativeDecodeByteArray(Native Method)
    at android.graphics.BitmapFactory.decodeByteArray(BitmapFactory.java:625)
    at android.graphics.BitmapFactory.decodeByteArray(BitmapFactory.java:638)
    at fishintales.Activity_View_FishSpecies.n_onCreate(Native Method)
    at fishintales.Activity_View_FishSpecies.onCreate(Activity_View_FishSpecies.java:29)
    at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1093)
    at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1780)
    at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:1837)
    at android.app.ActivityThread.handleRelaunchActivity(ActivityThread.java:3242)
    at android.app.ActivityThread.access$1600(ActivityThread.java:132)
    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1037)
    at android.os.Handler.dispatchMessage(Handler.java:99)
    at android.os.Looper.loop(Looper.java:143)
    at android.app.ActivityThread.main(ActivityThread.java:4196)
    at java.lang.reflect.Method.invokeNative(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:507)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:839)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:597)
    at dalvik.system.NativeStart.main(Native Method)

Unhandled Exception:
Java.Lang.OutOfMemoryError: Exception of type 'Java.Lang.OutOfMemoryError' was thrown.
at Android.Runtime.JNIEnv.CallStaticObjectMethod (intptr,intptr,Android.Runtime.JValue[]) <0x00080>
at Android.Graphics.BitmapFactory.DecodeByteArray (byte[],int,int) <0x001bb>
at FishinTales.FishSpeciesListAdapter.LoadBitmapsIntoCache () <0x00187>
at FishinTales.FishSpeciesListAdapter..ctor (Android.Content.Context,System.Collections.Generic.List`1<FishinTales.AppCode.Specie>) <0x000a3>
at FishinTales.Activity_View_FishSpecies.OnCreate (Android.OS.Bundle) <0x0012f>
at Android.App.Activity.n_OnCreate_Landroid_os_Bundle_ (intptr,intptr,intptr) <0x00057>
at (wrapper dynamic-method) object.99ebf5db-74ad-4da9-b431-612691e1f213 (intptr,intptr,intptr) <0x00033>

  --- End of managed exception stack trace ---
java.lang.OutOfMemoryError: bitmap size exceeds VM budget(Heap Size=6855KB, Allocated=3089KB, Bitmap Size=26666KB)
    at android.graphics.BitmapFactory.nativeDecodeByteArray(Native Method)
    at android.gra

When I view the activity initially, everything is fine. When I leave the activity and come back to it several times, it eventually crashes with the above error. This leads me to speculate that the error is coming from a memory leak where the Bitmaps are not recycled properly. So is there any example out there of how to deallocate these bitmaps acurately? It's hard to find information on this topic when your Bitmaps are inside the Custom ListView Adapter class. When I put Bitmap.recycle after I set it, I receive yet another error that says that the ListView is trying to access a recycled item. So how/when should I recycle according to the code above.

Upvotes: 2

Views: 2960

Answers (1)

Abu Saad Papa
Abu Saad Papa

Reputation: 1946

I would like to know which version of Android you are using. If it is Jelly Bean (Android 4.1.1) please remove this line of code

this.n_bitmapCache.Add(BitmapFactory.DecodeResource(this.n_context.Resources, Resource.Drawable.Icon)); 

and see whether you are getting memory leak.

My app also started giving memory leak after an update to Android 4.1.1 and I found the problem to be in

setBackgroundResource(id);

I tried changing it to

setBackground(context.getResources().getDrawable(id));

But still was getting the memory leak. I want to confirm if it is a bug in my code or bug in Jelly Bean which is causing a memory leak if we try to create drawables or bitmaps from Resources. Apologies for not answering your question but we can at least know the cause of the bug and raise an issue with the Android team.

UPDATE I found the solution in this link

Please use setBackgroundDrawable(null) for the Bitmaps created from the drawable folder of resources.

Upvotes: 1

Related Questions