4

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);
Whitebear
  • 1,761
  • 2
  • 12
  • 22
  • I think you should use `stopForground` after your service perform its intended task. – Rajnish suryavanshi Jun 21 '20 at 10:30
  • Even if I use it, it won't help, as it's a long operation. Once I start my app again the app oncreate won't be called, as it still hasn't finished performing its operation. – Whitebear Jun 21 '20 at 10:41
  • Is it possible to use thread instead of service? – Mike.R Jun 22 '20 at 19:52
  • No...It's not possible. – Whitebear Jun 23 '20 at 18:28
  • `When I close the app` do you mean you simply minimize the app or do you clear app from the recents app list? – azizbekian Jun 25 '20 at 08:08
  • Clear the app from the recent app list – Whitebear Jun 25 '20 at 14:47
  • You keep using the word "application"... `Application` lifecycle is the unix process lifecycle. If any of your other components (services, content providers, broadcast receivers) are active, the process is alive. Even if none of the components are visible you're not guaranteed that your `Application`/process is killed right away (so that `onCreate` is invoked next time the process starts). What you mean by "application" is the user-facing *activity*, right? – Eugen Pechanec Jul 01 '20 at 08:06

5 Answers5

1

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() {

        }
    }
}
Thracian
  • 43,021
  • 16
  • 133
  • 222
  • the onCreate gets called if I go to background and come back to the foreground, which is not my wanted behavior. How do you recommend to solve that? – Whitebear Jul 02 '20 at 10:28
  • 1
    You can check if your Activity started by notification or not in first or only Activity that's started, if it's started by touching notification you can use a shared object by that Activity and application and set a flag of that shared object and check that flag in application's process lifecycle methods. – Thracian Jul 02 '20 at 11:00
  • Yeah, that's basically what I ended up doing. – Whitebear Jul 02 '20 at 13:38
0

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.

