user2078872
user2078872

Reputation: 1609

Android: keep Service running when app is killed

I want to keep a IntentService running in background even when the app is killed. And by "killed" I mean press home-button for a long time -> see all running apps -> swipe my app aside -> app killed OR press back-button for a long time -> app killed

My code goes as follows. In my MainActivity:

Intent intent = new Intent(this, MyService.class);
this.startService(intent);

In my MyService:

public class MyService extends IntentService {

@Override
protected void onHandleIntent(Intent intent) {
    System.out.println("MyService started");
    run();
}

private void run() {
    while (true){
        System.out.println("MyService still running");
        doSomething();
        waitSomeTime();
    }
}

}

I see that the service is running when the app is open. It's still running when I minimize the app via home-button. It's still running when I close the app via back-button. But it will stop if I kill it as mentioned above. How do I solve this?

Upvotes: 128

Views: 225666

Answers (9)

Manisha Saini
Manisha Saini

Reputation: 61

Here is how i am triggering my alarms when the app is in background or even killed using foreground services:

  // Aware user about the foreground service
   private fun setForeGroundNotification() {
    val intent = Intent(this, AlarmForegroundService::class.java)
 
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        startForegroundService(intent)
    } else {
        startService(intent)
    }
}


 

public class AlarmForegroundService extends Service {
@Nullable
@Override
public IBinder onBind(Intent intent) {
    return null;
}

@Override
public void onCreate() {
    super.onCreate();
    final String CHANNELID = "Foreground Service ID";
    NotificationChannel channel;
    if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
        channel = new NotificationChannel(
                CHANNELID,
                CHANNELID,
                NotificationManager.IMPORTANCE_LOW
        );

        getSystemService(NotificationManager.class).createNotificationChannel(channel);
        Notification.Builder notification = new Notification.Builder(this, CHANNELID)
                .setContentTitle("App is running in background to check alarm")
                .setContentText("Checking Alarm..")
                .setAutoCancel(false)
                .setSmallIcon(R.mipmap.logo);
        startForeground(1, notification.build());
    }
}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    int hours = intent.getIntExtra("hours", 0);
    int minutes = intent.getIntExtra("minutes", 0);

    //Firing alarm at selected wake up time
    Calendar calendar = Calendar.getInstance();
    calendar.set(Calendar.HOUR_OF_DAY, hours);
    calendar.set(Calendar.MINUTE, minutes);

    //To prevent alarm trigger for past time.
    if (calendar.before(Calendar.getInstance())) {
        calendar.add(Calendar.DATE, 1);
    }

    Intent intent1 = new Intent(this, AlarmReceiver.class);
    PendingIntent pendingIntent = PendingIntent.getBroadcast(
            this,
            0,
            intent1,
            PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
    );

    AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
    alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(), (1000 * 60 * 60 * 24), pendingIntent);
    return super.onStartCommand(intent, flags, startId);
}

}

class AlarmReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
    val intent = Intent(context, AlarmIntentService::class.java)
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        context!!.startForegroundService(intent)
    } else {
        context!!.startService(intent)
    }
}

}

