【问题标题】:Continually Running Background Service持续运行后台服务
【发布时间】:2018-07-11 15:27:21
【问题描述】:

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

【问题讨论】:

标签: android service android-8.0-oreo


【解决方案1】:

在 oreo 版本的 Android 中定义了limits to background services

为了改善用户体验,Android 8.0(API 级别 26)强加了 限制应用在后台运行时可以执行的操作。

如果应用程序需要一直运行它的服务,那么我们可以创建前台服务。

后台服务限制:当应用处于空闲状态时,存在限制 对其使用后台服务。这不适用于前台 服务,对用户来说更明显。

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

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

您可以创建一些周期性任务来启动您的服务,服务将完成其工作并自行停止。这样,您的应用将不会被视为耗电。

您可以使用Alarm ManagerJob SchedulerEvernote-JobsWork Manager 创建定期任务。

  • 而不是告诉每个人的利弊。我只是告诉你最好的。 工作经理是周期性任务的最佳解决方案。这是用Android Architecture Component 介绍的。
  • 与 Job-Scheduler(仅 >21 API)不同,它适用于所有版本。
  • 它还在Doze-Standby mode 之后开始工作。
  • 创建一个Android Boot Receiver 用于在设备启动后安排服务。

我使用 Work-Manager 创建了永远运行的服务,它运行良好。

【讨论】:

  • 工作经理没有使用中文roms oreo 版本,如一加、redmi 等使用的oxygen os、miui 等。
  • @JerinAMathews 感谢分享信息,我会检查一下,如果需要,会在 google dev 中发布问题。
  • 如果有变化请告诉我
  • Google 还表示:默认情况下,这些限制仅适用于面向 Android 8.0(API 级别 26)或更高版本的应用。但是,用户可以从“设置”屏幕为任何应用启用大部分这些限制,即使应用的目标 API 级别低于 26。
  • @Khemraj 您能否发布此用例的示例代码?我正在寻找类似的解决方案,我尝试了所有方法,但该应用程序不断杀死后台服务。请任何有解决方案的人都可以发布它。
【解决方案2】:

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

两种解决方案:

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

  2. 如果您需要定期安排任务,那么工作管理器(在 Google I/O 18 中引入)是最佳解决方案。该组件选择最好的调度程序(Jobscheduler、JobDispatcher、AlarmManager..)。请记住,工作管理器 API 仅对需要保证执行且可延迟的任务有用。 参考:Android Dev Documentation

【讨论】:

  • WorkManager 只能运行十分钟的作业。
【解决方案3】:

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

【讨论】:

    【解决方案4】:

    使用 BroadcastReciever 我们可以连续运行后台服务,但是如果它会被杀死,会自动销毁重新实例化旧的服务实例 当服务强制停止时,它将调用 onDestroy() 方法,在这种情况下,当服务销毁并再次重新启动服务时,使用一个接收器并发送一个广播。在你下面的方法 com.android.app 是扩展广播接收器的接收器类的自定义操作

    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 中定义。 现在在包含 onReceive() 方法的 YourBroadcastReceiver.java 类中:

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

    【讨论】:

    • 这是我试过的,但在奥利奥及以上的任何东西上都失败了。
    • @CaseyB 我已经更新了我的答案,希望对你有所帮助
    【解决方案5】:

    如你所说:

    我尝试使用 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即可重启服务

    【讨论】:

      【解决方案6】:

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

      【讨论】:

      • 它在 pie 中不起作用。它说:引起:java.lang.IllegalStateException:不允许启动服务 Intent { cmp=com.bhusridevelopers.myapplication/.PermanentService }:应用程序在后台 uid界面
      • 它到底在哪里崩溃了?尚未在 Pie 上进行测试。再说一次,这是一个 hack,而不是你一开始就想做的事情(通常)。
      • 当temporaryservice尝试启动permanentservice时(在temporaryservice的oncreate方法中:startService(new Intent(this, PermanentBackgroundService.class));)
      • 在模拟器上测试过,仍然适用于我。您是否在清单中声明了 FOREGROUND_SERVICE 权限?
      • 是的,它不起作用,但在 pie 中,永久服务每 30 秒被销毁一次
      猜你喜欢
      • 2017-09-19
      • 2021-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-01-16
      • 2013-06-13
      • 2015-02-16
      • 2018-10-31
      • 2019-05-08
      相关资源
      最近更新 更多