【问题标题】:Android O - Old start foreground service still working?Android O - 旧的启动前台服务仍在工作?
【发布时间】:2017-09-01 06:26:41
【问题描述】:

因此,对于 Android O,如果您希望每小时接收的不仅仅是几个位置更新,您需要让您的服务作为前台服务运行。

我注意到启动前台服务的旧方法似乎确实适用于 O。 即

startForeground(NOTIFICATION_ID, getNotification());

根据此处的行为更改指南: https://developer.android.com/preview/behavior-changes.html

NotificationManager.startServiceInForeground() 方法启动前台服务。启动前台服务的旧方法不再有效。

虽然新方法仅在针对 O 时有效,但旧方法似乎仍然适用于 O 设备,无论是否针对 O。

编辑 包括例子:

Google 示例项目 LocationUpdatesForegroundService 实际上有一个工作示例,您可以在其中直接看到问题。 https://github.com/googlesamples/android-play-location/tree/master/LocationUpdatesForegroundService

startForeground 方法似乎可以正常工作,无论是针对 API 级别 25 进行定位和编译,还是针对 O 进行定位和编译(如下所示:https://developer.android.com/preview/migration.html#uya

所以,要重现:

  1. 按照上一个链接中的说明配置应用 gradle
  2. 打开应用
  3. 请求位置更新
  4. 关闭应用程序(通过返回按钮或主页按钮)

服务正在前台运行(由通知阴影中的图标显示)。即使在运行 O 的设备上,位置更新也会按预期进行(每 10 秒一次)。我在这里缺少什么?

【问题讨论】:

  • 我对@9​​87654324@ 的解释是,使用startServiceinForeground() 的风险较小,以防您处于后台并且即将失去启动后台服务的能力。
  • 从阅读developer.android.com/preview/behavior-changes.html#abll 部分可以看出,在前台启动服务在 Android O 中根本不起作用(至少在定位它时)。尽管文档中有这句话,但我很惊讶地看到它仍然有效。那么,如果一个应用保持目标 SDK
  • 最好没问题,否则很多应用会崩溃。
  • 是的,我就是这么想的。但也有人认为,这可能是 Google 采取的一种“将它们从水中吹走”的方法,以防止用户的电池被执行后台操作和耗尽电池的应用程序破坏。
  • 认为他们自 ODP1 以来更改了 API。不过要小心。他们似乎没有更新所有文档。 developer.android.com/about/versions/oreo/…

标签: android location android-8.0-oreo


【解决方案1】:

这对我有用。

  1. 在 Activity 类中,使用 startForegroundService() 而不是 startService() 启动服务
    Intent myService = new Intent(this, MyService.class);

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        startForegroundService(myService);
    } else {
        startService(myService);
    }
  1. 现在在 onStartCommand() 的 Service 类中执行以下操作
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    ......
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {

        Notification.Builder builder = new Notification.Builder(this, ANDROID_CHANNEL_ID)
                .setContentTitle(getString(R.string.app_name))
                .setContentText(text)
                .setAutoCancel(true);

        Notification notification = builder.build();
        startForeground(1, notification);

    } else {

        NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
                .setContentTitle(getString(R.string.app_name))
                .setContentText(text)
                .setPriority(NotificationCompat.PRIORITY_DEFAULT)
                .setAutoCancel(true);

        Notification notification = builder.build();

        startForeground(1, notification);
    }
    return START_NOT_STICKY;
}

注意:使用 Notification.Builder 而不是 NotificationCompat.Builder 可以正常工作。只有在 Notification.Builder 中,您才需要提供 Channel ID,这是 Android Oreo 的新功能。

希望它有效!

编辑:

如果您的目标 API 级别为 28 或更高,则需要 FOREGROUND_SERVICE 权限,否则您的应用会崩溃。

只需将其添加到 AndroidManifest.xml 文件中即可。

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

【讨论】:

  • 你只需要根据medium.com/google-developers/…将NotificationCompat从v7改为v4就可以得到带有ChannelId参数的方法(import android.support.v4.app.NotificationCompat;)
  • “无法解析此方法”当我使用 startForegroundService() 时出现此错误
  • 您还需要创建频道 (developer.android.com/reference/android/app/…) 才能使其正常工作。
  • ContextCompat.startForegroundService(Context, Intent) 是这里应该使用的。它的实现与上面第 1 项中指出的类似。
  • 记得将&lt;uses-permission android:name="android.permission.FOREGROUND_SERVICE" /&gt; 也添加到您的清单中。上面的示例在我的情况下不起作用,但添加权限有所帮助。
