持续运行的后台服务

23

我正在针对sdk版本27进行定位,最低版本为19,并尝试获得在后台持续运行的服务。我尝试了不同的服务启动选项,但它仍然会随着应用程序被杀死。我尝试使用BroadcastReceiver在服务被杀死时启动服务,但出现错误,说应用程序在后台运行,无法启动服务,因此我尝试使用JobScheduler,但这也给我同样的错误。这应该如何实现呢?例如,如果我制作一个计步器应用程序,如何使其在后台持续运行?


你有没有查看过这个网址:https://dev59.com/Rmox5IYBdhLWcg3wf0RP? - Sal
我认为你需要将你的应用程序加入白名单,这样安卓系统就不会在Oreo版本中杀掉你的服务。 - Kunj Mehta
6个回答

44
在奥利奥发布后,Android定义了后台服务的限制
为了改善用户体验,Android 8.0 (API级别26)对应用程序在后台运行时可以执行的操作进行了限制。
如果应用程序需要始终运行其服务,则可以创建前台服务。 后台服务限制: 当应用程序处于空闲状态时,其使用后台服务的能力受到限制。这不适用于前台服务,前台服务对用户更加显著。
因此,请创建一个前台服务。在您的服务运行时,您将向用户发出通知请参阅此答案(还有其他答案)。
现在,如果您不想要服务通知,可以采取解决方案。
您可以创建一些定期任务来启动您的服务,服务将执行其工作并停止自身。通过这种方式,您的应用程序将不被视为耗电量大的应用程序。
您可以使用Alarm ManagerJob SchedulerEvernote-JobsWork Manager创建定期任务。
  • 不要告诉每个选项的优缺点,只需告诉你最好的选择是Work Manager,这是用Android Architecture Component引入的周期性任务的最佳解决方案。
  • 与Job-Scheduler(仅适用于> 21 API)不同,它将适用于所有版本。
  • 它还会在Doze-Standby mode后开始工作。
  • 创建一个Android Boot Receiver以在设备启动后安排服务。

我使用Work-Manager创建了永久运行的服务,运行得非常完美。


3
工作管理器无法与使用中文ROM的Oreo版本(如氧OS、MIUI等)一起运行,这些ROM被用于一加手机、红米手机等设备上。 - Jerin A Mathews
1
@JerinAMathews 感谢分享信息,我会检查并在必要时在Google开发者社区发布问题。 - Khemraj Sharma
如果有任何变化,请告诉我。 - Jerin A Mathews
2
@Khemraj,您能否提供一个针对这种用例的示例代码?我已经寻找了很长时间类似的解决方案,尝试了所有方法,但应用程序仍然会终止后台服务。如果有人有解决方案,请发布出来。 - Houssem Chlegou
如何在所有ROM(包括中文)上安排可靠的周期性任务的最佳解决方案是什么? - j3ko
显示剩余6条评论

2
自 Android 8.0 起,许多后台服务限制已被引入。
两种解决方案:
1. 如果您需要完全控制任务和执行时间,请选择前台服务。优点:您的应用程序将被视为处于活动状态,因此操作系统不太可能杀死它以释放资源。缺点:用户将始终看到前台通知。
2. 如果您需要定期安排任务,则 Work Manager(在 Google I/O 18 中引入)是最佳解决方案。该组件选择最佳调度程序(Jobscheduler、JobDispatcher、AlarmManager..)。请记住,Work Manager API 仅适用于需要保证执行并且可推迟的任务。 参考资料:Android 开发文档

WorkManager 只能运行十分钟的作业。 - JohnyTex

1
我唯一建议的解决方案是使用Firebase云消息或前台服务。

1
使用BroadcastReciever我们可以持续运行后台服务,但是如果被杀死,它会自动销毁并重新实例化旧的服务实例。 当服务强制停止时,它将调用onDestroy()方法,在这种情况下使用一个接收器,每当服务销毁时发送一个广播,并重新启动服务。在以下方法中,com.android.app是扩展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);
}

在 onReceive 方法中

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

如果设备重新启动,则我们有onBootCompleted操作来捕获接收器

当您针对SdkVersion“O”时

在MainActivity.java中定义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);
 }

在这里,我们使用带有BroadcastReceiver的PendingIntent,而此BroadcastReceiver已在AndroidManifest.xml中定义。 现在,在YourBroadcastReceiver.java类中,它包含一个onReceive()方法:

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

这是我尝试过的,但在Oreo及以上版本上都失败了。 - CaseyB
@CaseyB 我已经更新了我的答案,希望能对你有所帮助。 - user10041244

0

就像你所说的:

我尝试使用BroadcastReceiver在服务被关闭时启动服务,但是却出现错误,提示应用程序在后台并且无法启动服务

在Oreo版本中,当你处于后台状态并且想要启动一个服务时,该服务必须成为前台服务。使用以下代码:

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

如果您在Oreo中使用此代码,则在onStartCommand中有几秒钟的时间来启动前台,否则您的服务将被视为未响应,并可能被用户强制关闭(在Android 8或更高版本中)。

无需使用BroadcastReceiver在服务关闭后启动服务,只需从您的服务的onStartCommand返回START_STICKYSTART_REDELIVER_INTENT即可在服务关闭后重新启动服务。


0
一个可行的解决方法是启动一个短暂可见的前台服务,然后启动你的后台服务。在后台服务中,你可以定期启动前台服务。
在我给出示例之前,你应该认真考虑是否采用这种方式,因为可能存在其他解决方案(例如使用JobIntentService等)。请记住,这是一种hack方法,它可能会被修补,我通常不建议使用它(尽管我已经测试过屏幕关闭和启用省电模式时仍然保持运行 - 但这可能会防止设备进入待机状态...再次强调,这是一种不正规的hack方法!)

示例:

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

它在Pie上无法工作。它显示:Caused by: java.lang.IllegalStateException: Not allowed to start service Intent { cmp=com.bhusridevelopers.myapplication/.PermanentService }: app is in background uid Ui - Shivam Bhusri
它在哪里崩溃了?我还没有在Pie上测试过。再说一遍,这是一个黑客行为,通常情况下你不会想这样做。 - Ch4t4r
当TemporaryService尝试在onCreate方法中启动PermanentService时(使用startService(new Intent(this, PermanentBackgroundService.class));)。 - Shivam Bhusri
已在模拟器上测试,对我仍有效。您在清单中声明了FOREGROUND_SERVICE权限吗? - Ch4t4r
是的,它能够工作,但在 Pie 版本中,永久服务每30秒就会被销毁。 - Shivam Bhusri

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接