jbww
jbww

Reputation: 765

Memory leak in very simple app

An app I am working on is leaking memory. As far as I can tell I am doing everything suggested in: http://developer.android.com/resources/articles/avoiding-memory-leaks.html

I've cut my app down to a very simple app which does nothing but set the background image. Every time I do a screen orientation change the app leaks 30k-50k of memory.

So I am suspicious of the following:

SetContentView() and findViewById()

Do I need to do something in the onDestroy() related to these calls, to decouple them from the Activity?

Also I have a couple of questions. In onDestroy() I call setBackgroundResource(0). I believe if I do not do this, the Drawable for the background bitmap will maintain a callback to the view and this will cause a leak of the entire context. Is this true? Adding this call to the onDestroy() certainly seemed to make a big difference in the magnitude of the leaks. In the view constructors I try to remove some references to the activity by making the super() calls with the Application context, as opposed to the activity context. Does this actually provide this benefit or does it matter at all? Are there side effects to doing this that I should be aware of?

Code and XML follows: At this point I really don’t see why it should be leaking memory. Any enlightenment would be greatly appreciated.

MemLeak.java

package randombrand.MemLeak;

import randombrand.MemLeak.R;
import randombrand.MemLeak.MLView;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;

public class MemLeak extends Activity {

    private MLView mmlView;
    private static final String strmlBundle = "Mem Leak";

    @Override
    public void onCreate(Bundle icicle) {
        super.onCreate(icicle);
        setContentView(R.layout.memleak_layout);
        mmlView = (MLView) findViewById(R.id.viewMemLeak);
        try {
            mmlView.Init(this);
            SetBackgroundBitmap();
        } catch (Exception ex) {
            Log.e(strmlBundle, "Failed to launch Mem Leak." + ex);
            this.finish();
        }
        if (icicle == null) {
            mmlView.SetActive(true);
        } else {
            Bundle bundle = icicle.getBundle(strmlBundle);
            if (bundle != null) {
                mmlView.SetActive(true);
                mmlView.invalidate();
            } else {
                mmlView.SetActive(false);
            }
        }
    }

    @Override
    protected void onPause() {
        super.onPause();
        mmlView.SetActive(false);
    }

    @Override
    protected void onResume() {
        super.onResume();
        mmlView.SetActive(true);
    }

    private void SetBackgroundBitmap() {
        mmlView.setBackgroundResource(R.drawable.dark_background);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        mmlView.setBackgroundResource(0);
    }
}

MLView.java

package randombrand.MemLeak;

import android.content.Context;
import android.graphics.Canvas;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.view.View;

public class MLView extends View {

    private static final long ltFpDrawDelay = 66;
    Context mContextApp;
    // true when we are not sleeping in the background
    private boolean mfActive = false;

    public MLView(Context context, AttributeSet aSet) {
        super(context.getApplicationContext(), aSet);
    }

    public MLView(Context context, AttributeSet aSet, int nStyle) {
        super(context.getApplicationContext(), aSet, nStyle);
    }

    public void Init(MemLeak mLeak) {
        mContextApp = mLeak.getApplicationContext();
        SetActive(true);
    }

    public void Update() {
        mRedrawHandler.sleep(ltFpDrawDelay);
    }

    public void SetActive(boolean fActive) {
        mfActive = fActive;
        if (fActive) Update();
    }

    @Override
    public void onDraw(Canvas canvas) {
        super.onDraw(canvas);
    }

    private RedrawHandler mRedrawHandler = new RedrawHandler();

    class RedrawHandler extends Handler {

        @Override
        public void handleMessage(Message msg) {
            MLView.this.Update();
            MLView.this.invalidate();
        }

        public void sleep(long ltMillis) {
            this.removeMessages(0);
            sendMessageDelayed(obtainMessage(0), ltMillis);
        }
    }
}

memleak_layout.java

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">

    <randombrand.MemLeak.MLView
        android:id="@+id/viewMemLeak"
        android:layout_width="fill_parent" 
        android:layout_height="fill_parent" />

</FrameLayout>    

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="randombrand.MemLeak"
      android:versionCode="1"
      android:versionName="1.0">
    <uses-sdk android:minSdkVersion="5" />

    <application android:icon="@drawable/icon" android:label="@string/app_name"
             android:debuggable="true">
        <activity android:name="MemLeak"
                 android:launchMode="singleInstance"
                 android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

Upvotes: 3

Views: 1593

Answers (1)

Will Tate
Will Tate

Reputation: 33509

jbww,

I was noticing similar behavior in some of my Activities so I added

android:configChanges="keyboardHidden|orientation"

to the Activity in AndroidManifest.xml then reloaded the necessary information by overriding onConfigurationChanged().

I no longer noticed the heap grow each time I rotated the device.

I hope this works for you too.

Upvotes: 1

Related Questions