1

I have an Android application with a single Activity (MainActivity). I also have a static state variable (foo) that needs to be started and stopped with MainActivity. The lifetime of foo must match the entire lifetime of MainActivity, not its visible lifetime, nor its foreground lifetime. Here's the basic gist:

static Foo foo;

public void onCreate(Bundle savedInstanceState)
{
    super.onCreate(savedInstanceState);
    foo.start();
}

protected void onPause()     
{
    super.onPause();
    if (isFinishing())
        disposeFoo();
}

protected void onDestroy()
{
    super.onDestroy();
    disposeFoo();
}

private void disposeFoo()
{
    if (foo.isRunning())
        foo.stop();
}

Every now and again I will get a crash report that says: Foo has been started while already running.

I can start and stop MainActivity all day long from the app launcher and this crash won't occur. As far as I know, no one is calling startActivity on MainActivity either.

Is it expected behavior that a new instance of MainActivity will be created, and onCreate called on it before the onDestroy is run on the old instance all within the same application? In what circumstances would this occur? Is there a different pattern I should be using to initialize libraries, databases, and other singleton objects?

dhakim
  • 508
  • 3
  • 10
  • 2
    Where is the exact definition of `Foo`? Does it extend something from the Android API? Can you post the full stacktrace of that exception? – m0skit0 May 06 '15 at 15:25
  • I unfortunately have a couple different 'Foo's in my codebase, and my MainActivity is far from being as simple as above. The most frustrating of my Foos is a subclass of SQLiteOpenHelper, and my 'Foo has been started while already running' is an IllegalStateException: Attempt to reopen an already closed database. – dhakim May 06 '15 at 15:47
  • I see but still not enough to help you properly. Post the **full stacktrace**. – m0skit0 May 06 '15 at 15:57
  • I understand your frustration, but there's no real stacktrace for this one, because there's no real code. In this toy example, the stacktrace is foo.start() was called by MainActivity.onCreate(). I'm looking to avoid future maintenance by finding a different way to structure initialization of third party libraries and instantiation/interaction with data storage. Maybe I need to split my question apart somehow... I'm interested in understanding why my activity would be reinstantiated in the same Application, and I'm interested in a better place to put my startup code. – dhakim May 06 '15 at 16:14
  • IllegalStateException is an exception and should have a stacktrace. BFrustration? No, you're wrong. You're the one seeking help, not me. I'm just trying to help, but you won't let me. Be my guest :) – m0skit0 May 07 '15 at 07:53

2 Answers2

1

Because onDestroy is not called if, for example, activity was recreated because of screen rotation.

From the Activity documentation:

The final call you receive before your activity is destroyed. This can happen either because the activity is finishing (someone called finish() on it, or because the system is temporarily destroying this instance of the activity to save space. You can distinguish between these two scenarios with the isFinishing() method.

So you should call foo.start/foo.stop in onStart/onStop or onResume/onPause.

--

Update:

If I understand correctly, the problem is that you are tied to a singleton/monostate object Foo the should be unique for all objects, and must be destroyed when ALL activities are destroyed.

The problem is that nothing can guarantee that only one instance of activity has a runinng Foo, because onDestroy can be called after a new instance is created.

So the solution is to use an Instance counter:

public class Foo {
private int instCounter = 0;

public synchronized void start() {
    ...
    ++instCounter;
}

public synchronized void dispose() {
    --instCounter;
    if (instCounter == 0) {
         // dispose
    }
}

This should do the trick.

HappyCactus
  • 1,935
  • 16
  • 24
  • This one got me a couple of times in the past, so I've upvoted it. But I am handling configuration changes to prevent re-instantiating the Activity. The onPause isFinishing() check catches these as well, if I remember correctly. – dhakim May 06 '15 at 16:11
  • Also, I need the static variable lifetime tied to the entire lifetime of the activity, not to when it is visible or in the foreground. – dhakim May 06 '15 at 16:12
  • I am not sure to understand correctly your need, you could instantiate the variable from a class derived from the Application class: http://developer.android.com/reference/android/app/Application.html . – HappyCactus May 07 '15 at 15:48
  • About your first comment: the difference between OnStart/onStop and onResume/onPause is described in the Activity class documentation. A popup (like an alert dialog) will fire onPause but not onStop. The choice depends on what Foo does . – HappyCactus May 07 '15 at 15:51
  • OK sorry, I misunderstood your second commend. Details on the post – HappyCactus May 07 '15 at 15:55
1

Your app is in very often killed if some other application with higher priority (generally, if it's in the foreground it's higher priority) needs the resources. This is due to the nature of mobile devices having relatively limited resources. You will find that it's static variables may be null once you return to it, so static variables for a longer periods of time in Android is a bad idea.

You should save your data somewhere more durable. You might find this article on general Data Storage to be useful. This question should be relevant too: Saving Android Activity state using Save Instance State

Community
  • 1
  • 1
Bojan Kseneman
  • 15,488
  • 2
  • 54
  • 59
  • I have heard this from a number of different sources, but I have never experienced it first hand, nor do I fully understand what is meant by 'static variables may be null once you return to it'. I believe you are saying that the entire process hosting an Activity and its entire memory space may be wiped at any time. When you return to the activity, you cannot rely on any other activities having already been run or existing in the task stack underneath your activity. If that's what you mean, then I totally agree, but I don't think it answers my question. – dhakim May 06 '15 at 15:57
  • One of my 'Foo's is a database which should be wiped every time the user launches MainActivity, but not when he 'returns' to it. That is, if he hid the app or navigated away, then returns to it, his data remains. But if he backed out of the app with the back button, his data stays. I'm looking for a proper pattern to do that in a generic way. – dhakim May 06 '15 at 16:01