Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Activity instance still exists even after onDestroy() is called

I pass a handler created on mainUI thread from Activity and passed to a thread which performs some network operation and when i obtain result i send the result back to the activity using the handler.

This approach had issue in memory leaks when i went through these links:
Inner ClassHandler Memory Leak
Android Developers

So i had implemented WeakReference, and kept the activity instance using WeakReference. But i am still seeing Activity instance alive even after activity is destroyed.

I created a Handler inside activity and passed activity instance as weakreference to handler.
By the time my Handler responds with a message delivered to it after 10secs, Activity is destroyed. But the weak reference still has the Activity instance and i am seeing the Toast, after Activity is destroyed.

Is there some where my understanding wrong ?
Can someone explain how to handle messages delivered to a handler,but the UI is not around ?

import java.lang.ref.WeakReference;

import android.os.Handler;
import android.os.Message;

public abstract class SingleParamHandler <T> extends Handler
{
private WeakReference<T> mActivityReference;

public SingleParamHandler(T activity) {
    mActivityReference = new WeakReference<T>(activity);
}

@Override
public void handleMessage(Message msg) {
    if (mActivityReference.get() == null) {
        return;
    }
    handleMessage(mActivityReference.get(), msg);
}

protected abstract void handleMessage(T activity, Message msg);

}

import android.app.Activity;
import android.os.Bundle;
import android.os.Message;
import android.widget.Toast;

public class MainActivity extends Activity {

MyHandler<MainActivity> handler;
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main1);
    handler = new MyHandler<MainActivity>(this);
    new Thread(new MyRunnable(handler)).start();
}

public void onDestroy() {
    super.onDestroy();
    System.out.println("######## Activity onDestroy() ###### ");
}

private class MyRunnable implements Runnable {
    private Handler mHandler;
    public MyRunnable(Handler handler) {
        mHandler = handler;
    }

    public void run() {
        try {
            Thread.sleep(10000);
            mHandler.sendMessage(Message.obtain(handler, 1));
        } catch ( Exception e) {
            e.printStackTrace();
        }
    }
}


private static class MyHandler<T> extends SingleParamHandler<T> {

    public MyHandler(T activity) {
        super(activity);
    }

    @Override
    public void handleMessage(T act, Message msg) {
        if(msg.what == 1) {
            Toast.makeText((MainActivity)act, "Called after activity destroyed", Toast.LENGTH_LONG).show();;
        }
    }
}

}

Based on the response obtained, i am updating the answer here. You may do it in the way u liked. But this is one way.

Added the below function in SingleParamHandler

public void clear() {
    mActivityReference.clear();
}

And in Activity onDestroy()

public void onDestroy() {
    super.onDestroy();
    System.out.println("######## Activity onDestroy() ###### ");
    handler.clear();
}
like image 639
Mani Avatar asked Apr 24 '13 06:04

Mani


2 Answers

You don't need a WeakReference here. The Handler can just contain a reference to the Activity. In activity's onDestroy() just call a method on MyHandler that sets the reference to the Activity to null. Check for null in handleMessage().

Another choice would be this: in activity's onDestroy() call a method that interrupts the sleeping thread so that it shuts down before sending the message.

like image 149
David Wasser Avatar answered Sep 23 '22 12:09

David Wasser


There's no guarantee that Android will really delete an object from memory if it's not required to do so. In other words, Activity objects can stay in memory even after onDestroy() has been called (if there's enough memory available). On the other hand, there's no guarantee that onDestroy() will be called if there's not enough memory; quite to the contrary, Android is allowed to kill your whole process after calling onPause() on your current Activity (depending on the Android version).

I think there's a better path to follow for your purpose. What you may want to do is attach, detach and possibly re-attach (e.g. on configuration changes) Activities to your Service. Don't hope for the garbage collector to do the work for you. Rather, make it explicitly.

Subclass Activity and override the lifecycle methods as well as startActivity() and startActivityForResult() to let your Service know who's in charge right now. Of course, that's only a best-effort approach since some callbacks aren't guaranteed, but that only matters in certain situations which aren't dangerous. For example, your Activity won't detach from your Service in onPause(), but it could get killed right afterwards. But either your Service runs in the same process, so it gets killed at the same time. Or it runs in a different process, but then Android will notice the broken connection and may or may not kill the service as well; if not, then all you need to do is implement it in a robust fashion to be able to deal with the connection loss.

Update

After reading your comment: You're right, I didn't address that specifically.

i am figuring out how to avoid messages being sent to a handler which is created in a activity which is destroyed

Given your code above, and assuming that you really just want to display Toasts with an Activity as long as it exists, the following approach should help.

  • If your Thread is supposed to serve more than one Activity, extend it such that Activities can register with the Thread after it is created. If your Thread just serves one Activity, pass the Activity reference along with the Handler reference upon your Thread's (Runnable's) construction.
  • Before your Thread sends the message via the Handler, check activity.isDestroyed(). If the Activity is not destroyed, send the message. If the Activity is destroyed, do not send the message.
  • Depending on whether your Thread should server more than one Activity, either exit it's Runnable's run() method or set it's Activity reference to null if it finds that the Activity has been destroyed.

This should fix your above code. However, if your scenario grows, other approaches may be more suitable.

like image 32
class stacker Avatar answered Sep 23 '22 12:09

class stacker