public class AlarmIntentService extends Service {
@Nullable
@Override
public IBinder onBind(Intent intent) {
    return null;
}

@Override
public void onCreate() {
    super.onCreate();
    NotificationChannel channel = null;
    int notificationId = 123;
    long[] vibPattern = {1000, 1000, 1000, 1000};

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        channel = new NotificationChannel("channel2",
                "Sleep Alarm",
                NotificationManager.IMPORTANCE_HIGH);
    }

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        channel.setVibrationPattern(vibPattern);
    }

    NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        manager.createNotificationChannel(channel);
    }

    //Creating the notification object
    NotificationCompat.Builder notification = new NotificationCompat.Builder(this, "channel2");
    notification.setSmallIcon(R.drawable.ic_bell);
    notification.setContentTitle("Wake up Alarm!!");
    notification.setContentText("It's time to get up");
    notification.setAutoCancel(true);
    notification.setSound(null);
    //notification.build().flags |= Notification.FLAG_AUTO_CANCEL;

    //Action button handling
    Intent onDismiss = new Intent(this, DismissReceiver.class);
    onDismiss.putExtra("NOTIFICATION_ID", notificationId);
    Intent onSnooze = new Intent(this, SnoozeReceiver.class);


    SharedPreferences sh = getSharedPreferences("MySharedPref", Context.MODE_PRIVATE);
    int snooze = sh.getInt("snooze", 0);

    PendingIntent dismissPendingIntent = PendingIntent.getBroadcast(this, 1, onDismiss, PendingIntent.FLAG_IMMUTABLE);
    if (snooze == 0) {
        notification.setContentIntent(dismissPendingIntent);
        notification.addAction(R.drawable.ic_cross, "Dismiss", dismissPendingIntent);
    } else {
        PendingIntent snoozePendingIntent = PendingIntent.getBroadcast(this, 1, onSnooze, PendingIntent.FLAG_IMMUTABLE);
        notification.setContentIntent(dismissPendingIntent);
        notification.setContentIntent(snoozePendingIntent);
        notification.addAction(R.drawable.ic_cross, "Dismiss", dismissPendingIntent);
        notification.addAction(R.drawable.ic_cross, "Snooze", snoozePendingIntent);
    }

    //To trigger the chosen alarm ringtone
    Intent startIntent = new Intent(this, RingtonePlayingService.class);
    this.startService(startIntent);

    manager.notify(notificationId, notification.build());
    startForeground(notificationId, notification.build());
}

}

Upvotes: 0

Apple Yellow
Apple Yellow

Reputation: 307

You try below code:

public class HeartbeartService extends Service {

private static final int SERVICE_NOTIFICATION_ID = 54321;
private static final String CHANNEL_ID = "Notification service";
private final LocalBinder mBinder = new LocalBinder();

Handler handler = new Handler();
private Runnable runnableCode = new Runnable() {
    @Override
    public void run() {
        Context context = getApplicationContext();

        Intent myIntent = new Intent(context, HeartbeatEventService.class);
        context.startService(myIntent);
        HeadlessJsTaskService.acquireWakeLockNow(context);
        handler.postDelayed(this, 2000);
        
    }
};

public class LocalBinder extends Binder {
    public HeartbeartService getService() {
        return HeartbeartService.this;
    }
}

private void createNotificationChannel() {
    // Create the NotificationChannel, but only on API 26+ because
    // the NotificationChannel class is new and not in the support library
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        int importance = NotificationManager.IMPORTANCE_MIN;
        NotificationChannel channel = new NotificationChannel(CHANNEL_ID, "Notification service", importance);
        channel.setDescription("CHANEL DESCRIPTION");
        NotificationManager notificationManager = getSystemService(NotificationManager.class);
        notificationManager.createNotificationChannel(channel);
    }
}





@Override
public void onCreate() {
    super.onCreate();
   
}

@Override
public void onDestroy() {
    super.onDestroy();
    this.handler.removeCallbacks(this.runnableCode);
   
}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    this.handler.post(this.runnableCode);
     createNotificationChannel();
     Intent notificationIntent = new Intent(this, MainActivity.class);
     PendingIntent contentIntent = PendingIntent.getActivity(this, 0, notificationIntent,
             PendingIntent.FLAG_CANCEL_CURRENT);
     Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
             .setContentTitle("Notification service")
             .setContentText("Running...")
             .setBadgeIconType(0)
             .setAutoCancel(false)
             .setOngoing(true)
             .build();

    startForeground(1, notification);
  
    return START_STICKY;
}

}

Upvotes: 0

waseem
waseem

Reputation: 166

use onTaskRemoved in your service class

 @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(AlarmManager.ELAPSED_REALTIME, elapsedRealtime() + 500,
                restartServicePendingIntent);
        Log.d("taskremoved", "task removed ");
        super.onTaskRemoved(rootIntent);
    }

Upvotes: 2

Sayan Sil
Sayan Sil

Reputation: 6095

All the answers seem correct so I'll go ahead and give a complete answer here.

