Android 8.0: java.lang.IllegalStateException: запрещено запускать службу

360

При запуске приложения приложение запускает службу, которая должна выполнять некоторые сетевые задачи. После нацеливания на уровень API 26 моему приложению не удается запустить службу на Android 8.0 в фоновом режиме.

Вызвано: java.lang.IllegalStateException: не разрешено запускать службу. Намерение {cmp = my.app.tt / com.my.service}: приложение находится в фоновом режиме uid UidRecord {90372b1 u0a136 CEM бездействует: 1 сек (0,0 , 0)}

насколько я понимаю, это связано с: Фоновые пределы выполнения

Метод startService () теперь генерирует исключение IllegalStateException, если приложение, ориентированное на Android 8.0, пытается использовать этот метод в ситуации, когда ему не разрешено создавать фоновые службы.

« в ситуации, когда это не разрешено » - что это на самом деле значит ?? И как это исправить. Я не хочу устанавливать свой сервис как "передний план"

phnmnn
источник
4
Это означает, что вы не можете запустить службу, когда ваше приложение находится в фоновом режиме
Тим
22
это не имеет ничего общего с разрешениями во время выполнения
Тим
11
Используйте startForegroundService()вместо startService().
frogatto
2
Вы можете попробовать использовать targetSdkVersion 25, но скомпилировать с compileSdkVersion 26. Таким образом, вы можете использовать новые классы из Android 8 и новейшую библиотеку поддержки, но ваше приложение не будет ограничено фоновыми ограничениями выполнения.
Качпер Дзюбек
2
@KacperDziubek Это должно сработать, но это временное решение, так как оно должно быть нацелено на SDK26 осенью 2018 года.
RightHandedMonkey

Ответы:

194

Разрешенные ситуации - это временный белый список, в котором фоновая служба работает так же, как и до Android O.

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

  • Обработка высокоприоритетного сообщения Firebase Cloud Messaging (FCM).
  • Получение трансляции, такой как SMS / MMS-сообщение.
  • Выполнение PendingIntent из уведомления.
  • Запуск VpnService до того, как приложение VPN продвигает себя на передний план.

Источник: https://developer.android.com/about/versions/oreo/background.html

Другими словами, если ваша фоновая служба не соответствует требованиям белого списка, вы должны использовать новый JobScheduler . По сути, это то же самое, что и фоновая служба, но она вызывается периодически, а не работает в фоновом режиме.

Если вы используете IntentService, вы можете перейти на JobIntentService. Смотрите ответ @ kosev ниже .

Мурат Карагез
источник
Я получаю сбой после того, как хочу запустить сервис сразу после того, как получаю сообщение GCM "high" prio. Я все еще использую GCM: «com.google.android.gms: play-services-gcm: 11.4.2», а не «com.google.firebase: firebase-messaging: 11.4.2». Не уверен, что это имеет значение, хотя ..
Алекс Радзишевский
«По сути, это то же самое, что и фоновая служба, но она вызывается периодически, а не работает в фоновом режиме». - не уверен, что вы подразумеваете под этим, так как сервисы Android никогда не выполнялись непрерывно. Они запускаются, бегут, затем выключаются.
Melllvar
2
Является ли метод FirebaseInstanceIdService и его onTokenRefreshметод высокоприоритетным сообщением FCM?
Шнур Рен
@phnmnn нет, GCMTaskService на самом деле не следует FCM, следовательно, они не работают.
Абхинав Упадхьяй,
4
Разве вы не должны использовать WorkManager (здесь: developer.android.com/topic/libraries/architecture/workmanager ) вместо JobScheduler или других? Я имею в виду это: youtu.be/IrKoBFLwTN0
разработчик Android
257

Я получил решение. Для устройств до 8.0 вы должны просто использовать startService(), но для устройств после 7.0, вы должны использовать startForgroundService(). Вот пример кода для запуска сервиса.

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        context.startForegroundService(new Intent(context, ServedService.class));
    } else {
        context.startService(new Intent(context, ServedService.class));
    }

А в классе обслуживания, пожалуйста, добавьте код ниже для уведомления:

@Override
public void onCreate() {
    super.onCreate();
    startForeground(1,new Notification());
}

Где O - версия для Android 26.

