Memory leak with interface referance

2019-08-22 00:11发布

问题:

I have a question about memory leak.I have two classes.

The first one is:

public class Utility {
private static Utility instance = null;
private UpdateListener listener;

//Make it a Singleton class
private Utility(){}
public static Utility getInstance() {
    if (instance == null)
        instance = new Utility();
    return instance;
}

public void setListener(UpdateListener listener) {
    this.listener = listener;
}

//Long running background thread
public void startNewTread() {
    new Thread (new Runnable() {
        @Override
        public void run() {
            try {
                Thread.sleep(1000 * 10);
                if (listener != null)
                    listener.onUpdate();
            } catch (InterruptedException e) {
                Log.d("Utility", e.getMessage());
            }
        }
    }).start();
}

//Listener interface
public interface UpdateListener {
    public void onUpdate();
}
}

Thesecond class is:

public class ListenerLeak extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    //Setting the listener
    Utility.getInstance().setListener(new Utility.UpdateListener() {
        @Override
        public void onUpdate() {
            Log.d("ListenerLeak", "Something is updated!");
        }
    });

    //Starting a background thread
    Utility.getInstance().startNewTread();
}

@Override
protected void onDestroy() {
    super.onDestroy();
}
}

in this activity.May new Utility.UpdateListener create a memory leak? when the activity destoroyed , only Updatelistener can be alive.does activity can be alive?

回答1:

Create an inner class inside a Utility class like below. Then move the thread to that class.

 public void startNewTread() {
    new MyThread().start();
    }

 private static class MyThread extends Thread {
    @Override
    public void run() {
       try {
            Thread.sleep(1000 * 10);
            if (listener != null)
                listener.onUpdate();
        } catch (InterruptedException e) {
            Log.d("Utility", e.getMessage());
        }
    }
  }

Reason: After each configuration change, the Android system creates a new Activity and leaves the old one behind to be garbage collected. However, the thread holds an implicit reference to the old Activity and prevents it from ever being reclaimed. As a result, each new Activity is leaked and all resources associated with them are never able to be reclaimed. https://www.androiddesignpatterns.com/2013/04/activitys-threads-memory-leaks.html will help to understand it.



回答2:

Leak simply means when an object of a class is no longer used but the system cannot reclaim memory where they live on (heap memory). This happens because there is at least one strong reference keep refer to the object.

Back to your case.

// Setting the listener
Utility.getInstance().setListener(new Utility.UpdateListener() {
    @Override
    public void onUpdate() {
        Log.d("ListenerLeak", "Something is updated!");
    }
});

You create an anonymous inner class (class which no name) which implements Utility.UpdateListener interface and you create an anonymous object of the anonymous inner class as well by using new keyword but do not keep any reference to the object.

In Java when you declare an inner class, it will keep a reference to outer class (in this case ListenerLeak activity).

// Starting a background thread
Utility.getInstance().startNewTread();

In startNewTread method, you create and start a background thread.

new Thread (new Runnable() {
    @Override
    public void run() {
        try {
            Thread.sleep(1000 * 10);
            if (listener != null)
                listener.onUpdate();
        } catch (InterruptedException e) {
            Log.d("Utility", e.getMessage());
        }
    }
}).start();

Again in this class you create an anonymous class which implements Runnable interface and you create an anonymous object of Thread class. As a result, the thread keep a reference to Utility class. This is reference chain

Background Thread -> Utility -> Listener -> ListenerLeak (the activity)

When users press back key on the activity. At this time the activity is longer used and the system can reclaim memory where the activity live on. Unfortunately, because the background thread is still keep a reference to it so that why the system cannot reclaim the memory. As a result, a leak occurs.

Solution: When users press back key, before destroy the activity, the system will call 'onDestroy' method. In this method you should remove the reference from the background thread to this activity.

@Override
protected void onDestroy() {
    Utility.getInstance().setListener(null);
    super.onDestroy();
}

Here is the reference chain

Background Thread -> Utility -> Listener -> null (point to nothing)

And now there is no strong reference refer to the activity, so the system will reclaim the memory and no leak occur.