Continually Running Background Service

2020-01-27 03:08发布

问题:

I'm targeting sdk version 27 with a minimum version of 19 and trying to get a service that runs continuously in the background. I tried different service start options but it still got killed with the app. I tried using a BroadcastReceiver to start the service when it got killed but that gave me an error saying that the app was in the background and couldn't start a service so I tried using the JobScheduler and that gave me the same error. How is this supposed to be done? For example, if I were making a pedometer app, how could I keep that running in the background?

回答1:

In oreo release Android defined limits to background services.

To improve the user experience, Android 8.0 (API level 26) imposes limitations on what apps can do while running in the background.

Still if app need to run its service always, then we can create foreground service.

Background Service Limitations: While an app is idle, there are limits to its use of background services. This does not apply to foreground services, which are more noticeable to the user.

So create a foreground service. In which you will put a notification for user while your service is running. See this answer (There are many others)

Now what if you don't want a notification for your service. A solution is for that.

You can create some periodic task that will start your service, service will do its work and stops itself. By this your app will not be considered battery draining.

You can create periodic task with Alarm Manager, Job Scheduler, Evernote-Jobs or Work Manager.

  • Instead of telling pros & cons of each one. I just tell you best. Work manager is best solution for periodic tasks. Which was introduced with Android Architecture Component.
  • Unlike Job-Scheduler(only >21 API) it will work for all versions.
  • Also it starts work after a Doze-Standby mode.
  • Make a Android Boot Receiver for scheduling service after device boot.

I created forever running service with Work-Manager, that is working perfectly.



回答2:

Since Android 8.0 many background service limitations have been introduced.

Two solutions:

  1. if you need to get total control of task and execution timing, you have to choose Foreground Service. Pros: your app will be considered to be alive, then is more unlikely that the os will kill it to free resources. Cons: your user will always see the Foreground Notification.

  2. if you need to schedule periodically task, then Work Manager (introduced in Google I/O 18) is the best solution. This component choose the best possible scheduler (Jobscheduler, JobDispatcher, AlarmManager..). Keep in mind that work manager APIs are useful only for the tasks that require guaranteed execution and they are deferrable. Ref: Android Dev Documentation



回答3:

The only solution I would suggest is using Firebase Cloud Messages. Or foreground services.



回答4:

as you say:

I tried using a BroadcastReceiver to start the service when it got killed but that gave me an error saying that the app was in the background and couldn't start a service

in Oreo when you are in background and you want to start a service that service must be a foreground service use this code:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            context.startForegroundService(intent);
} else {
            context.startService(intent);
}

if you use this code in Oreo you have a few seconds in onStartCommand to start foreground otherwise your service considered as not responding and may be force close by user (in Android 8 or above)

There is no need to use BroadcastReceiver to start service after it is closed it is enough to just return START_STICKY or START_REDELIVER_INTENT from onStartCommand of your service to restart service after it is closed



回答5:

Using BroadcastReciever we can run backgrouund service continuously, but if it will get killed , destroy automatically re-instance the old service instance When service stops forcefully it will call onDestroy() method, in this case use one receiver and send one broadcast when ever service destroy and restart service again. in thee following method com.android.app is custom action of reciever class which extends BroadcastReciever

public void onDestroy() {
    try {
        myTimer.cancel();
        timerTask.cancel();
    } catch (Exception e) {
        e.printStackTrace();
    }
    Intent intent = new Intent("com.android.app");
    intent.putExtra("valueone", "tostoreagain");
    sendBroadcast(intent);
}

and in onReceive Method

 @Override
public void onReceive(Context context, Intent intent) {
    Log.i("Service Stoped", "call service again");
    context.startService(new Intent(context, ServiceCheckWork.class));
}

In case device is restarted then we have onBootCompleted action for receiver to catch

When you are targeting SdkVersion "O"

In MainActivity.java define getPendingIntent()

private PendingIntent getPendingIntent() {
  Intent intent = new Intent(this, YourBroadcastReceiver.class);
 intent.setAction(YourBroadcastReceiver.ACTION_PROCESS_UPDATES);
 return PendingIntent.getBroadcast(this, 0, intent, 
  PendingIntent.FLAG_UPDATE_CURRENT);
 }

here we use PendingIntent with BroadcastReceiver and This BroadcastReceiver has already been defined in AndroidManifest.xml. Now in YourBroadcastReceiver.java class which contains an onReceive() method:

 Override
public void onReceive(Context context, Intent intent) {
if (intent != null) {
   final String action = intent.getAction();
   if (ACTION_PROCESS_UPDATES.equals(action)) {
       NotificationResult result = NotificationResult.extractResult(intent);
       if (result != null) {
           List<Notification> notifications = result.getNotification();
           NotificationResultHelper notificationResultHelper = new 
   NotificationResultHelper(
                   context, notifications);
           // Save the notification data to SharedPreferences.
           notificationResultHelper.saveResults();
           // Show notification with the notification data.
           notificationResultHelper.showNotification();
           Log.i(TAG, 
NotificationResultHelper.getSavedNotificationResult(context));
       }
   }
 }
}


回答6:

A working hack for this is to simply start a foreground service which is only visible for the fraction of a second and starts your background service. In the background service you'd then periodically start the foreground service.
Before I give an example you should really ask yourself if this is the way to go for you, there might be other solutions to given problems (like using JobIntentService etc.); and keep in mind that this is a hack, it might be patched some time around and I'd generally not use it (I tested it with screen off and battery saving enabled though and it stayed alive the whole time - but this might prevent your device from dozing.. again, this is a dirty hack!)

Example:

public class TemporaryForegroundService extends Service {
public static final int NOTIFICATION_ID = 666;
private static Notification notification;

@Override
public void onCreate() {
    super.onCreate();
    if(notification == null)
        notification = new NotificationCompat.Builder(this, NotificationChannels.importantChannel(this)).
                setSmallIcon(R.mipmap.ic_launcher).setContentTitle("The unseen blade").setContentText("If you see me, congrats to you.").build();
    startForeground(NOTIFICATION_ID, notification);
    startService(new Intent(this, PermanentBackgroundService.class));
    stopForeground(true);
    stopSelf();
}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    return START_NOT_STICKY;
}

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



public class PermanentBackgroundService extends Service {
    private Runnable keepAliveRunnable = new Runnable() {
        @Override
        public void run() {
            keepServiceAlive();
            if(handler != null) handler.postDelayed(this, 15*1000);
        }
    };
    private Handler handler;

    public void onCreate(){
        handler = new Handler();
        handler.postDelayed(keepAliveRunnable, 30* 1000);
    }

    public void onDestroy() {
        super.onDestroy();
        keepServiceAlive();
    }

    private void keepServiceAlive() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            startForegroundService(new Intent(PermanentBackgroundService.this, TemporaryForegroundService .class));
        } else {
            startService(new Intent(PermanentBackgroundService.this, TemporaryForegroundService .class));
        }
    }
}