Сагар Кача
источник
9
Служба переднего плана - это то, о чем пользователь должен знать, и для этого требуется уведомление. Это также будет ANR, если он работает слишком долго. Так что это не совсем подходящий ответ, если приложение уже работает в фоновом режиме.
SimonH
80
Есть ContextCompat.startForegroundService(...)библиотека поддержки, которую можно использовать вместо.
Jayeffkay
37
Это не решение.
JacksOnF1re
17
Я также согласен, что это не решение. Это обходной путь, и это помогает, но фоновые ограничения в Oreo были введены по причине. Обход этих ограничений таким образом определенно не является правильным подходом (даже если он работает). Лучший способ - использовать JobScheduler (см. Принятый ответ).
Вратислав Джиндра
6
Я не думаю, что это будет хорошим опытом для пользователя, если вы должны показать пустое уведомление переднего плана. Учитывая тот факт, что ты должен. Android 8.0 представляет новый метод startForegroundService () для запуска нового сервиса на переднем плане. После того, как система создала сервис, у приложения есть пять секунд, чтобы вызвать метод startForeground () сервиса, чтобы показать видимое пользователю уведомление новой службы. Если приложение не вызывает startForeground () в течение определенного периода времени, система останавливает службу и объявляет приложение ANR.
heeleeaz
85

Лучший способ - использовать JobIntentService, который использует новый JobScheduler для Oreo или старые сервисы, если они недоступны.

Объявите в своем манифесте:

<service android:name=".YourService"
         android:permission="android.permission.BIND_JOB_SERVICE"/>

И в вашем сервисе вы должны заменить onHandleIntent на onHandleWork:

public class YourService extends JobIntentService {

    public static final int JOB_ID = 1;

    public static void enqueueWork(Context context, Intent work) {
        enqueueWork(context, YourService.class, JOB_ID, work);
    }

    @Override
    protected void onHandleWork(@NonNull Intent intent) {
        // your code
    }

}

Затем вы начинаете свой сервис с:

YourService.enqueueWork(context, new Intent());
Kosev
источник
Как вы можете вызывать нестатический метод внутри статического метода? Не могли бы вы объяснить?
Мэдди,
@Maddy также enqueueWork(...)является статическим методом.
hgoebl
2
Где бы вы позвонили YourService.enqueueWork (контекст, новый Intent ()); ? Из трансляции ресивер?
TheLearner
Я не верю, что это самое простое решение. Смотрите мой комментарий ниже о WorkManager. Он использует JobIntentService, когда это уместно, но у него гораздо меньше котла.
СКАЗКА
36

Если служба работает в фоновом потоке за счет расширения IntentService, вы можете заменить IntentServiceс JobIntentServiceкоторой осуществляется в рамках Android библиотеки поддержки

Преимущество использования JobIntentServiceзаключается в том, что он ведет себя как IntentServiceна устройствах pre-O, а на O и выше он отправляет его как задание.

JobSchedulerможет также использоваться для периодических / по требованию рабочих мест. Но необходимо обеспечить обратную совместимость, так как JobSchedulerAPI доступен только из API 21

Харини С
источник
1
Проблема с JobIntentService заключается в том, что Android может планировать вашу работу довольно произвольно, и его нельзя запустить неявно, не повозившись, в отличие от IntentService. См stackoverflow.com/questions/52479262/...
kilokahn
15

В Oreo Android определены ограничения на фоновые сервисы .

Чтобы улучшить взаимодействие с пользователем, Android 8.0 (уровень API 26) накладывает ограничения на то, что приложения могут делать в фоновом режиме.

Тем не менее, если вам нужен всегда запущенный сервис, вы можете использовать приоритетный сервис.

Ограничения фоновых сервисов. Пока приложение не используется, существуют ограничения на использование фоновых сервисов. Это не относится к приоритетным службам, которые более заметны для пользователя.

Таким образом, вы можете сделать передний план обслуживания . Вам нужно будет показать уведомление пользователю, когда ваша служба работает. Смотрите этот ответ (Есть много других)

Решение, если -

Вы не хотите получать уведомления для вашего сервиса?

Вы можете выполнять периодическое задание, 1. оно запускает ваш сервис, 2. сервис выполнит свою работу и 3. останавливается сам. При этом ваше приложение не будет считаться разрядкой аккумулятора.

Вы можете использовать периодическую задачу с сигнализации диспетчера , планировщика заданий , Evernote-Работа или Работа менеджером .

  • Работа менеджера - лучшее решение для периодических задач. Который был представлен с компонентом архитектуры Android .
  • В отличие от Job-Scheduler (только> 21 API) он будет работать для всех версий.
  • Также он начинает работать после режима ожидания .
  • Создайте загрузчик Android для планирования службы после загрузки устройства.

Я тестировал вечно работающий сервис с Work-Manager.