Firstly, the easiest way to do what you are trying to do is launch a Broadcast in Android when the app is killed manually, and define a custom BroadcastReceiver to trigger a service restart following that.

Now lets jump into code.


Create your Service in YourService.java

Note the onCreate() method, where we are starting a foreground service differently for Build versions greater than Android Oreo. This because of the strict notification policies introduced recently where we have to define our own notification channel to display them correctly.

The this.sendBroadcast(broadcastIntent); in the onDestroy() method is the statement which asynchronously sends a broadcast with the action name "restartservice". We'll be using this later as a trigger to restart our service.

Here we have defined a simple Timer task, which prints a counter value every 1 second in the Log while incrementing itself every time it prints.

public class YourService extends Service {
public int counter=0;

    @Override
    public void onCreate() {
        super.onCreate();
        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.O)
            startMyOwnForeground();
        else
            startForeground(1, new Notification());
    }

    @RequiresApi(Build.VERSION_CODES.O)
    private void startMyOwnForeground()
    {
        String NOTIFICATION_CHANNEL_ID = "example.permanence";
        String channelName = "Background Service";
        NotificationChannel chan = new NotificationChannel(NOTIFICATION_CHANNEL_ID, channelName, NotificationManager.IMPORTANCE_NONE);
        chan.setLightColor(Color.BLUE);
        chan.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
        
        NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
        assert manager != null;
        manager.createNotificationChannel(chan);

        NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID);
        Notification notification = notificationBuilder.setOngoing(true)
                .setContentTitle("App is running in background")
                .setPriority(NotificationManager.IMPORTANCE_MIN)
                .setCategory(Notification.CATEGORY_SERVICE)
                .build();
        startForeground(2, notification);
    }


    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        super.onStartCommand(intent, flags, startId);
        startTimer();
        return START_STICKY;
    }


    @Override
    public void onDestroy() {
        super.onDestroy();
        stoptimertask();

        Intent broadcastIntent = new Intent();
        broadcastIntent.setAction("restartservice");
        broadcastIntent.setClass(this, Restarter.class);
        this.sendBroadcast(broadcastIntent);
    }



    private Timer timer;
    private TimerTask timerTask;
    public void startTimer() {
        timer = new Timer();
        timerTask = new TimerTask() {
            public void run() {
                Log.i("Count", "=========  "+ (counter++));
            }
        };
        timer.schedule(timerTask, 1000, 1000); //
    }

    public void stoptimertask() {
        if (timer != null) {
            timer.cancel();
            timer = null;
        }
    }

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

Create a Broadcast Receiver to respond to your custom defined broadcasts in Restarter.java

The broadcast with the action name "restartservice" which you just defined in YourService.java is now supposed to trigger a method which will restart your service. This is done using BroadcastReceiver in Android.

We override the built-in onRecieve() method in BroadcastReceiver to add the statement which will restart the service. The startService() will not work as intended in and above Android Oreo 8.1, as strict background policies will soon terminate the service after restart once the app is killed. Therefore we use the startForegroundService() for higher versions and show a continuous notification to keep the service running.

public class Restarter extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        Log.i("Broadcast Listened", "Service tried to stop");
        Toast.makeText(context, "Service restarted", Toast.LENGTH_SHORT).show();

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            context.startForegroundService(new Intent(context, YourService.class));
        } else {
            context.startService(new Intent(context, YourService.class));
        }
    }
}

Define your MainActivity.java to call the service on app start.

Here we define a separate isMyServiceRunning() method to check the current status of the background service. If the service is not running, we start it by using startService().

Since the app is already running in foreground, we need not launch the service as a foreground service to prevent itself from being terminated.

Note that in onDestroy() we are dedicatedly calling stopService(), so that our overridden method gets invoked. If this was not done, then the service would have ended automatically after app is killed without invoking our modified onDestroy() method in YourService.java

