Android 8.0: java.lang.IllegalStateException: impossibile avviare il servizio Intent


360

All'avvio dell'applicazione, l'app avvia il servizio che dovrebbe eseguire alcune attività di rete. Dopo aver scelto come target il livello API 26, la mia applicazione non riesce ad avviare il servizio su Android 8.0 in background.

Causato da: java.lang.IllegalStateException: impossibile avviare il servizio Intent {cmp = my.app.tt / com.my.service}: l'app è in background uid UidRecord {90372b1 u0a136 Proc. Inattivo CEM: 1 seq (0,0 , 0)}

come ho capito in relazione a: Limiti di esecuzione in background

Il metodo startService () ora genera un'eccezione IllegalStateException se un'app destinata ad Android 8.0 tenta di utilizzare quel metodo in una situazione in cui non è consentito creare servizi in background.

" in una situazione in cui non è permesso " - cosa significa in realtà ?? E come risolverlo. Non voglio impostare il mio servizio come "primo piano"


4
Significa che non puoi avviare un servizio quando la tua app è in background
Tim

22
questo non ha nulla a che fare con le autorizzazioni di runtime
Tim

10
Usa startForegroundService()invece di startService().
frogatto,

2
Puoi provare a utilizzare targetSdkVersion 25 ma compilare con compileSdkVersion 26. In questo modo puoi utilizzare nuove classi da Android 8 e la libreria di supporto più recente ma la tua app non sarà limitata dai limiti di esecuzione in background.
Kacper Dziubek,

2
@KacperDziubek Dovrebbe funzionare, ma è una soluzione temporanea in quanto sarà richiesto come target SDK26 nell'autunno del 2018.
RightHandedMonkey

Risposte:


194

Le situazioni consentite sono una whitelist temporanea in cui il servizio in background si comporta come prima di Android O.

In determinate circostanze, un'app in background viene inserita in una whitelist temporanea per diversi minuti. Mentre un'app è nella whitelist, può avviare servizi senza limitazioni e i suoi servizi in background possono essere eseguiti. Un'app viene inserita nella whitelist quando gestisce un'attività visibile all'utente, ad esempio:

  • Gestione di un messaggio FCM (Firebase Cloud Messaging) ad alta priorità.
  • Ricezione di una trasmissione, ad esempio un messaggio SMS / MMS.
  • Esecuzione di un PendingIntent da una notifica.
  • L'avvio di un VpnService prima che l'app VPN si promuova in primo piano.

Fonte: https://developer.android.com/about/versions/oreo/background.html

Quindi, in altre parole, se il servizio in background non soddisfa i requisiti della whitelist, è necessario utilizzare il nuovo JobScheduler . È praticamente lo stesso di un servizio in background, ma viene chiamato periodicamente invece di essere eseguito in background continuamente.

Se si utilizza un IntentService, è possibile passare a un JobIntentService. Vedi la risposta di @ kosev di seguito .


Ho un arresto anomalo dopo che desidero avviare il servizio subito dopo aver ricevuto il messaggio GCM di prio "alto". Uso ancora GCM: "com.google.android.gms: play-services-gcm: 11.4.2", non "com.google.firebase: firebase-messaging: 11.4.2". Non sono sicuro che sia importante però ...
Alex Radzishevsky,

"È praticamente lo stesso di un servizio in background, ma viene chiamato periodicamente invece di essere eseguito in background continuamente." - non sono sicuro di cosa intendi con questo, poiché i servizi Android non sono mai stati eseguiti in modo continuo. Iniziano, corrono, poi si spengono.
Melllvar,

2
È il FirebaseInstanceIdService e il suo onTokenRefreshmetodo di un messaggio FCM ad alta priorità?
Corda Rehn,

@phnmnn no, GCMTaskService non segue realmente FCM, quindi non funzionano.
Abhinav Upadhyay,

4
Non dovresti usare WorkManager (qui: developer.android.com/topic/libraries/architecture/workmanager ) invece di JobScheduler o altri? Intendo questo: youtu.be/IrKoBFLwTN0
sviluppatore Android

256

Ho una soluzione. Per i dispositivi pre-8.0, devi solo usare startService(), ma per i dispositivi post-7.0, devi usare startForgroundService(). Ecco un esempio di codice per avviare il servizio.

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

E nella classe di servizio, aggiungi il codice seguente per la notifica:

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

Dove O è la versione 26 di Android.