Khemraj
источник
WorkManager кажется наилучшим способом, при условии, что работа не должна выполняться немедленно. Он обратно совместим с API 14, использует JobScheduler на устройствах с API 23+ и комбинацию BroadcastReceiver + AlarmManager на устройствах с API 14-22
Джеймс Аллен
Ключевым моментом в WorkManager является то, что WorkManager предназначен для задач, которые могут быть отложены - то есть не требуются для немедленного запуска
touhid udoy
13

Да, это потому, что вы больше не можете запускать службы в фоновом режиме через API 26. Таким образом, вы можете запустить ForegroundService выше API 26.

Вам придется использовать

ContextCompat.startForegroundService(...)

и опубликовать уведомление при обработке утечки.

PK
источник
1
ОП специально сказал, что не хочет на переднем плане. Это должно быть указано как комментарий или как часть более полного ответа.
Рикардо А.
7

Как сказал @kosev в своем ответе, вы можете использовать JobIntentService. Но я использую альтернативное решение - я ловлю IllegalStateException и запускаю службу в качестве переднего плана. Например, эта функция запускает мой сервис:

@JvmStatic
protected fun startService(intentAction: String, serviceType: Class<*>, intentExtraSetup: (Intent) -> Unit) {
    val context = App.context
    val intent = Intent(context, serviceType)
    intent.action = intentAction
    intentExtraSetup(intent)
    intent.putExtra(NEED_FOREGROUND_KEY, false)

    try {
        context.startService(intent)
    }
    catch (ex: IllegalStateException) {
        intent.putExtra(NEED_FOREGROUND_KEY, true)
        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            context.startForegroundService(intent)
        }
        else {
            context.startService(intent)
        }
    }
}

и когда я обрабатываю Intent, я делаю следующее:

override fun onHandleIntent(intent: Intent?) {
    val needToMoveToForeground = intent?.getBooleanExtra(NEED_FOREGROUND_KEY, false) ?: false
    if(needToMoveToForeground) {
        val notification = notificationService.createSyncServiceNotification()
        startForeground(notification.second, notification.first)

        isInForeground = true
    }

    intent?.let {
        getTask(it)?.process()
    }
}
Алекс Шевелев
источник
Мне нравится ваша попытка поймать решение. Для меня это решение, потому что иногда context.startServiceработает в фоновом режиме - иногда нет - это выглядит как единственный лучший способ, в противном случае вам нужно реализовать больше кода в своем основном классе extending Applicationи implementing ActivityLifecycleCallbacksотслеживать, находится ли приложение на переднем плане или в фоне, и начинать свои намерения. соответственно.
Пьер
Может ли это исключение быть поймано?
thecr0w
5

В примечаниях к выпуску Firebase говорится , что поддержка Android O была впервые выпущена в 10.2.1 (хотя я бы рекомендовал использовать самую последнюю версию).

пожалуйста, добавьте новые зависимости от сообщений Firebase для Android O

compile 'com.google.firebase:firebase-messaging:11.6.2'

обновить службы Google Play и Google репозитории, если это необходимо.

Давал Дживани
источник
Это не отвечает на вопрос, и вопрос не имеет никакого отношения к базе огня. Это должно быть помещено как комментарий.
Рикардо А.
5

Если какое-либо намерение ранее работало нормально, когда приложение находится в фоновом режиме, это не будет иметь место больше для Android 8 и выше. Имеется в виду только намерение, которое должно выполнять некоторую обработку, когда приложение находится в фоновом режиме.

Следующие шаги должны быть выполнены:

  1. Вышеупомянутое намерение следует использовать JobIntentServiceвместо IntentService.
  2. Класс, который расширяет, JobIntentServiceдолжен реализовывать onHandleWork(@NonNull Intent intent)метод - и должен иметь метод ниже, который вызовет onHandleWorkметод:

    public static void enqueueWork(Context context, Intent work) {
        enqueueWork(context, xyz.class, 123, work);
    }
  3. Позвоните enqueueWork(Context, intent)из класса, где определены ваши намерения.

    Образец кода:

    Public class A {
    ...
    ...
        Intent intent = new Intent(Context, B.class);
        //startService(intent); 
        B.enqueueWork(Context, intent);
    }

Приведенный ниже класс ранее расширял класс Service

Public Class B extends JobIntentService{
...

    public static void enqueueWork(Context context, Intent work) {
        enqueueWork(context, B.class, JobId, work);
    }

    protected void onHandleWork(@NonNull Intent intent) {
        ...
        ...
    }
}
  1. com.android.support:support-compatнужен для JobIntentService- я использую 26.1.0 V.

  2. Самое главное, чтобы версия библиотек Firebase была, по крайней мере 10.2.1, у меня были проблемы 10.2.0- если у вас есть какие-либо!

  3. Ваш манифест должен иметь приведенное ниже разрешение для класса обслуживания:

    service android:name=".B"
    android:exported="false"
    android:permission="android.permission.BIND_JOB_SERVICE"

