Android 8.0: java.lang.IllegalStateException: не разрешено запускать сервис Intent

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

Причина: java.lang.IllegalStateException: Не разрешено запускать сервис Intent {cmp = my.app.tt/com.my.service}: приложение находится в фоновом режиме uidRecord {90372b1 u0a136 CEM idle procs: 1 seq (0,0, 0)}

как я понимаю, это связано с: лимитами исполнения

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

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

Ответ 1

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

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

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

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

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

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

Ответ 2

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

Ответ 3

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

Ответ 4

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

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

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

Ответ 5

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

Вы должны будете использовать

ContextCompat.startForegroundService(...)

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

Ответ 6

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

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

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

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

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

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

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

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

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

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

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

Ответ 7

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

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

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

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

Ответ 8

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

Ответ 9

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

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

Ответ 10

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

Ответ 11

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

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

Ответ 12

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

Ответ 13

если вы включили push-оповещение об обмене сообщениями firebase,

Добавьте новые/обновленные зависимости обмена сообщениями 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'

Ответ 14

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

Ответ 15

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

Ответ 16

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

Ответ 17

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

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

Ответ 18

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

return START_NOT_STICKY

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

return START_STICKY

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