David Wasser
  • 93,459
  • 16
  • 209
  • 274
  • I've tried this approach, but the Application object does not get recreated. (I'll give it another go though) – Whitebear Jun 29 '20 at 07:36
  • The `Application` is only recreated if the OS process is killed off by Android. But there is no guarantee that this will happen, even if you close all your activities. You may need to rethink your architecture because you cannot rely on this behaviour. What exactly are you trying to accomplish? You probably need to approach this a different way – David Wasser Jun 29 '20 at 09:47
  • There is no guarantee it will happen you're correct about that, but it usually does happen in case it's not held by a process. My use case is actually initializing an SDK from scratch every Application onCreate, which doesn't happen when using a service, therefore for my end users it's weird behaviour... I did find other workarounds for this - was just wondering if there's something else I could do to make it work the "proper" way that I was used to doing (through the Application object's onCreate) – Whitebear Jun 29 '20 at 10:03
  • Still don't understand. `Application.onCreate()` happens once, when the OS process is created. Your SDK can be initialized there and shouldn't need to be initialized again within the same OS process. `Application.onCreate()` is always called ONCE. Why would you need to call it again if the OS process is still running? – David Wasser Jun 29 '20 at 10:31
  • The other thing you can do is to write something to shared preferences when the app (`Activity`) is started by the `Service`. In this way you can differentiate if the `Activity` is started by a `Service` or by the user manually (nothing present in shared preferences). – David Wasser Jun 29 '20 at 10:36
  • `Application.onCreate` is called one per "app open" in case the app isn't running a service. The case isn't the same with services of course. I needed my SDK to run on `Application.onCreate` in order to hook into various component lifecycles and make sure I didn't miss them. – Whitebear Jun 29 '20 at 13:47
  • I could of course identify whether the "first" launcher,main intent was called when I opened the app , that's not a bad idea, and that's eventually what I did in my code... Hooked into the activity lifecycle and understood from there that the first main,laucher activity intent was called, and there I put my SDK initialization. Just hoped there was something else I overlooked concerning identifying specifically `Application.onCreate` :) – Whitebear Jun 29 '20 at 13:49
  • Your statement about it being called "once per app open" is not true. If you open your app, finish it and open it again quickly, `Application.onCreate()` may not be called. Because Android may not have killed the OS process hosting the app and just reuses it. Also, Android can kill the app if it is in the background. In this case, when the user returns to the app, Android will create a new OS process and `Application.onCreate()` will be called. You are trying to use something that isn't guaranteed to work. You need a more accurate and reliable method. – David Wasser Jun 29 '20 at 14:47
  • Android has some strange concepts that take some getting used to. There isn't really a very good way to determine when an app "starts" and "ends". It is built intentionally like this but seems strange if you aren't used to it. – David Wasser Jun 29 '20 at 14:50
  • I know it might happen, but it usually doesn't and that's good enough for me. Can you intentionally recreate the scenario where `Application.onCreate` isn't getting called after we "kill" the app? I've been doing this for some time and it has never happened to me. Not once in tens of thousands of times I've tried... Theoreotically you're probably right, but in practise I haven't seen it happening – Whitebear Jun 30 '20 at 07:42
  • Don't "kill" the app, just press the BACK key on the first `Activity`. The app now has no active activities. immediately launch the app again. Android probably has not killed the OS process. But maybe I don't understand what you are trying to do. All I'm saying is that `Application.onCreate()` is where you do things **ONCE per OS process instantiation**. That's what it is for. And it sounds like you are putting code there that needs to execute more than **ONCE per OS process instantiation**. Which is wrong. That code should go somewhere else. – David Wasser Jun 30 '20 at 09:24
  • Pressing back will take it to the background, I was referring to killing it from the task launcher, by pressing the "square" button and "swiping up", actually killing it. – Whitebear Jun 30 '20 at 11:30
  • You don't really understand how this works. Pressing back doesn't "put it in the background". This will end all activities and the OS process hosting the app is killable (unless there is an active `Service`, in which case it is still killable but Android will only do this if it is tight on resources). Swiping the app from the list of "recent tasks" doesn't "kill it" either. It only removes the task from the list of recent tasks. This may (or may not) cause Android to kill the OS process hosting the app, depending on Android version and manufacturer-specific behaviour. – David Wasser Jun 30 '20 at 18:46
  • Well, I do understand how it works, as I implemented all these things in my SDK. the app when it goes to the background it still has less chance of being killed compared to when the task is removed (Take any device, "go to the background", by minimizing your app, and see that even after a few minutes it will still be there) That's for sure. When the task is removed, the OS might not kill it instantly, but it does kill it relatively quickly, and I do rely on that. (You can also here take for example several test devices and try it on them and see the Application onDestroy is called) – Whitebear Jul 02 '20 at 10:22
  • Sorry, still sounds to me like your initialization code should go in an `Activity` and not in the `Application` class. It sounds to me like need something to happen whenever the user is interacting with your `Activity` and has nothing to do with the OS Process instantiation (which is what `Application.onCreate()` is for). – David Wasser Jul 02 '20 at 11:15
  • Well, with activity I'd have to be careful as it has its own dangers, such as launch modes that can effect the code I put inside the activity:onCreate. I think that what eventually would help is to combine putting the code in the activity:onCreate and checking the intent that started it to see if that's the correct one... (main launcher for example) – Whitebear Jul 02 '20 at 13:41
0

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.

Lennart Steinke
  • 584
  • 5
  • 11
  • 1
    Well, that's a workaround I've already tried and also a bit problematic, in case of different launch Modes of your activity. the right thing to do in this case would be to check the intent that opens your app, hook into the activities lifecycles, and see that the first created activity is defined as "launcher" and "main" and there do the initialization... – Whitebear Jul 02 '20 at 10:26
  • Is it an option to store the results of your init code, and load it when needed (say in onEnterForeground())? – Lennart Steinke Jul 02 '20 at 11:32
  • That's a lot of overhead the loading each time for background and foreground. But what I described can achieve the goal! – Whitebear Jul 02 '20 at 13:36
  • Oh sorry, I was not suggesting to load it very time. I'd use a flag to load it when needed. – Lennart Steinke Jul 02 '20 at 14:11
0

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

Agi Maulana
  • 497
  • 1
  • 6
  • 16
  • If I have an application with one activity that goes to bg and fg repeatedly, will the call to my initialization code be called once? – Whitebear Jul 02 '20 at 10:31
  • No, if you placed the initialization in `onEnterForeground()` it will be called multiple times, as much as entering foreground mode – Agi Maulana Jul 03 '20 at 04:03
-2

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

Phantom Lord
  • 572
  • 3
  • 13