Continue Service even if application is cleared from Recent app

AndroidAndroid Service

Android Problem Overview


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?

Android Solutions


Solution 1 - Android

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));

Solution 2 - Android

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); 
}

Solution 3 - Android

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.

Solution 4 - Android

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)
        }
    }

}

Solution 5 - Android

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://stackoverflow.com/a/22464640/4232337

Solution 6 - Android

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);
	}

}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionMysticMagicϡView Question on Stackoverflow
Solution 1 - AndroidAvnish ChoudharyView Answer on Stackoverflow
Solution 2 - AndroidSelim RazaView Answer on Stackoverflow
Solution 3 - AndroidNima GView Answer on Stackoverflow
Solution 4 - AndroidAllan VelosoView Answer on Stackoverflow
Solution 5 - AndroidNSimonView Answer on Stackoverflow
Solution 6 - Androidnawaab saabView Answer on Stackoverflow