【解决方案2】:

在 Activity(或任何启动前台服务的上下文)中,调用这个:

Intent intent = new Intent(this, MyService.class)
ContextCompat.startForegroundService(context, intent);

服务启动后,使用与Android docs say类似的代码创建一个通知通道,然后创建一个构建器并使用它:

final Builder builder = new NotificationCompat.Builder(context, CHANNEL_ID).setSmallIcon(...)//
            .setPriority(...).setCategory(...).setContentTitle(...).setContentText(...).setTicker(...);
// and maybe other preparations to the notification...
startForeground(notificationId, builder.build());

【讨论】:

  • 感谢,为 ContextCompat.startForegroundService +1
  • @the_new_mr 这应该被标记为正确答案,因为它允许 Google 将来进行“幕后”更改。
【解决方案3】:

通常您使用startService 从广播接收器启动您的服务。他们说不再可能(或可靠)调用startService,因为现在有背景限制,所以你需要调用startServiceInForeground。但是从文档中并不清楚它何时发生,因为应用程序在收到广播意图时被列入白名单,因此不清楚startService 何时抛出IllegalStateException

【讨论】:

  • 是的。我同意这绝对不清楚,并感谢您的回答。问题是,当我尝试启动一项服务并将其置于前台时,无论是否针对 O,它似乎仍然有效。这就是让我想知道我是否遗漏了什么的原因。我将更新我的问题以包含一个示例(来自 Google 自己的代码示例)。
  • 恕我直言,API 仍然有效,但他们正在讨论当您无法从接收器启动服务并且您的应用处于“缓存状态”时的特殊情况。
  • 我明白你的意思。我们只需要澄清这个问题。更令人困惑的是,Google 代码示例的工作方式是在 O 设备上编写的。
  • @greywolf82 “因为应用程序在收到广播意图时被列入白名单,”对于任何类型的广播都是如此吗?因为根据文档,它仅适用于用户可见的内容。来自developer.android.com/preview/features/background.html#services -> “应用程序在处理用户可见的任务时被列入白名单,例如:处理高优先级 Firebase 云消息 (FCM) 消息。接收广播,例如 SMS/ MMS 消息。从通知中执行 PendingIntent。"
  • 我发现 BOOT_COMPLETED 和 MY_PACKAGE_REPLACED 广播没有将应用程序放入白名单。我打开了一个错误,要求记录将应用程序放入白名单的广播:issuetracker.google.com/issues/62821226.
【解决方案4】:

当应用程序在前台时,启动前台服务的传统方式仍然有效,但针对 API 级别 26/Android O 的应用程序启动前台服务的推荐方法是使用新引入的 NotificationManager#startServiceInForeground 方法来创建首先是前台服务。

由于Android O的后台执行限制,如果应用程序处于后台模式,则在后台启动服务然后将其提升到前台的旧方式将不起作用。

此处记录了迁移过程和步骤。 https://developer.android.com/preview/features/background.html#migration

【讨论】:

  • 通知管理器。 startServiceInForeground 已被删除。 context.startForegroundService 而不是它。
  • 如果服务已经启动并在前台运行,startService 会在后台工作吗?我用它来向我的服务发送信息
【解决方案5】:

@Kislingk 在评论中提到的 NotificationManager.startServiceInForeground 也被删除了。它被标记为已弃用,带有commit 08992ac

来自提交消息:

而不是要求提供先验通知以便 启动一个服务直接进入前台状态,我们采用两阶段 用于进行持续服务工作的复合操作,即使从 后台执行状态。 Context#startForegroundService() 不是 受背景限制,并要求 服务通过 startForeground() 正式进入前台状态 5秒。如果服务不这样做,它会被操作系统停止并 该应用因服务 ANR 而受到指责。

【讨论】:

【解决方案6】:

