Continue Service even if application is cleared from Recent app
Asked Answered
L

6

57

I am having a little issue.

In my application, a Service is started after user is logged in successfully. Previously, the service needed to stop if application was killed. (say, removed from Recent application list by swiping.) So we had used android:stopWithTask="true". Now we need the Service to run as it is, even if the Task which started it, is removed from Recent app list. So I changed the Service to include android:stopWithTask="false". But that doesn't seem to work.

Related code:

Here is manifest part related to Service:

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

In MyService.java:

public class MyService extends AbstractService {

    @Override
    public void onStartService() {
        Intent intent = new Intent(this, MyActivity.class);
        intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, 0);
        Notification notification = new Notification(R.drawable.ic_launcher, "My network services", System.currentTimeMillis());
        notification.setLatestEventInfo(this, "AppName", "Message", pendingIntent);
        startForeground(MY_NOTIFICATION_ID, notification);  
    }

    @Override
    public void onTaskRemoved(Intent rootIntent) {
        Toast.makeText(getApplicationContext(), "onTaskRemoved called", Toast.LENGTH_LONG).show();
        System.out.println("onTaskRemoved called");
        super.onTaskRemoved(rootIntent);
    }
}

AbstractService.java is custom class that extends Sevrice:

public abstract class AbstractService extends Service {

    protected final String TAG = this.getClass().getName();

    @Override
    public void onCreate() {
        super.onCreate();
        onStartService();
        Log.i(TAG, "onCreate(): Service Started.");
    }

    @Override
    public final int onStartCommand(Intent intent, int flags, int startId) {
        Log.i(TAG, "onStarCommand(): Received id " + startId + ": " + intent);
        return START_STICKY; // run until explicitly stopped.
    }

    @Override
    public final IBinder onBind(Intent intent) {
        return m_messenger.getBinder();
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        onStopService();
        Log.i(TAG, "Service Stopped.");
    }    

    public abstract void onStartService();
    public abstract void onStopService();
    public abstract void onReceiveMessage(Message msg);

    @Override
    public void onTaskRemoved(Intent rootIntent) {
        Toast.makeText(getApplicationContext(), "AS onTaskRemoved called", Toast.LENGTH_LONG).show();
        super.onTaskRemoved(rootIntent);
    }
}

Now if I login in the application, MyService is started. After that I press Home button, so application is moved to background. Now I remove the application from Recent Application's list. At that time, I should see the Toast and console message, as per this method's description:

public void onTaskRemoved (Intent rootIntent)

Added in API level 14

This is called if the service is currently running and the user has removed a task that comes from the service's application. If you have set ServiceInfo.FLAG_STOP_WITH_TASK then you will not receive this callback; instead, the service will simply be stopped.

Parameters rootIntent The original root Intent that was used to launch the task that is being removed.

But I am not seeing any of that. Service is returning START_STICKY in onStartCommand, So I think onTaskRemoved should be fired along with flag android:stopWithTask="false".

Am I missing anything?

Let me know in case I need to add some code which might be important to figure out what's wrong.

P.S.: I tested this on 4.2.2 till now.

P.S.: I just tested the same code in 4.1.2, on which Service keeps running, and I get the message "onTaskRemoved called" in log, too.

What should I do to make this work in all versions?

Lyall answered 10/11, 2014 at 11:23 Comment(5)
Just in case, how are you starting this service? If via bindService(), then the Service is automatically destroyed when the client (e.g. Activity) unbinds, unless you also called startService() explicitly.Anastigmatic
AFAIK, there is only one way to start service again from its onStopService()Insectile
@Anastigmatic working in other versions. May be issue in Karbonn or 4.2.2 Thanks. :)Uppsala
please help me #53706456Elba
thank you, i do fix with your question, got the clue :)Sectarianism
T
36