Надеюсь это поможет.

Читраю Чайтанья
источник
4

Я вижу много ответов, которые рекомендуют просто использовать ForegroundService. Чтобы использовать ForegroundService, должно быть уведомление, связанное с ним. Пользователи увидят это уведомление. В зависимости от ситуации они могут раздражать ваше приложение и удалять его.

Самое простое решение - использовать новый компонент архитектуры под названием WorkManager. Вы можете проверить документацию здесь: https://developer.android.com/topic/libraries/architecture/workmanager/

Вы просто определяете свой рабочий класс, который расширяет Worker.

public class CompressWorker extends Worker {

    public CompressWorker(
        @NonNull Context context,
        @NonNull WorkerParameters params) {
        super(context, params);
    }

    @Override
    public Worker.Result doWork() {

        // Do the work here--in this case, compress the stored images.
        // In this example no parameters are passed; the task is
        // assumed to be "compress the whole library."
        myCompress();

        // Indicate success or failure with your return value:
        return Result.SUCCESS;

        // (Returning RETRY tells WorkManager to try this task again
        // later; FAILURE says not to try again.)
    }
}

Затем вы планируете, когда вы хотите запустить его.

    OneTimeWorkRequest compressionWork = 
        new OneTimeWorkRequest.Builder(CompressWorker.class)
            .build();
    WorkManager.getInstance().enqueue(compressionWork);

Легко! Существует множество способов настройки рабочих. Он поддерживает повторяющиеся задания, и вы можете даже делать сложные вещи, такие как цепочка, если вам это нужно. Надеюсь это поможет.

СКАЗКА
источник
3
В настоящее время WorkManager все еще альфа.
pzulw
3
05 марта 2019 г. - стабильная версия WorkManager 1.0.0.
phnmnn
следует использовать WorkManager вместо interservice или JobIntentService
sivaBE35
1
WorkManager is intended for tasks that are deferrable—that is, not required to run immediately... это может быть проще, но моему приложению нужен фоновый сервис, который немедленно выполняет запросы пользователей!
Кто-то где-то
Если вам требуется, чтобы задача была выполнена немедленно, вам следует воспользоваться услугой переднего плана. Пользователь увидит уведомление и узнает, что вы делаете работу. Проверьте документы, если вам нужна помощь, чтобы решить, что использовать. У них есть довольно хорошее руководство для фоновой обработки. developer.android.com/guide/background
СКАЗКА
4

Альтернативное решение с помощью JobScheduler позволяет запускать службу в фоновом режиме через регулярные промежутки времени.

Сначала создайте класс с именем Util.java

import android.app.job.JobInfo;
import android.app.job.JobScheduler;
import android.content.ComponentName;
import android.content.Context;

public class Util {
// schedule the start of the service every 10 - 30 seconds
public static void schedulerJob(Context context) {
    ComponentName serviceComponent = new ComponentName(context,TestJobService.class);
    JobInfo.Builder builder = new JobInfo.Builder(0,serviceComponent);
    builder.setMinimumLatency(1*1000);    // wait at least
    builder.setOverrideDeadline(3*1000);  //delay time
    builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED);  // require unmetered network
    builder.setRequiresCharging(false);  // we don't care if the device is charging or not
    builder.setRequiresDeviceIdle(true); // device should be idle
    System.out.println("(scheduler Job");

    JobScheduler jobScheduler = null;
    if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
        jobScheduler = context.getSystemService(JobScheduler.class);
    }
    jobScheduler.schedule(builder.build());
   }
  }

Затем сделайте класс JobService с именем TestJobService.java

import android.app.job.JobParameters;
import android.app.job.JobService;
import android.widget.Toast;

  /**
   * JobService to be scheduled by the JobScheduler.
   * start another service
   */ 
public class TestJobService extends JobService {
@Override
public boolean onStartJob(JobParameters params) {
    Util.schedulerJob(getApplicationContext()); // reschedule the job
    Toast.makeText(this, "Bg Service", Toast.LENGTH_SHORT).show();
    return true;
}

@Override
public boolean onStopJob(JobParameters params) {
    return true;
  }
 }

После этого класс приемника BroadCast с именем ServiceReceiver.java

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;

 public class ServiceReceiver extends BroadcastReceiver {
 @Override
public void onReceive(Context context, Intent intent) {
    Util.schedulerJob(context);
 }
}

Обновите файл манифеста с кодом класса обслуживания и получателя.

