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


359

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

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

наскільки я розумію, це стосується: Лімітів фонового виконання

Метод startService () тепер передає IllegalStateException, якщо програма, націлена на Android 8.0, намагається використовувати цей метод у ситуації, коли заборонено створювати фонові послуги.

" у ситуації, коли це не дозволено " - що це насправді означає ?? І як це виправити. Я не хочу встановлювати свою послугу як "передній план"


4
Це означає, що ви не можете запустити послугу, коли ваша програма знаходиться у фоновому режимі
Тим,

22
це не має нічого спільного з дозволами виконання
Тім,

10
Використовуйте startForegroundService()замість startService().
frogatto

2
Ви можете спробувати використовувати targetSdkVersion 25, але компілювати з compileSdkVersion 26. Таким чином ви можете використовувати нові класи з Android 8 та новітньої бібліотеки підтримки, але ваш додаток не обмежуватиметься фоновими обмеженнями виконання.
Кацпер Дзюбек

2
@KacperDziubek Це повинно працювати, але це тимчасове рішення, оскільки його потрібно буде націлити на SDK26 восени 2018 року.
RightHandedMonkey

Відповіді:


194

Дозволені ситуації - це тимчасовий білий список, коли фонова служба поводиться так само, як і раніше Android O.

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

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

Джерело: https://developer.android.com/about/versions/oreo/background.html

Інакше кажучи, якщо ваша послуга, що працює на задньому плані, не відповідає вимогам білого списку, ви повинні використовувати новий JobScheduler . Це в основному те саме, що і фоновий сервіс, але його викликають періодично, а не працювати у фоновому режимі постійно.

Якщо ви використовуєте IntentService, ви можете перейти на JobIntentService. Дивіться відповідь @ kosev нижче .


Я отримую збої після того, як хочу запустити послугу одразу після того, як я отримаю GCM повідомлення "високого" пріорі. Я все ще використовую GCM: "com.google.android.gms: play-services-gcm: 11.4.2", а не 'com.google.firebase: firebase-обмін повідомленнями: 11.4.2'. Не впевнений, що це має значення ..
Олексій Радзішевський

"Це в основному те саме, що і фоновий сервіс, але його викликають періодично, замість того, щоб постійно працювати у фоновому режимі." - не впевнений, що ви маєте на увазі під цим, оскільки служби Android ніколи не виконувались постійно. Вони стартують, бігають, потім вимикаються.
Melllvar

2
Чи є FirebaseInstanceIdService та його onTokenRefreshметод пріоритетним повідомленням FCM?
Cord Rehn

@phnmnn ні, GCMTaskService насправді не дотримується FCM, отже, вони не працюють.
Абхінав Упадхей

4
Чи не слід використовувати WorkManager (тут: developer.android.com/topic/libraries/architecture/workmanager ) замість JobScheduler чи інших? Я маю на увазі це: youtu.be/IrKoBFLwTN0
андроїд розробник

254

Я отримав рішення. Для пристроїв до 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());


Як ви можете викликати нестатичний метод всередині статичного методу? Чи можете ви поясніть, будь ласка?
Меді

@Maddy також enqueueWork(...)є статичним методом.
hgoebl

2
Куди б ви назвали YourService.enqueueWork (контекст, новий намір ()); ? Від приймача широкомовної передачі?
TheLearner

Я не вірю, що це найпростіше рішення. Дивіться мій коментар нижче про WorkManager. Він використовує JobIntentService, коли це доречно, але у нього набагато менше плити котла.
TALE

36

Якщо служба працює у фоновому потоці за рахунок розширення IntentService, ви можете замінити IntentServiceз JobIntentServiceякої здійснюється в рамках Android бібліотеки підтримки

Перевага використання JobIntentServiceполягає в тому, що він поводиться як IntentServiceна пристроях до виводу, так і на 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-Робота або Робота менеджером .

Я перевірив вічно працюючу службу з Work-Manager.


WorkManager видається найкращим способом, якщо робота не повинна виконуватися негайно. Він сумісний з API 14, використовуючи JobScheduler на пристроях з API 23+ та комбінацію BroadcastReceiver + AlarmManager на пристроях з API 14-22
Джеймс Аллен

головне про WorkManager - це те, що WorkManager призначений для відкладених завдань, тобто не потрібно виконувати негайно
touhid udoy

13

Так, це тому, що ви більше не можете запускати служби у фоновому режимі на API 26. Отже, ви можете запустити ForegroundService вище API 26.

Вам доведеться скористатися

ContextCompat.startForegroundService(...)

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


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

і коли я обробляю наміри, я роблю таке:

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

З нот випуску вогневої бази , вони заявляють, що підтримка Android O була вперше випущена в 10.2.1 (хоча я рекомендую використовувати останню версію).

будь ласка, додайте нові залежності обміну повідомленнями Firebase для android O

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

оновіть сервіси google play та сховища google, якщо це необхідно.


Це не відповідає на питання, а також питання не має нічого спільного з firebase. Це слід ставити як коментар.
Рікардо А.

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

Нижній клас раніше розширював клас обслуговування

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
TALE

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

Оновіть файл Manifest за допомогою коду класу служби та приймача

<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 !! Фон-сервіс починається без служби переднього плану


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

Використовувати послуги Foreground рекомендується лише у випадках, коли користувачеві необхідно знати, що служба працює. Типовий приклад - відтворення музики у фоновому режимі. Є й інші випадки, які мають сенс, але ви не повинні просто перетворювати всі свої послуги на послуги Foreground. Подумайте про перетворення своїх служб на використання WorkManager з архітектурних компонентів Google, коли вам просто потрібно виконати деяку роботу у фоновому режимі та гарантувати, що вона запуститься.
TALE

startForegroundService вимагає дозволу, в іншому випадку java.lang.SecurityException: Permission Denial: startForeground from pid=13708, uid=10087 requires android.permission.FOREGROUND_SERVICE. Виправити на stackoverflow.com/a/52382711/550471
Хтось десь

1

якщо у вас є інтегроване повідомлення про передачу повідомлень 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'

0

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


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
Попросити своїх користувачів дозволити вам отримати безкоштовний пропуск, використовуючи якомога більше акумулятора, не є гарним рішенням. Подумайте про перетворення свого коду на більш зручне рішення від акумулятора. Ваші користувачі будуть вам вдячні.
TALE

5
@TALE Не кожний фоновий сервіс можна зробити зручним для використання батареї JobSchedulerта іншого обладнання. Деякі програми повинні працювати на нижчому рівні, ніж типові програми синхронізації. Це альтернативне рішення, коли це не працює.
Mygod

-17

не використовувати в onStartCommand:

return START_NOT_STICKY

просто змініть його на:

return START_STICKY

і це буде працювати

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.