Just follow these scenarios, your service and processes (Threads run inside your service) will remain continuous.

  1. Create service and use START_STICKY as return value in onStartCommand method like below:

    @Override
    public int onStartCommand(final Intent intent, 
                              final int flags,
                              final int startId) {
    
        //your code
        return START_STICKY;
    }  
    
  2. Above code will Restart the service if destroyed and always remain running but the process(Threads) run from the service will stop working if your app is removed from the recent apps. To ensure that your processes(Threads) remains always in running condition you have to Override onTaskRemoved() method and add code to restart Tasks like below.

    @Override
    public void onTaskRemoved(Intent rootIntent){
        Intent restartServiceTask = new Intent(getApplicationContext(),this.getClass());
        restartServiceTask.setPackage(getPackageName());    
        PendingIntent restartPendingIntent =PendingIntent.getService(getApplicationContext(), 1,restartServiceTask, PendingIntent.FLAG_ONE_SHOT);
        AlarmManager myAlarmService = (AlarmManager) getApplicationContext().getSystemService(Context.ALARM_SERVICE);
        myAlarmService.set(
                AlarmManager.ELAPSED_REALTIME,
                SystemClock.elapsedRealtime() + 1000,
                restartPendingIntent);
    
        super.onTaskRemoved(rootIntent);
    }
    
  3. Start service like below

startService(new Intent(this, YourService.class));

Tannen answered 21/12, 2016 at 7:46 Comment(8)
With respect to answer for 1), developer.android.com/reference/android/app/… Please do ctrl+f and search for following text to get full meaning. " An important consequence of this is that if you implement onStartCommand() to schedule work to be done asynchronously or in another thread, then you may want to use START_FLAG_REDELIVERY to have the system re-deliver an Intent for you " a) Though following is for process lifecycle and using "START_STICKY" is for Service life cycle, now What should we use? b) What is meant by "deliver Intent for you"?Tosh
With your solution of using onTaskRemoved( ), I notice that process and service getting restarted. Which means, the process and service goes into a state where it doesn't do the job for few seconds. But I want the service and process to run 24 x 7 even if user swipes the app to right in recent apps list. How to do that? Please help.Tosh
@user3705478: did you get the solution, then plz share itBrothel
where should I put no. 3 "start service like below" code?Garton
Code: startService(new Intent(this, YourService.class)); Put it anywhere you want to start the service. Mainly we start it from Activity's onCreate() method but we can start it from anywhere in the code where required. @GartonTannen
This won't work for Devices like Huawei and Xiaomi which have a battery saving setting for your app. You must disable this battery optimization manually. Only then it will work.Straub
@Straub : How to disable this on Huawei?Vander
We have to use AlarmManager for this solution?Dupre
K
8

In your service, add the following code. It fine work to me in 4.4.2

Here is a workaround I came across and works well for re-starting a service if its process is killed on closing the application.

 @Override public void onTaskRemoved(Intent rootIntent){
     Intent restartServiceIntent = new Intent(getApplicationContext(), this.getClass());

     PendingIntent restartServicePendingIntent = PendingIntent.getService(
         getApplicationContext(), 1, restartServiceIntent, PendingIntent.FLAG_ONE_SHOT);
     AlarmManager alarmService = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
     alarmService.set(ELAPSED_REALTIME, elapsedRealtime() + 1000,
         restartServicePendingIntent);

     super.onTaskRemoved(rootIntent); 
}
Kortneykoruna answered 29/12, 2015 at 7:55 Comment(6)
When I swipe my app away I do see this onTaskRemoved() callback triggering and I see my service being relaunchedComical
This isn't a solution, your service is still being killed and restarted using this which is not desired most of the time.Grundyism
@Grundyism Yes. It does get restarted which I don't want. Did you get a solution for this scenario?Tosh
The service fails to restart even added in onTaskRemoved() and return START_STICKY when app is swipe removed from recent apps listOracle
please help me #53706456Elba
That's a bad solution, it will stop the service for a second. If the app is a media player the user will feel it stop. No music app work like this, it must be another better solution.Ulrick
G
1

If you bind to your service from a subclass of Application class and hold on to your IBinder connection, the service will remain alive even after the application is removed from the recent apps.