9
Un servizio in primo piano è qualcosa di cui l'utente sarà a conoscenza e che necessita di una notifica. ANR anche se dura troppo a lungo. Quindi non è davvero una risposta adatta se l'app è già in esecuzione in background.
Simone,

80
Al suo posto è disponibile una ContextCompat.startForegroundService(...)libreria di supporto.
jayeffkay,

37
Questa non è una soluzione.
JacksOnF1re

17
Sono anche d'accordo che questa non è una soluzione. È una soluzione alternativa e aiuta, ma i limiti di background in Oreo sono stati introdotti per un motivo. Bypassare questi limiti in questo modo sicuramente non è l'approccio corretto (anche se funziona). Il modo migliore è utilizzare JobScheduler (fare riferimento alla risposta accettata).
Vratislav Jindra,

6
Non penso che sarà una buona esperienza utente se devi mostrare una notifica vuota in primo piano. Considerando il fatto che devi. - Android 8.0 introduce il nuovo metodo startForegroundService () per avviare un nuovo servizio in primo piano. Dopo che il sistema ha creato il servizio, l'app ha cinque secondi per chiamare il metodo startForeground () del servizio per mostrare la notifica visibile all'utente del nuovo servizio. Se l'app non chiama startForeground () entro il limite di tempo, il sistema interrompe il servizio e dichiara l'app come ANR.
Heeleeaz,

85

Il modo migliore è utilizzare JobIntentService che utilizza il nuovo JobScheduler per Oreo o i vecchi servizi se non disponibili.

Dichiara nel tuo manifest:

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

E al tuo servizio devi sostituire onHandleIntent con 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
    }

}

Quindi inizi il tuo servizio con:

YourService.enqueueWork(context, new Intent());


Come puoi chiamare un metodo non statico all'interno di un metodo statico? Puoi spiegare per favore?
Maddy,

@Maddy enqueueWork(...)è anche un metodo statico.
hgoebl,

2
Dove chiameresti YourService.enqueueWork (context, new Intent ()); ? Dal ricevitore di trasmissione?
TheLearner

Non credo che questa sia la soluzione più semplice. Vedi il mio commento qui sotto su WorkManager. Utilizza JobIntentService quando appropriato, ma ha una piastra della caldaia molto inferiore.
TALE

36

Se il servizio è in esecuzione in un thread in background mediante l'estensione IntentService, è possibile sostituire IntentServicecon il JobIntentServicequale viene fornito come parte della Libreria di supporto Android

Il vantaggio dell'uso JobIntentServiceè che si comporta come IntentServicesu dispositivi pre-O e su O e superiori, lo invia come un lavoro

JobSchedulerpuò essere utilizzato anche per lavori periodici / su richiesta. Tuttavia, assicurarsi di gestire la compatibilità con le versioni precedenti poiché l' JobSchedulerAPI è disponibile solo dall'API 21


1
Il problema con JobIntentService è che Android può pianificare il tuo lavoro in modo piuttosto arbitrario e non può essere avviato implicitamente senza armeggiare, a differenza di IntentService. Vedere stackoverflow.com/questions/52479262/...
kilokahn

15

In Oreo Android ha definito i limiti dei servizi in background .

Per migliorare l'esperienza dell'utente, Android 8.0 (livello API 26) impone limitazioni su ciò che le app possono fare durante l'esecuzione in background.

Tuttavia, se è necessario eseguire sempre il servizio, è possibile utilizzare il servizio di primo piano.

Limitazioni del servizio in background: mentre un'app è inattiva, ci sono limiti al suo utilizzo dei servizi in background. Questo non si applica ai servizi in primo piano, che sono più evidenti per l'utente.

Quindi puoi fare un servizio in primo piano . Dovrai mostrare una notifica all'utente quando il servizio è in esecuzione. Vedi questa risposta (Ce ne sono molti altri)

Una soluzione se -

non vuoi una notifica per il tuo servizio?

È possibile eseguire attività periodiche, 1. avvia il servizio, 2. il servizio farà il suo lavoro e 3. si interromperà. In questo modo la tua app non verrà considerata scarica della batteria.

È possibile utilizzare attività periodiche con Alarm Manager , Job Scheduler , Evernote-Jobs o Work Manager .

Ho testato per sempre il servizio con Work-Manager.


WorkManager sembra il modo migliore di procedere, supponendo che il lavoro non debba essere eseguito immediatamente. È retrocompatibile con API 14, utilizzando JobScheduler su dispositivi con API 23+ e una combinazione di BroadcastReceiver + AlarmManager su dispositivi con API 14-22
James Allen

