Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible for a callback method to be called after onDestroy?

In the latest version of my app, some users are experiencing a crash that I'm unable to reproduce. Currently only Samsung devices running Lollipop are having the issue, but that might just be coincidence. After analyzing the stack trace and relevant code, I think that I may have found the culprit. To test my assumption, I simplified the code to the snippet below:

public class TestActivity extends AppCompatActivity {

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

        Button b = new Button(this);
        b.setText("Click me!");
        b.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                new Handler().post(new Runnable() {
                    @Override
                    public void run() {
                        // This is the callback method
                        Log.d("TAG", "listenerNotified");
                    }
                });
            }
        });

        setContentView(b);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        Log.d("TAG", "onDestroy");
    }

}

Every time I test the above application by first tapping the Click me button and then the back button, listenerNotified is printed to the console before onDestroy().

I'm however not sure if I can rely on this behavior. Does Android make any guarantees about the above scenario? Can I safely assume that my Runnable will always be executed before onDestroy() or is there a scenario where that won't be the case? In my real app, there is of course a lot more happening (like other threads posting to the main thread and more operations happening in the callback). But this simple snippet seemed sufficient to demonstrate my concern.

Is it every possible (possibly due to the influence of other threads or callbacks posted to the main thread) that I get the debug output below?

D/TAG: onDestroy
D/TAG: listenerNotified

I would like to know this, since that outcome being possible would explain the crash.

like image 549
s1m0n Avatar asked Sep 18 '16 21:09

s1m0n


People also ask

What is ondestroy() method in Android?

The Android onDestroy () is the method that is called when an activity finishes and the user stops using it. It is the final callback method received by activity, as after this it is destroyed.

What is a callback function?

Callback function is a function that is called through a function pointer. If you pass the pointer (address) of a function as an argument to another, when that pointer is used to call the function it points to it is said that a call back is made. Why Should You Use Callback Functions?

What are the callback methods in Android for activity?

In Android, we have the following 7 callback methods that activity uses to go through the four states: We’ll understand these in the following: The Android oncreate () method is called at the very start when an activity is created. An activity is created as soon as an application is opened. This method is used in order to create an Activity.

Is the original callback actually executed by Another callback?

So the example’s original callback is actually executed by another callback. Be careful not to nest too many callbacks if you can help it, as this can lead to something called “callback hell”! As the name implies, it isn’t a joy to deal with.


2 Answers

Is it possible for a callback method to be called after onDestroy()?

Yes.

Let's change a bit your sample code regarding posting a Runnable to the Handler. I also assume (according to your description) that you may have multiple Runnables posted to the main thread, so at some point there might be a queue of Runnables which brings me to a delay in the experiment below:

public void onClick(View view) {
    new Handler().postDelayed(new Runnable() {
        @Override
        public void run() {
            // This is the callback method
            Log.d("TAG", "listenerNotified");
        }
    }, 3000);
}

Now push the button b, then press the back button and you should see the output in question.

Might it be the reason of your app crash? It's hard to say without seeing what you got. I'd just like to note that when new Handler() is instantiated on a thread (the main thread in your case), the Handler is associated with the Looper's message queue of the thread, sending to and processing Runnables and messages from the queue. Those Runnables and messages have a reference to the target Handler. Even though Activity's onDestroy() method isn't a "destructor", i.e. when the method returns the Activity's instance won't be immediately killed (see), the memory cannot be GC-ed because of the implicit reference* to the Activity. You'll be leaking until the Runnable will be de-queued from the Looper's message queue and processed.

A more detailed explanation can be found on How to Leak a Context: Handlers & Inner Classes


* Instance of anonymous inner class Runnable has a refers to an instance of anonymous inner class View.OnClickListener that, in its turn, has a reference to the Activity instance.

like image 88
Onik Avatar answered Oct 15 '22 04:10

Onik


You might need to consider not just posting delayed runnable to handler. You might encounter issues when your running task into a separate thread and your activity is already destroyed. You can do and implement like this.

your class activity
{
  Handler mHandler;

  .. onCreate ()
  {
    mHandler = new Handler();
  }

  .. onDestory ()
  {
    if (mHandler != null)
    {
      mHandler.removeCallbacksAndMessages(null);
      mHandler = null;
    }
  }

  private void post (Runnable r)
  {
    if (mHandler != null)
    {
      mHandler.post(r);
    }
  }
}

By this, any pending task on handler message queue will be destroyed upon activity is destroyed.

Only to make consider that you are aware that you dont need to run any task after the activity is destroyed.

like image 33
Keyfe Ang Avatar answered Oct 15 '22 04:10

Keyfe Ang