How do I enforce Application onCreate after app was closed and a service is running in the bg?

1.3k Views Asked by At

Basically I've got an application that runs a foreground service. When I start the application I need to do session specific initialization in the Application's onCreate method.

When I close the app, the service keeps running (desired behaviour), however, when I reopen the app from the launcher / from my notification, the Application onCreate isn't getting called again.

My question are:

  1. How do I enforce the Application onCreate be called again even though there's a service running? (The service is probably keeping a reference to the application object, right?)
  2. Is there a way to get indication inside the Application class that the app has been started again but from a service?
  3. What other solutions can you think about?

MyService in AndroidManifest.xml:

<service android:name=".MyService"
    android:exported="false"/>

My service's onStartCommand:

    createNotificationChannel();
    Intent notificationIntent = new Intent(this, MyActivity.class);
    PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
    Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
            .setContentTitle("My Service")
            .setContentText("Service")
            .setSmallIcon(R.drawable.ic_android)
            .setContentIntent(pendingIntent)
            .build();
    startForeground(1, notification);
5

There are 5 best solutions below

3
Phantom Lord On

The event onCreate is fired during the creation of the object. After there is onStart. If the app has been closed, the object is still active in memory and will be destroyed if the device require more free resources. So, if you reopen the app, only the onStart event is fired. Try to move the initialization in onStart, or put a check or similar in onStart.

Lifecycle of on activity https://developer.android.com/guide/components/activities/activity-lifecycle

16
David Wasser On

The Application object is a singleton. It gets instantiated (and onCreate() is called) when the OS process hosting your app is created. If your app is hosting a foreground Service, then that OS process will continue to exist, so Application.onCreate() will not be called.

It isn't exactly clear to me why you need it to be called, but you can have your Service run in a different OS process than the other components (Activity, BroadcastReceiver, Provider) of your app. In that case, the OS process hosting the foreground Service won't get recreated, but the OS process hosting your activities would get recreated (if it is killed off by Android which would normally occur if there are no active components running in it). Since the Application object exists in each OS process, when the OS process hosting the activities is recreated, Android will instantiate a new Application object and call onCreate() on it at that time.

To have your Service run in a different OS process, just add this to the <service> tag in the manifest:

android:process=":remote"

NOTE: You can use any name you want instead of "remote", just make sure that it begins with a colon (":")

WARNING: You need to be aware that you have 2 OS processes for your app and plan accordingly. The code running in these 2 processes can not see each other so this may cause you additional problems in your app. Any data that you were sharing between components (Service and Activity, for example) will need to be put in a database or shared preferences or some other mechanism to share it across OS processes.

4
Lennart Steinke On

I would suggest that you create a utility function containing your init code. Use a flag to see if the operation needs to be run and do nothing if this flag is set.

Call this from your base Activity (not from the application) due to the exact problem you're having. It also allows to give your user feedback during your initialization.

Clear the flag once the user exits.

So, basically, move your code to a place where it can be called consistantly.

3
Thracian On

You can create a ProcessLifeCycleOwner inside Application and listen lifecycle events. You may also need to set a flag to a class and get that value from a method to check is user got back after pressing home button or started app using notification

class MyApp: Application() {

    override fun onCreate() {
        super.onCreate()

        ProcessLifecycleOwner
            .get()
            .lifecycle
            .addObserver(ApplicationObserver())
    }

    inner class ApplicationObserver : LifecycleObserver {

        @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
        fun onStop() {
        }

        @OnLifecycleEvent(Lifecycle.Event.ON_START)
        fun onStart() {

        }

        @OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
        fun onCreate() {

        }


        @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
        fun onDestroy() {

        }
    }
}
2
Agi Maulana On

Find the way to find out the app started (at least the first activity started) then do some initialization as you want on onCreate(). I am facing the same situation as you, the app must doing something when get launched and left. I created an interface to listen that the app entering foreground and background mode (there is no activity running):

interface IApplicationLifeCycle {
    fun onEnterForeground()
    fun onEnterBackground()
    fun onFirstCreated()
    fun onLastDestroyed()
}

Then I create a class to detect the app is entering background and foreground

class AppLifeCycleTracker(private val iApplicationLifeCycle: IApplicationLifeCycle? = null) : Application.ActivityLifecycleCallbacks {
private var aliveActivityCount = 0 // created on destroyed
private var activeActivityCount = 0 // started on stopped
private var liveActivityCount = 0 // resumed on paused

override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
    aliveActivityCount++
    if (aliveActivityCount == 1) {
        iApplicationLifeCycle?.onFirstCreated()
    }
}

override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {}

override fun onActivityStarted(activity: Activity) {
    activeActivityCount++
}

override fun onActivityResumed(activity: Activity) {
    liveActivityCount++
    if (liveActivityCount == 1 && activeActivityCount == 1) {
        iApplicationLifeCycle?.onEnterForeground()
    }
}

override fun onActivityPaused(activity: Activity) {
    liveActivityCount--
}

override fun onActivityStopped(activity: Activity) {
    activeActivityCount--
    if (liveActivityCount == 0 && activeActivityCount == 0) {
        iApplicationLifeCycle?.onEnterBackground()
    }
}

override fun onActivityDestroyed(activity: Activity) {
    aliveActivityCount--
    if (aliveActivityCount == 0) {
        iApplicationLifeCycle?.onLastDestroyed()
    }
}

}

on my application

class MyApplication: IApplicationLifeCycle{

    onCreate() {
        super.onCreate() 
        registerActivityLifecycleCallbacks(AppLifeCycleTracker(this))
    }

    override fun onEnterForeground() {
        Log.i("AppVisibility", "onEnterForeground")
        // do action here
    }

    ...
    ...
    ...
}

on overridden method, onEnterForeground just do the action as you want, in this case, you want to do some initialization