la cosa chiave di WorkManager è che WorkManager è destinato alle attività che sono differibili, ovvero non è necessario eseguirle immediatamente
touhid udoy

13

Sì, è perché non puoi più avviare i servizi in background sull'API 26. Quindi puoi avviare ForegroundService sopra l'API 26.

Dovrai usare

ContextCompat.startForegroundService(...)

e pubblica una notifica durante l'elaborazione della perdita.


1
L'OP ha affermato specificamente che non vuole essere in primo piano. Questo dovrebbe essere messo come un commento o come parte di una risposta più completa.
Ricardo A.,

7

Come ha detto @kosev nella sua risposta , puoi usare JobIntentService. Ma utilizzo una soluzione alternativa: prendo IllegalStateException e avvio il servizio in primo piano. Ad esempio, questa funzione avvia il mio servizio:

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

e quando elaboro Intento faccio una cosa del genere:

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

Mi piace la tua soluzione catch catch. Per me è una soluzione perché a volte context.startServicefunziona in background - a volte non - Questo appare come l'unico modo migliore altrimenti si devono implementare più codice nella classe principale extending Applicatione implementing ActivityLifecycleCallbackse tenere traccia se l'applicazione è in primo piano o sullo sfondo e iniziare il vostro intento di conseguenza.
Pierre,

Questa eccezione può essere catturata?
giovedì

5

Dalle note di rilascio di firebase , affermano che il supporto per Android O è stato rilasciato per la prima volta in 10.2.1 (anche se consiglierei di utilizzare la versione più recente).

aggiungi nuove dipendenze di messaggistica firebase per Android O

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

aggiorna i servizi di Google Play e i repository di Google, se necessario.


Questo non risponde alla domanda, né la domanda ha nulla a che fare con Firebase. Dovrebbe essere messo come un commento.
Ricardo A.

5

Se qualche intento in precedenza funzionava correttamente quando l'app è in background, non sarà più il caso da Android 8 e versioni successive. Si riferisce solo all'intento che deve eseguire alcune elaborazioni quando l'app è in background.

I seguenti passaggi devono essere seguiti:

  1. L'intenzione sopra menzionata dovrebbe essere utilizzata al JobIntentServiceposto di IntentService.
  2. La classe che si estende JobIntentServicedovrebbe implementare il onHandleWork(@NonNull Intent intent)metodo - e dovrebbe avere sotto il metodo, che invocherà il onHandleWorkmetodo:

    public static void enqueueWork(Context context, Intent work) {
        enqueueWork(context, xyz.class, 123, work);
    }
  3. Chiama enqueueWork(Context, intent)dalla classe in cui è definito il tuo intento.

    Codice di esempio:

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

La classe seguente estendeva in precedenza la classe 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è necessario per JobIntentService- io uso 26.1.0 V.

  2. La cosa più importante è assicurarsi che la versione delle librerie di Firebase sia attiva almeno 10.2.1, ho avuto problemi con 10.2.0- se ne hai!

  3. Il manifest deve disporre dell'autorizzazione seguente per la classe di servizio:

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

Spero che sia di aiuto.


4

Vedo molte risposte che raccomandano di usare un ForegroundService. Per utilizzare un ForegroundService deve essere associata una notifica. Gli utenti vedranno questa notifica. A seconda della situazione, potrebbero infastidire la tua app e disinstallarla.

La soluzione più semplice è utilizzare il nuovo componente Architecture chiamato WorkManager. Puoi consultare la documentazione qui: https://developer.android.com/topic/libraries/architecture/workmanager/

Devi solo definire la tua classe di lavoratore che estende il lavoratore.

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

Quindi pianificare quando si desidera eseguirlo.

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

Facile! Esistono molti modi per configurare i lavoratori. Supporta lavori ricorrenti e puoi persino fare cose complesse come concatenare se ne hai bisogno. Spero che sia di aiuto.


3
Attualmente WorkManager è ancora alfa.
pzulw,

3
05 marzo 2019 - Versione stabile di WorkManager 1.0.0.
phnmnn,

dovrebbe usare WorkManager invece di usare interservice o JobIntentService
sivaBE35

1
WorkManager is intended for tasks that are deferrable—that is, not required to run immediately... potrebbe essere più semplice, tuttavia, la mia app necessita di un servizio in background che esegue immediatamente le richieste degli utenti!
Someone Somewhere