<receiver android:name=".ServiceReceiver" >
        <intent-filter>
            <action android:name="android.intent.action.BOOT_COMPLETED" />
        </intent-filter>
    </receiver>
    <service
        android:name=".TestJobService"
        android:label="Word service"
        android:permission="android.permission.BIND_JOB_SERVICE" >

    </service>

Оставьте средство запуска main_intent в файле mainActivity.java, который создается по умолчанию, и изменения в файле MainActivity.java

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

public class MainActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    Util.schedulerJob(getApplicationContext());
  }
 }

WOOAAH !! Фоновая служба запускается без службы Foreground

Anshul1507
источник
2

Если вы запускаете свой код на 8.0, то приложение будет аварийно завершать работу. Так что запускайте сервис на переднем плане. Если ниже 8.0 используйте это:

Intent serviceIntent = new Intent(context, RingtonePlayingService.class);
context.startService(serviceIntent);

Если выше или 8.0, то используйте это:

Intent serviceIntent = new Intent(context, RingtonePlayingService.class);
ContextCompat.startForegroundService(context, serviceIntent );
iamfaizuddin
источник
Рекомендуется использовать службы Foreground только в тех случаях, когда пользователь должен знать, что служба запущена. Типичный пример для воспроизведения музыки в фоновом режиме. Есть и другие случаи, которые имеют смысл, но вы не должны просто конвертировать все свои сервисы в сервисы Foreground. Подумайте о том, чтобы преобразовать свои службы в WorkManager из архитектурных компонентов Google, когда вам просто нужно поработать в фоновом режиме и быть уверенным, что он будет работать.
СКАЗ
В противном случае startForegroundService требует разрешения java.lang.SecurityException: Permission Denial: startForeground from pid=13708, uid=10087 requires android.permission.FOREGROUND_SERVICE. Исправить на stackoverflow.com/a/52382711/550471
Кто-то где-то
1

если у вас есть встроенное push-уведомление, то

Добавьте новые / обновите зависимости обмена сообщениями Firebase для Android O (Android 8.0), из-за ограничений фонового выполнения .

compile 'com.google.firebase:firebase-messaging:11.4.0'

обновить службы Google Play и Google репозитории, если это необходимо.

Обновить:

 compile 'com.google.firebase:firebase-messaging:11.4.2'
Самир Мангролия
источник
0

Используйте startForegroundService()вместо startService() и не забудьте создать startForeground(1,new Notification());в своем сервисе в течение 5 секунд с момента запуска сервиса.

Arpit
источник
2
Кажется, что новая Notificaction () не работает с Android 8.1; Вы должны создать канал для уведомления: stackoverflow.com/a/47533338/1048087
Prizoff
0

Из-за неоднозначного голосования по этому ответу (+ 4 / -4 по состоянию на это редактирование), ПОЖАЛУЙСТА, СМОТРИТЕ НА ДРУГИЕ ОТВЕТЫ И ПЕРВЫЙ ИСПОЛЬЗУЙТЕ ЭТО ТОЛЬКО В ПОСЛЕДНЕМ КУРОРТЕ . Я использовал это только один раз для сетевого приложения, работающего от имени пользователя root, и я согласен с общим мнением, что это решение не следует использовать в обычных условиях.

Оригинальный ответ ниже:

Все остальные ответы верны, но я хотел бы отметить, что еще один способ обойти это - попросить пользователя отключить оптимизацию батареи для вашего приложения (обычно это не очень хорошая идея, если ваше приложение не связано с системой). Посмотрите этот ответ, чтобы узнать, как запросить отказ от оптимизации батареи без блокировки приложения в Google Play.

Вам также следует проверить, не отключены ли в вашем приемнике оптимизации батареи, чтобы предотвратить сбои, с помощью:

if (Build.VERSION.SDK_INT < 26 || getSystemService<PowerManager>()
        ?.isIgnoringBatteryOptimizations(packageName) != false) {
    startService(Intent(context, MyService::class.java))
} // else calling startService will result in crash
Боже мой
источник
1
Попросить своих пользователей дать вам бесплатный пропуск, используя как можно больше батареи, не очень хорошее решение. Рассмотрите возможность преобразования вашего кода в более дружественное решение. Ваши пользователи будут вам благодарны.
СКАЗ
5
@ TALE Не каждый фоновый сервис может быть сделан с использованием батареи JobSchedulerи прочее. Некоторые из приложений должны работать на более низком уровне, чем типичные приложения синхронизации. Это альтернативное решение, когда это не работает.
Mygod
-17

не используйте в onStartCommand:

return START_NOT_STICKY

просто измените его на:

return START_STICKY

и это будет работать

Омар Отман
источник