Grundyism answered 29/6, 2015 at 13:27 Comment(6)
Thank you Nima. Can you elaborate more on this? Or even better, some nice example would be great. ThxMannes
@mauron, what you do is extend the Application class, then within that class you Override onCreate() and in there you start your service with startService() and then bind to it. Keep the binder variable alive by not letting it set to null.Comical
Someone tested this solution? I try and nothing.Stutter
That sounds like an unnecessary use of memory by the service. Especially that Android docs say: ~"A started service must manage its own lifecycle."Dupre
not sure what you mean by unnecessary use of memory, how is this any different than binding to a service from an activity? and you don't need to manage the service lifecycle from App subclass, just starting it is done by the subclass.Grundyism
@Grundyism can you give more details? Maybe by adding details and code to your answer?Lycopodium
U
0

If it's okay to put a notification while the service is running, you can use startForegroundService and startForeground to accomplish it.

There is three important tricks:

  1. Call startForegroundService which creates a long running service not limited to the binded context and make a promise to call startForeground later.
  2. Return START_STICKY in onStartComand
  3. Call startForeground with a notification as promised in (1).

For example, if you want to run a TimerService, in your TimerActivity you will do:

private var timerService: TimerService? = null

private val timerServiceConnection = object : ServiceConnection {

    override fun onServiceConnected(className: ComponentName, service: IBinder) {
        val binder = service as TimerService.Binder
        timerService = binder.getService()
    }

    override fun onServiceDisconnected(arg0: ComponentName) {
    }
}

override fun onCreate(savedInstanceState: Bundle?) {
    ...
    startButton.setOnClickListener {
        timerService?.startTimer(60L, 0L)
    }
}

override fun onStart() {
    super.onStart()

    Intent(this, TimerService::class.java).also {
        ContextCompat.startForegroundService(this, it) // that's the first trick
        bindService(it, timerServiceConnection, Context.BIND_AUTO_CREATE)
    }
}

override fun onStop() {
    super.onStop()
    unbindService(timerServiceConnection)
    timerService?.updateNotification(secondsRemaining)
}

Your TimerService will be something like that:

class TimerService : Service() {

    private val binder = Binder()

    private var serviceLooper: Looper? = null

    private var serviceHandler: ServiceHandler? = null

    private var timer: CountDownTimer? = null

    private val notificationUtil by lazy {
        NotificationUtil(this)
    }

    override fun onCreate() {
        HandlerThread("ServiceStartArguments", Process.THREAD_PRIORITY_BACKGROUND).apply {
            start()
            serviceLooper = looper
            serviceHandler = ServiceHandler(looper)
        }
    }

    override fun onBind(intent: Intent?): IBinder? = binder

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        val timerRemaining = intent?.getLongExtra(EXTRA_REMAINING, 0) ?: 0L
        if (timerRemaining != 0L) {
            serviceHandler?.obtainMessage()?.also { msg ->
                msg.arg1 = startId
                msg.data.putLong(EXTRA_REMAINING, timerRemaining)
                serviceHandler?.sendMessage(msg)
            }
        }