public class MainActivity extends AppCompatActivity {
    Intent mServiceIntent;
    private YourService mYourService;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mYourService = new YourService();
        mServiceIntent = new Intent(this, mYourService.getClass());
        if (!isMyServiceRunning(mYourService.getClass())) {
            startService(mServiceIntent);
        }
    }

    private boolean isMyServiceRunning(Class<?> serviceClass) {
        ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
        for (ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
            if (serviceClass.getName().equals(service.service.getClassName())) {
                Log.i ("Service status", "Running");
                return true;
            }
        }
        Log.i ("Service status", "Not running");
        return false;
    }


    @Override
    protected void onDestroy() {
        //stopService(mServiceIntent);
        Intent broadcastIntent = new Intent();
        broadcastIntent.setAction("restartservice");
        broadcastIntent.setClass(this, Restarter.class);
        this.sendBroadcast(broadcastIntent);
        super.onDestroy();
    }
}

Finally register them in your AndroidManifest.xml

All of the above three classes need to be separately registered in AndroidManifest.xml.

Note that we define an intent-filter with the action name as "restartservice" where the Restarter.java is registered as a receiver. This ensures that our custom BroadcastReciever is called whenever the system encounters a broadcast with the given action name.

<application
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:supportsRtl="true"
    android:theme="@style/AppTheme">

    <receiver
        android:name="Restarter"
        android:enabled="true"
        android:exported="true">
        <intent-filter>
            <action android:name="restartservice" />
        </intent-filter>
    </receiver>

    <activity android:name="MainActivity">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>

    <service
        android:name="YourService"
        android:enabled="true" >
    </service>
</application>

This should now restart your service again if the app was killed from the task-manager. This service will keep on running in background as long as the user doesn't Force Stop the app from Application Settings.

UPDATE: Kudos to Dr.jacky for pointing it out. The above mentioned way will only work if the onDestroy() of the service is called, which might not be the case certain times, which I was unaware of. Thanks.

Upvotes: 129

Iman Marashi
Iman Marashi

Reputation: 5753

In your service, add the following code.

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

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

    super.onTaskRemoved(rootIntent);
 }

Upvotes: 20

Melbourne Lopes
Melbourne Lopes

Reputation: 4855

You can use android:stopWithTask="false"in manifest as bellow, This means even if user kills app by removing it from tasklist, your service won't stop.

 <service android:name=".service.StickyService"
                  android:stopWithTask="false"/>

Upvotes: 1

Vishal Maral
Vishal Maral

Reputation: 1318

If your Service is started by your app then actually your service is running on main process. so when app is killed service will also be stopped. So what you can do is, send broadcast from onTaskRemoved method of your service as follows:

 Intent intent = new Intent("com.android.ServiceStopped");
 sendBroadcast(intent);

and have an broadcast receiver which will again start a service. I have tried it. service restarts from all type of kills.

Upvotes: 56

Rishit Shah
Rishit Shah

Reputation: 375

The reason for this is that you are trying to use an IntentService. Here is the line from the API Docs

The IntentService does the following:

Stops the service after all start requests have been handled, so you never have to call stopSelf().

Thus if you want your service to run indefinitely i suggest you extend the Service class instead. However this does not guarantee your service will run indefinitely. Your service will still have a chance of being killed by the kernel in a state of low memory if it is low priority.So you have two options:
1)Keep it running in the foreground by calling the startForeground() method.
2)Restart the service if it gets killed. Here is a part of the example from the docs where they talk about restarting the service after it is killed

 public int onStartCommand(Intent intent, int flags, int startId) {
      Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();

      // For each start request, send a message to start a job and deliver the 
      // start ID so we know which request we're stopping when we finish the job 
      Message msg = mServiceHandler.obtainMessage();
      msg.arg1 = startId;
      mServiceHandler.sendMessage(msg);

      // If we get killed, after returning from here, restart 
      return START_STICKY;
  }  

Upvotes: 6

kiturk3
kiturk3

Reputation: 558

inside onstart command put START_STICKY... This service won't kill unless it is doing too much task and kernel wants to kill it for it...

@Override
        public int onStartCommand(Intent intent, int flags, int startId) {
            Log.i("LocalService", "Received start id " + startId + ": " + intent);
            // We want this service to continue running until it is explicitly
            // stopped, so return sticky.
            return START_STICKY;
        }

Upvotes: 7

Related Questions