Se si richiede che l'attività venga completata immediatamente, è necessario utilizzare un servizio di primo piano. L'utente vedrà una notifica e saprà che stai facendo un lavoro. Controlla i documenti se hai bisogno di aiuto per decidere cosa usare. Hanno una guida abbastanza buona per l'elaborazione in background. developer.android.com/guide/background
TALE

4

Soluzione alternativa utilizzando JobScheduler, può avviare il servizio in background a intervalli regolari di tempo.

Per prima cosa crea una classe chiamata 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());
   }
  }

Quindi, crea la classe JobService denominata 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;
  }
 }

Dopo quella classe BroadCast Receiver denominata 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);
 }
}

Aggiorna il file manifest con codice di servizio e classe destinatario

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

Ha lasciato il programma di avvio main_intent sul file mainActivity.java, creato per impostazione predefinita, e le modifiche al file MainActivity.java sono

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 !! Il servizio in background si avvia senza il servizio in primo piano


2

Se si esegue il codice su 8.0, l'applicazione si arresterà in modo anomalo. Quindi avvia il servizio in primo piano. Se inferiore a 8.0 utilizzare questo:

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

Se sopra o 8.0 quindi utilizzare questo:

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

Si consiglia di utilizzare i servizi in primo piano solo nei casi in cui l'utente deve essere consapevole che un servizio è in esecuzione. L'esempio tipico è per la riproduzione di musica in sottofondo. Ci sono altri casi che hanno senso, ma non dovresti semplicemente convertire tutti i tuoi servizi in servizi in primo piano. Prendi in considerazione la possibilità di convertire i tuoi servizi in modo da utilizzare WorkManager dai componenti di Google Architectural quando hai solo bisogno di fare un po 'di lavoro in background e assicurarti che funzionerà.
RACCONTO

startForegroundService richiede l'autorizzazione, altrimenti java.lang.SecurityException: Permission Denial: startForeground from pid=13708, uid=10087 requires android.permission.FOREGROUND_SERVICE. Risolto il problema
Someone Somewhere

1

se hai integrato la notifica push di messaggistica firebase,

Aggiungi nuove / aggiorna dipendenze di messaggistica firebase per Android O (Android 8.0), a causa dei limiti di esecuzione in background .

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

aggiorna i servizi di Google Play e i repository di Google, se necessario.

Aggiornare:

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

0

Usa startForegroundService()invece di startService() e non dimenticare di creare startForeground(1,new Notification());nel tuo servizio entro 5 secondi dall'inizio del servizio.


2
Sembra che il nuovo Notificaction () non funzioni da Android 8.1; Si dovrebbe creare il canale per la notifica: stackoverflow.com/a/47533338/1048087
Prizoff

0

A causa di voti controversi su questa risposta (+ 4 / -4 a partire da questa modifica), PER FAVORE , GUARDA PER PRIMA LE ALTRE RISPOSTE E UTILIZZA QUESTO SOLO COME ULTIMO RESORT . L'ho usato solo una volta per un'app di rete che funziona come root e sono d'accordo con l'opinione generale che questa soluzione non dovrebbe essere utilizzata in circostanze normali.

Risposta originale di seguito:

Le altre risposte sono tutte corrette, ma vorrei sottolineare che un altro modo per aggirare questo è chiedere all'utente di disabilitare le ottimizzazioni della batteria per la tua app (questa non è di solito una buona idea a meno che l'app non sia correlata al sistema). Vedi questa risposta per sapere come richiedere la disattivazione delle ottimizzazioni della batteria senza che la tua app venga vietata in Google Play.

È inoltre necessario verificare se le ottimizzazioni della batteria sono disattivate nel ricevitore per evitare arresti anomali tramite:

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
Chiedere ai tuoi utenti di permetterti di avere un pass gratuito usando la maggior quantità di batteria possibile non è una buona soluzione. Valuta la possibilità di convertire il codice in una soluzione più compatibile con la batteria. I tuoi utenti ti ringrazieranno.
RACCONTO

5
@TALE Non tutti i servizi in background possono essere resi compatibili con la batteria JobSchedulere altro. Alcune app devono funzionare a un livello inferiore rispetto alle applicazioni di sincronizzazione tipiche. Questa è una soluzione alternativa quando non funziona.
Mygod,

-17

non usare in onStartCommand:

return START_NOT_STICKY

basta cambiarlo in:

return START_STICKY

e funzionerà

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.