Android O - Старый сервис запуска переднего плана все еще работает?

Итак, в 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)

Итак, для воспроизведения:

  1. Сконфигурируйте gradle приложения, как упомянуто в предыдущей ссылке
  2. Открыть приложение
  3. Запросить обновление местоположения
  4. Закрыть приложение (с помощью кнопки "назад" или "домой")

Сервис работает на переднем плане (показывается значком в тени уведомлений). Обновления местоположения проходят, как и ожидалось (каждые 10 секунд), даже на устройстве под управлением O. Что мне здесь не хватает?

7 ответов

Это сработало для меня.

  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. Теперь в классе Service в onStartCommand() сделайте следующее
@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 вам нужно будет указать идентификатор канала, который является новой функцией в Android Oreo.

Надеюсь, что это работает!

В Activity (или в любом контексте, запускающем службу переднего плана), вызовите это:

ContextCompat.startForegroundService(context, serviceClass);

Когда служба запустится, создайте канал уведомлений, используя код, аналогичный тому, что написано в документации Android, а затем создайте сборку и используйте ее:

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

Обычно вы запускаете службу с приемника вещания, используя startService, Они говорят, что больше невозможно (или надежно) позвонить startService потому что теперь есть фоновые ограничения, поэтому вам нужно позвонить startServiceInForeground вместо. Однако из документов не совсем понятно, когда это происходит, потому что приложение попадает в белый список при получении намерения вещания, поэтому неясно, когда именно startService бросает IllegalStateException,

Устаревший способ запуска службы переднего плана все еще работает, когда приложение находится на переднем плане, но рекомендуемый способ запуска службы переднего плана для приложений, ориентированных на API уровня 26/Android O, состоит в том, чтобы использовать недавно представленный метод NotificationManager # startServiceInForeground для создания службы переднего плана. на первом месте.

Старый способ запуска службы в фоновом режиме и последующего ее продвижения на передний план не будет работать, если приложение находится в фоновом режиме, из-за ограничений фонового выполнения Android O.

Процесс миграции и этапы описаны здесь. https://developer.android.com/preview/features/background.html

Как и @Kislingk упоминается в комментарии NotificationManager.startServiceInForeground был удален. Он был помечен как устаревший с коммитом 08992ac.

Из сообщения фиксации:

Вместо того, чтобы требовать предоставления априорного уведомления для запуска службы непосредственно в состояние переднего плана, мы применяем двухэтапную составную операцию для выполнения текущей работы службы даже из фонового состояния выполнения. Контекст #startForegroundService() не подлежит фоновым ограничениям, с требованием, чтобы сервис официально входил в состояние переднего плана через startForeground() в течение 5 секунд. Если служба не делает этого, она останавливается операционной системой, и приложение обвиняется в ANR службы.

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

}

Я добавляю образец, если нужно, с помощью 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())

startForeground(1, уведомление); будет работать на Android O, но согласно требованию Android O мы должны показывать постоянное уведомление пользователю. В то же время в некоторых случаях это может сбить пользователя с толку (системное уведомление о запуске приложения в фоновом режиме и воздействии на батарею), поэтому пользователь может удалить приложение. Поэтому лучше всего использовать недавно введенный класс WorkManager для планирования задачи в качестве переднего плана.

  1. Создайте свой рабочий класс (скажем, MyWorker), расширив класс "Рабочий", где вы можете выполнить долгосрочную задачу. Переопределите метод ниже в этом классе:
    • doWork () [обязательно]
    • onStopped () [Необязательно]
    • onWorkFinished [Необязательно] и т. д.
  2. Создайте повторяющиеся / периодические [PeriodicWorkRequest] или неповторяющиеся [OneTimeWorkRequest] работы в соответствии с требованиями.
  3. Получить экземпляр WorkManager и поставить в очередь работу.

Фрагмент кода:

OneTimeWorkRequest work =
     new OneTimeWorkRequest.Builder(MyWorker.class)
 .build();
WorkManager.getInstance().enqueue(work);    
Другие вопросы по тегам