持续 运行 后台服务

Continually Running Background Service

我的目标是 sdk 版本 27,最低版本为 19,并试图获得在后台连续运行的服务。我尝试了不同的服务启动选项,但它仍然被该应用程序杀死。我尝试使用 BroadcastReceiver 在它被杀死时启动服务,但这给了我一个错误,说应用程序在后台并且无法启动服务所以我尝试使用 JobScheduler 并且给了我同样的错误。这应该怎么做?例如,如果我正在制作一个计步器应用程序,我如何在后台保留 运行?

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

在 oreo 版本 Android 中定义 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.

如果应用需要始终运行其服务,那么我们可以创建前台服务。

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.

因此创建一个前台服务。当您的服务 运行ning 时,您将在其中为用户 放置一个 通知。 See this answer(还有很多)

现在,如果您不想收到服务通知怎么办。有一个解决方案。

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.

您可以使用 Alarm Manager, Job Scheduler, Evernote-Jobs or Work Manager 创建周期性任务。

  • 而不是说每一个的优缺点。我只告诉你最好的。 工作管理器是周期性任务的最佳解决方案。这是用 Android Architecture Component 引入的。
  • 与 Job-Scheduler(仅 >21 API)不同,它将适用于所有版本。
  • 它也在 Doze-Standby mode 之后开始工作。
  • 在设备启动后制作Android Boot Receiver调度服务。

我用 Work-Manager 创建了永远的 运行ning 服务,它工作得很好。

自 Android 8.0 以来引入了许多后台服务限制。

两种解决方案:

  1. 如果您需要完全控制任务和执行时间,您必须选择ose 前台服务。 Pros:你的应用程序将被认为是存活的,那么 os 将不太可能杀死它以释放资源。 缺点:您的用户将始终看到前台通知。

  2. 如果你需要定期调度任务,那么Work Manager(在GoogleI/O18中介绍)是最好的解决方案.该组件选择os最好的 possible 调度程序(Jobscheduler、JobDispatcher、AlarmManager..)。请记住,工作管理器 API 仅对需要保证执行且可延迟的任务有用。 参考:Android Dev Documentation

使用 BroadcastReciever 我们可以 运行 backgrouund service continuously,但如果它会被杀死,自动销毁重新实例化旧的服务实例 当服务强行停止时,它将调用 onDestroy() 方法,在这种情况下,使用一个接收器并在服务销毁并再次重新启动服务时发送一个广播。在下面的方法中 com.android.app 是接收者 class 的自定义操作,它扩展了 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);
 }

这里我们将 PendingIntent 与 BroadcastReceiver 一起使用,并且此 BroadcastReceiver 已在 AndroidManifest.xml 中定义。 现在在 YourBroadcastReceiver.java class 中包含一个 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));
       }
   }
 }
}

如你所说:

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

在 Oreo 中,当您处于后台并且想要启动服务时,该服务必须是前台服务,请使用此代码:

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

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

关闭后不需要使用BroadcastReceiver启动服务,只需return START_STICKYSTART_REDELIVER_INTENT from onStartCommand即可您的服务关闭后重新启动服务

一个有效的技巧是简单地启动一个仅在几分之一秒内可见的前台服务,然后启动您的后台服务。在后台服务中,您将定期启动前台服务。
在我举一个例子之前,你真的应该问问自己这是否适合你,对于给定的问题可能还有其他解决方案(比如使用 JobIntentService 等);请记住,这是一个 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));
        }
    }
}