        return START_STICKY // that's the second trick
    }

    override fun onDestroy() {
        super.onDestroy()
        timer?.cancel()
    }

    fun startTimer(secondsRemaining: Long, id: Long) {
        updateNotification(secondsRemaining)

        Intent(this, TimerService::class.java).apply {
            putExtra(EXTRA_REMAINING, secondsRemaining)
        }.also {
            onStartCommand(it, 0, id.toInt())
        }
    }

    fun stopTimer() {
        timer?.cancel()
    }

    fun updateNotification(secondsRemaining: Long){
        val notification = NotificationCompat.Builder(this, NotificationUtil.CHANNEL_ID_TIMER)
                .setSmallIcon(R.drawable.ic_timer)
                .setAutoCancel(true)
                .setDefaults(0)
                .setContentTitle(secondsRemaining.formatSeconds())
                .setContentText("Timer")
                .setContentIntent(notificationUtil.getPendingIntentWithStack(this, TimerActivity::class.java))
                .setOngoing(true)
                .build()
        startForeground(NotificationUtil.NOTIFICATION_ID, notification) // that's the last trick
    }

    private fun sendMessage(remaining: Long) {
        Intent(TimerService::class.java.simpleName).apply {
            putExtra(EXTRA_REMAINING, remaining)
        }.also {
            LocalBroadcastManager.getInstance(this).sendBroadcast(it)
        }
    }

    private inner class ServiceHandler(looper: Looper) : Handler(looper) {

        override fun handleMessage(msg: Message) {
            val secondsRemaining = msg.data.getLong(EXTRA_REMAINING)
            notificationUtil.showTimerStarted(secondsRemaining)

            timer = object : CountDownTimer(secondsRemaining * 1000, 1000) {

                override fun onTick(millisUntilFinished: Long) {
                    Log.i(this::class.java.simpleName, "tick ${(millisUntilFinished / 1000L).formatSeconds()}")
                    updateNotification(millisUntilFinished / 1000)
                    sendMessage(millisUntilFinished / 1000)
                }

                override fun onFinish() {
                    Log.i(this::class.java.simpleName, "finish")
                    notificationUtil.showTimerEnded()
                    sendMessage(0)
                    stopSelf()
                }
            }.start()
        }
    }

    inner class Binder : android.os.Binder() {
        // Return this instance of LocalService so clients can call public methods
        fun getService(): TimerService = this@TimerService
    }

    companion object {

        const val EXTRA_REMAINING = "EXTRA_REMAINING"
        const val NOTIFICATION_ID = 1 // cannot be 0

        fun Long.formatSeconds(): String {
            val s = this % 60
            val m = this / 60 % 60
            val h = this / (60 * 60) % 24
            return if (h > 0) String.format("%d:%02d:%02d", h, m, s)
            else String.format("%02d:%02d", m, s)
        }
    }

}
Ulrick answered 17/9, 2019 at 1:43 Comment(0)
A
-2

It appears that swipping an application out of the 'recent tasks' kills everything attached.

Maybe you should have a look over there to find a way to relaunch your service if it stops : https://mcmap.net/q/131508/-background-service-stopped-when-activity-killed-or-stop-or-force-close

Acetanilide answered 10/11, 2014 at 11:34 Comment(1)
That is not true for foreground Services. They will remain intact after swiping from recents...Dupre
S
-4

Write the 5 lines that I added in oncreate() of service class

Like this:

public class AlarmService extends Service {

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        Intent iHeartBeatService = new Intent(AlarmService.this,
                AlarmService.class);
        PendingIntent piHeartBeatService = PendingIntent.getService(this, 0,
                iHeartBeatService, PendingIntent.FLAG_UPDATE_CURRENT);
        AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
        alarmManager.cancel(piHeartBeatService);
        alarmManager.setRepeating(AlarmManager.RTC_WAKEUP,
                System.currentTimeMillis(), 1000, piHeartBeatService);

    }
}

or

try this one

public class MyService extends Service{


    @Override
    public IBinder onBind(Intent intent) {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public void onCreate() {
        // TODO Auto-generated method stub
        super.onCreate();
        System.out.println("service created");
    }

    @SuppressLint("NewApi")
    @Override
    public void onTaskRemoved(Intent rootIntent) {
        // TODO Auto-generated method stub
        System.out.println("onTaskRemoved");
        super.onTaskRemoved(rootIntent);

    }

    @Override
    @Deprecated
    public void onStart(Intent intent, int startId) {
        // TODO Auto-generated method stub
        super.onStart(intent, startId);
        System.out.println("Service started");
        new Handler().postDelayed(new Runnable() {

            @Override
            public void run() {
                // TODO Auto-generated method stub
                System.out.println("Service is running");
            }
        }, 5000);
    }

}
Scarcely answered 10/11, 2014 at 11:33 Comment(6)
Please provide some sort of description for ease to understandSchmidt
AlarmManager get cleared when application gets killed from anywhere. So this is not a solution.Exobiology
@PankajKumar i personally used this n its working fine, try it once n then tellScarcely
@UnityBeginner: If I know that you are using it in an app, I would never install it. Because you are waking up my device on each second and you are the reason why my battery is being drained like hell.Insectile
agreed that it consumes battery so much but it won't let the service stopScarcely
please help me #53706456Elba

© 2022 - 2024 — McMap. All rights reserved.