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

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

Вызвано: java.lang.IllegalStateException: не разрешено запускать службу. Intent { cmp=my.app.tt/com.my.service }: приложение находится в фоновом режиме.,0)}

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

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

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

23 ответа

Решение

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

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

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

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

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

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

Я получил решение. Для устройств до 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.

Надеюсь, это решит IllegalArgumentException

Лучший способ - использовать 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());

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

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

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

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

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

ContextCompat.startForegroundService(...)

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

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

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

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

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

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

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

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

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

Вы можете использовать периодические задачи с Диспетчером аварий, Планировщиком заданий, Evernote-Jobs или Диспетчером работ.

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

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

Как сказал @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()
    }
}

Альтернативное решение с помощью 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.

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

Я вижу много ответов, которые рекомендуют просто использовать 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);

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

Если какое-либо намерение ранее работало нормально, когда приложение находится в фоновом режиме, это больше не будет иметь место в 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:

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

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

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

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

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

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

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

Если вы запускаете свой код на 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 );

Если у вас есть встроенное 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'

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

Я думаю, что лучший способ обойти это - проверить версию сборки во время выполнения и, в зависимости от этого, если она меньше, чем Api level 21, продолжать использовать startService(). но если он выше, вы должны использовать планировщик заданий. Я думаю, вы можете использовать менеджер работ, но он все еще находится в стадии бета-тестирования

Вы можете попробовать этот код, чтобы избежать сбоя. Как сказали разработчики Google в трекере.

      private val activityManager by lazy { getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager }

//due to https://issuetracker.google.com/issues/113122354
private fun isInForegroundByImportance(): Boolean {
    val importanceState = activityManager.runningAppProcesses.find {
        it.pid == android.os.Process.myPid()
    }?.importance ?: return false
    return importanceState >= RunningAppProcessInfo.IMPORTANCE_FOREGROUND
}

и использование

      override fun onResume() {
    super.onResume()
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O || isInForegroundByImportance()) {
        val intent = Intent(this, BluetoothScannerService::class.java)
        this.startService(intent)
    }
}

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

Я очень недоволен ответами здесь. Что, если служба переднего плана или WorkManager не подходят для варианта использования? Я пришел к решению, в котором я использую область процесса и не включаю исключение отмены области действия в логику ведения журнала. Вот так:

      with(ProcessLifecycleOwner.get()) {
  lifecycleScope.launch {
    lifecycle.repeatOnLifecycle(Lifecycle.State.RESUMED) {
      try {
        context.startService(context, Service::class.java)
      } catch (ex: CancellationException) {
        // app minimized, scope cancelled, do not log as error
      } catch (ex: IllegalStateException) {
        logToFirebase(ex)
      }
    }
  }
}

Подробнее в этой статье https://medium.com/@lepicekmichal/android-background-service-without-hiccup-501e4479110f

У меня тоже была эта пробема

добавил эту библиотеку

implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.0.0'

и переустановил приложение, решило это для меня

- Первый метод вызова в приложении запуска или основной деятельности:

public static void createChancelNotification(Context mContext) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        NotificationManager notificationManager =
                (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
        NotificationChannel androidChannel = new NotificationChannel(Constants.ANDROID_CHANNEL_ID,
                Constants.ANDROID_CHANNEL_NAME, NotificationManager.IMPORTANCE_MAX);
        notificationManager.createNotificationChannel(androidChannel);
    }
}

- Второе, когда закрываются приложения и Firebase Push:

public class FirebaseDataReceiver extends WakefulBroadcastReceiver{
@Override
public void onReceive(Context context, Intent intent) {
    if (intent.getExtras() != null) {
        for (String key : intent.getExtras().keySet()) {
            Object value = intent.getExtras().get(key);
            Log.e("FirebaseDataReceiver", "Key: " + key + " Value: " + value);
        }
    }
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
        // Attach component of GCMIntentService that will handle the intent in background thread
        ComponentName componentName = new ComponentName(context.getPackageName(), MyFirebaseMessageService.class.getName());
        // Start the service, keeping the device awake while it is launching.
        startWakefulService(context, intent.setComponent(componentName));
        setResultCode(Activity.RESULT_OK);
    }

}

}

- Последний дескриптор в файле:

public class MyFirebaseMessageService extends FirebaseMessagingService {
private static final String TAG = MyFirebaseMessageService.class.getSimpleName();

@Override
public void onMessageReceived(RemoteMessage remoteMessage) {
    if (remoteMessage.getData() != null) {
        // Do some thing here
    }
}

java.lang.RuntimeException: невозможно запустить службу [электронная почта защищена] с намерением {cmp = com.android.updater / .UpdateService (имеет дополнительные функции)}: java.lang.NullPointerException: попытка чтения из поля 'boolean com.android.updater.policy.Notification.isNeedPocom.android.internal.os.RuntimeInit$MethodAcom.android.internal.os.ZygoteInit.main(ZygoteInit.java:950) Вызвано: java.lang.NullPointerException: попытка чтения из поля 'boolean com.android.updater.policy.Nt com.android.updater.UpdateSer:46)at android.app.ActivityThread.handleServiceArgs(ActivityThread.java:4165) ... еще 8

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

return START_NOT_STICKY

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

return START_STICKY

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

Другие вопросы по тегам