如果需要使用 backstack builder,我会添加示例

    val notifyManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
    val playIntent    = Intent(this, this::class.java).setAction(PAUSE)
    val cancelIntent  = Intent(this, this::class.java).setAction(EXIT)

    val stop          = PendingIntent.getService(this, 1, playIntent, PendingIntent.FLAG_UPDATE_CURRENT)
    val exit          = PendingIntent.getService(this, 2, cancelIntent, PendingIntent.FLAG_UPDATE_CURRENT)

    val builder = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        notifyManager.createNotificationChannel(NotificationChannel(NOTIFICATION_ID_CHANNEL_ID, getString(R.string.app_name), NotificationManager.IMPORTANCE_HIGH))
        NotificationCompat.Builder(this, NOTIFICATION_ID_CHANNEL_ID)
    } else
        NotificationCompat.Builder(this)

    builder.apply {
        setContentTitle(station.name)
        setContentText(metaToText(meta) )
        setSmallIcon(R.drawable.ic_play_arrow_white_24px)
        setAutoCancel(false)
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) priority = Notification.PRIORITY_MAX
        addAction(R.drawable.ic_stop_white_24px, getString(R.string.player_notification_stop), stop)
        addAction(R.drawable.ic_close_white_24px, getString(R.string.player_notification_exit), exit)
    }

    val stackBuilder = TaskStackBuilder.create(this)
    stackBuilder.addParentStack(PlayerActivity::class.java)
    stackBuilder.addNextIntent(Intent(this, PlayerActivity::class.java))
    builder.setContentIntent(stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT))

    startForeground(NOTIFICATION_ID, builder.build())

【讨论】:

    【解决方案7】:

    startForeground(1, 通知);将适用于 Android O,但根据 Android O 要求,我们必须向用户显示持久通知。同时在某些情况下可能会混淆用户(关于应用程序在后台运行并影响电池的系统通知),因此用户可能会卸载应用程序。 所以最好是使用新引入的 WorkManager 类将任务安排为前台。

    1. 通过扩展“Worker”类来创建您的工作者类(比如 MyWorker),您可以在其中执行长时间运行的任务。在此类中覆盖以下方法:
      • doWork() [必需]
      • onStopped() [可选]
      • onWorkFinished [可选] 等
    2. 根据要求创建重复/周期性 [PeriodicWorkRequest] 或非重复 [OneTimeWorkRequest] 工作。
    3. 获取 WorkManager 的实例并将工作排队。

    代码sn-p:

    OneTimeWorkRequest work =
         new OneTimeWorkRequest.Builder(MyWorker.class)
     .build();
    WorkManager.getInstance().enqueue(work);    
    

    【讨论】:

      【解决方案8】:

      在android O中,android有后台限制,所以我们必须管理或调用startForegroundService(service)方法而不是startSetvice()

      在清单中添加权限

          <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
      

      //我们像这样启动服务

       if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
              var service = Intent(context, AnyService::class.java)
              context?.startForegroundService(service)
          } else {
              var service = Intent(context, AnyService::class.java)
              context?.startService(service)
          }
      

      在 AnyService 类中

      class AnyService : Service() {
      
      override fun onBind(intent: Intent?): IBinder? {
      
      
      }
      
      override fun onCreate() {
      
          if (Build.VERSION.SDK_INT > Build.VERSION_CODES.O)
              startMyOwnForeground()
          else
              startForeground(1, Notification())
      
      }
      
      
      override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
      
      
          return START_STICKY
      
      }
      
      override fun onDestroy() {
          super.onDestroy()
      }
      
      
      @RequiresApi(Build.VERSION_CODES.O)
      private fun startMyOwnForeground() {
          val NOTIFICATION_CHANNEL_ID = "example.permanence"
          val channelName = "Background Service"
          val chan = NotificationChannel(NOTIFICATION_CHANNEL_ID, channelName, NotificationManager.IMPORTANCE_NONE)
          chan.lightColor = Color.BLUE
          chan.lockscreenVisibility = Notification.VISIBILITY_PRIVATE
      
          val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
          manager.createNotificationChannel(chan)
      
          val notificationBuilder = NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID)
          val notification = notificationBuilder.setOngoing(true)
              .setContentTitle("App is running in background")
              .setPriority(NotificationManager.IMPORTANCE_MIN)
              .setCategory(Notification.CATEGORY_SERVICE)
              .build()
          startForeground(2, notification)
      }
      

      }

      【讨论】:

      • 所以对于那些正在寻找Java实现而不是Kotlin的人,只需将val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager更改为NotificationManager manager = getSystemService(NotificationManager.class);
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-04-07
      • 1970-01-01
      • 2021-06-01
      • 2020-08-31
      • 2015-01-28
      • 1970-01-01
      • 2022-01-19
      相关资源
      最近更新 更多