Context.startForegroundService () non ha quindi chiamato Service.startForeground ()


258

sto usando Service Class su Android O OS.

Ho intenzione di utilizzare il Servicein background.

La documentazione di Android afferma che

Se la tua app ha come target il livello API 26 o superiore, il sistema impone restrizioni sull'uso o sulla creazione di servizi in background a meno che l'app stessa non sia in primo piano. Se un'app deve creare un servizio in primo piano, l'app deve chiamare startForegroundService().

Se si utilizza startForegroundService(), Servicegenera il seguente errore.

Context.startForegroundService() did not then call
Service.startForeground() 

Cosa c'è di sbagliato in questo?


IOW, fornisci un esempio riproducibile minimo . Ciò include l'intera traccia dello stack Java e il codice che sta causando l'arresto anomalo.
CommonsWare,

3
Il bug è ancora qui nelle API 26 e 27 (27.0.3). Le versioni di Android interessate sono 8.0 e 8.1 È possibile ridurre il numero di arresti anomali aggiungendo startForeground () sia a onCreate () che a onStartCommand (), ma gli arresti anomali continueranno a verificarsi per alcuni utenti. L'unico modo per risolverlo atm è targetSdkVersion 25 in build.gradle.
Alex,


1
Sto usando questo work-around ora. funziona come il fascino theandroiddeveloper.com/blog/…
Prasad Pawar

1
Ho lo stesso problema. Risolvo questo problema. Ho condiviso la mia implementazione in questo argomento stackoverflow.com/questions/55894636/…
Beyazid,

Risposte:


99

Dai documenti di Google sulle modifiche del comportamento di Android 8.0 :

Il sistema consente alle app di chiamare Context.startForegroundService () anche quando l'app è in background. Tuttavia, l'app deve chiamare il metodo startForeground () di quel servizio entro cinque secondi dalla creazione del servizio.

Soluzione: Chiamata startForeground()in onCreate()per il Servicequale si utilizzaContext.startForegroundService()

Vedi anche: Limiti di esecuzione in background per Android 8.0 (Oreo)


19
Ho fatto questo nel onStartCommandmetodo, ma sto ancora ricevendo questo errore. Ho chiamato il startForegroundService(intent)mio MainActivity. Forse il servizio è avviato troppo lentamente. Penso che il limite di cinque secondi non dovrebbe esistere prima che possano promettere che il servizio viene avviato immediatamente.
Kimi Chiu,

29
Il periodo di 5 secondi non è assolutamente sufficiente, questa eccezione si verifica molto spesso nelle sessioni di debug. Sospetto che di tanto in tanto accadrà in modalità di rilascio. Ed essendo FATAL EXCEPTION si blocca semplicemente l'app! Ho provato a catturarlo con Thread.setDefaultUncaughtExceptionHandler (), ma anche su come ottenerlo e ignorare Android congela l'applicazione. Questo perché questa eccezione è innescata da handleMessage () e il ciclo principale termina efficacemente ... in cerca di soluzioni alternative.
southerton,

Ho chiamato il metodo Context.startForegroundService () nel ricevitore di trasmissione. quindi come gestire questa situazione? perché onCreate () non è disponibile nel ricevitore di trasmissione
Anand Savjani il

6
@southerton Penso che dovresti chiamarlo onStartCommand()invece di onCreate, perché se chiudi il servizio e lo riavvii, potrebbe andare onStartCommand()senza chiamare onCreate...
Sviluppatore Android

1
Qui possiamo controllare la risposta del team di Google issuetracker.google.com/issues/76112072#comment56
Prags

82

Ho chiamato ContextCompat.startForegroundService(this, intent)per avviare il servizio quindi

In servizio onCreate

 @Override
 public void onCreate() {
        super.onCreate();

        if (Build.VERSION.SDK_INT >= 26) {
            String CHANNEL_ID = "my_channel_01";
            NotificationChannel channel = new NotificationChannel(CHANNEL_ID,
                    "Channel human readable title",
                    NotificationManager.IMPORTANCE_DEFAULT);

            ((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)).createNotificationChannel(channel);

            Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
                    .setContentTitle("")
                    .setContentText("").build();

            startForeground(1, notification);
        }
}

48
Anch'io. Ma ricevo ancora questo errore di tanto in tanto. Forse Android non può garantire che chiamerà onCreatein 5 secondi. Quindi dovrebbero ridisegnarlo prima di costringerci a seguire le regole.
Kimi Chiu,

5
Stavo chiamando startForeground in onStartCommand () e occasionalmente ricevevo questo errore. L'ho spostato su onCreate e da allora non l'ho più visto (incrociando le dita).
Tyler,

4
Questo crea una notifica locale in Oreo che dice "APP_NAME è in esecuzione. Tocca per chiudere o vedere le informazioni". Come smettere di mostrare quella notifica?
Artista

6
No, il team di Android ha affermato che si tratta di un comportamento previsto. Quindi ho appena riprogettato la mia app. Questo è ridicolo. È sempre necessario riprogettare le app a causa di questi "comportamenti previsti". È la terza volta.
Kimi Chiu,

12
La causa principale di questo problema è che il servizio è stato arrestato prima di essere promosso in primo piano. Ma l'affermazione non si è fermata dopo che il servizio è stato distrutto. Puoi provare a riprodurlo aggiungendo StopServicedopo aver chiamato startForegroundService.
Kimi Chiu,

55

Perché questo problema si verifica perché il framework Android non può garantire che il servizio venga avviato entro 5 secondi, ma dall'altro lato il framework ha un limite rigoroso alla notifica in primo piano che deve essere attivata entro 5 secondi, senza verificare se framework ha tentato di avviare il servizio .

Questo è sicuramente un problema di framework, ma non tutti gli sviluppatori che affrontano questo problema stanno facendo del loro meglio:

  1. startForegrounduna notifica deve essere in entrambi onCreatee onStartCommand, perché se il tuo servizio è già stato creato e in qualche modo la tua attività sta provando a riavviarlo, onCreatenon verrà chiamato.

  2. l'ID di notifica non deve essere 0, altrimenti si verificherà lo stesso arresto anomalo anche se non è lo stesso motivo.

  3. stopSelfnon deve essere chiamato prima startForeground.

Con tutto quanto sopra 3 questo problema può essere ridotto un po ', ma ancora non è una soluzione, la soluzione reale o diciamo che la soluzione alternativa è il downgrade della versione SDK di destinazione a 25.

E nota che molto probabilmente Android P porterà ancora questo problema perché Google rifiuta persino di capire cosa sta succedendo e non crede che sia colpa sua, leggi # 36 e # 56 per maggiori informazioni


9
Perché sia ​​onCreate che onStartCommand? Puoi semplicemente inserirlo in StartStommand?
rcell

stopSelf non deve essere chiamato prima di startForeground => o direttamente dopo, perché sembra annullare "startForeground" quando lo fai.
Frank

1
Ho eseguito alcuni test e anche se onCreate non viene chiamato se il servizio è già stato creato, non è necessario chiamare nuovamente startForeground. Quindi è possibile chiamarlo solo sul metodo onCreate e non su onStartCommand. Probabilmente considerano la singola istanza del servizio come in primo piano dopo la prima chiamata fino alla fine.
Lxu,

Sto usando 0 come ID di notifica della chiamata iniziale per startForeground. Cambiarlo in 1 risolve il problema per me (si spera)
sig. 5

Quindi qual è la soluzione?
IgorGanapolsky,

35

Lo so, sono già state pubblicate troppe risposte, tuttavia la verità è che startForegroundService non può essere risolto a livello di app e dovresti smettere di usarlo. La raccomandazione di Google di utilizzare l'API Service # startForeground () entro 5 secondi dalla chiamata a Context # startForegroundService () non è qualcosa che un'app può sempre fare.

Android esegue molti processi contemporaneamente e non vi è alcuna garanzia che Looper chiamerà il servizio di destinazione che dovrebbe chiamare startForeground () entro 5 secondi. Se il tuo servizio di destinazione non ha ricevuto la chiamata entro 5 secondi, sei sfortunato e i tuoi utenti riscontreranno una situazione ANR. Nella traccia dello stack vedrai qualcosa del genere:

Context.startForegroundService() did not then call Service.startForeground(): ServiceRecord{1946947 u0 ...MessageService}

main" prio=5 tid=1 Native
  | group="main" sCount=1 dsCount=0 flags=1 obj=0x763e01d8 self=0x7d77814c00
  | sysTid=11171 nice=-10 cgrp=default sched=0/0 handle=0x7dfe411560
  | state=S schedstat=( 1337466614 103021380 2047 ) utm=106 stm=27 core=0 HZ=100
  | stack=0x7fd522f000-0x7fd5231000 stackSize=8MB
  | held mutexes=
  #00  pc 00000000000712e0  /system/lib64/libc.so (__epoll_pwait+8)
  #01  pc 00000000000141c0  /system/lib64/libutils.so (android::Looper::pollInner(int)+144)
  #02  pc 000000000001408c  /system/lib64/libutils.so (android::Looper::pollOnce(int, int*, int*, void**)+60)
  #03  pc 000000000012c0d4  /system/lib64/libandroid_runtime.so (android::android_os_MessageQueue_nativePollOnce(_JNIEnv*, _jobject*, long, int)+44)
  at android.os.MessageQueue.nativePollOnce (MessageQueue.java)
  at android.os.MessageQueue.next (MessageQueue.java:326)
  at android.os.Looper.loop (Looper.java:181)
  at android.app.ActivityThread.main (ActivityThread.java:6981)
  at java.lang.reflect.Method.invoke (Method.java)
  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:493)
  at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:1445)

A quanto ho capito, Looper ha analizzato la coda qui, ha trovato un "maltrattatore" e l'ha semplicemente ucciso. Il sistema è felice e sano ora, mentre gli sviluppatori e gli utenti non lo sono, ma poiché Google limita le loro responsabilità al sistema, perché dovrebbero preoccuparsi degli ultimi due? Apparentemente non lo fanno. Potrebbero renderlo migliore? Naturalmente, ad esempio, avrebbero potuto pubblicare la finestra di dialogo "Applicazione occupata", chiedendo a un utente di prendere una decisione sull'attesa o sull'uccisione dell'app, ma perché preoccuparsi, non è una sua responsabilità. La cosa principale è che il sistema è integro adesso.

Dalle mie osservazioni, ciò accade relativamente raramente, nel mio caso circa 1 arresto in un mese per gli utenti 1K. Riproducerlo è impossibile e anche se è riprodotto, non c'è nulla che tu possa fare per risolverlo in modo permanente.

In questo thread c'era un buon suggerimento per usare "bind" invece di "start" e poi quando il servizio è pronto, elabora onServiceConnected, ma di nuovo significa che non usa affatto le chiamate startForegroundService.

Penso che l'azione giusta e onesta da parte di Google sarebbe quella di dire a tutti che startForegourndServcie ha un difetto e non dovrebbe essere usato.

La domanda rimane ancora: cosa usare invece? Fortunatamente per noi, ci sono JobScheduler e JobService ora, che sono un'alternativa migliore per i servizi in primo piano. È un'opzione migliore, per questo:

Mentre un lavoro è in esecuzione, il sistema mantiene un wakelock per conto della tua app. Per questo motivo, non è necessario intraprendere alcuna azione per garantire che il dispositivo rimanga sveglio per la durata del lavoro.

Significa che non devi più preoccuparti di gestire i wakelock ed è per questo che non è diverso dai servizi di primo piano. Dal punto di vista dell'implementazione JobScheduler non è il tuo servizio, è un sistema, presumibilmente gestirà la coda giusta e Google non terminerà mai il proprio figlio :)

Samsung è passato da startForegroundService a JobScheduler e JobService nel loro Samsung Accessory Protocol (SAP). È molto utile quando dispositivi come gli smartwatch devono parlare con host come telefoni, in cui il lavoro deve interagire con un utente attraverso il thread principale di un'app. Poiché i lavori vengono registrati dallo scheduler nel thread principale, diventa possibile. Tuttavia, è necessario ricordare che il lavoro è in esecuzione sul thread principale e scaricare tutti gli elementi pesanti su altri thread e attività asincrone.

Questo servizio esegue ogni processo in entrata su un gestore in esecuzione sul thread principale dell'applicazione. Ciò significa che è necessario scaricare la logica di esecuzione su un altro thread / gestore / AsyncTask di propria scelta

L'unico inconveniente del passaggio a JobScheduler / JobService è che dovrai refactificare il vecchio codice e non è divertente. Ho passato gli ultimi due giorni a fare proprio questo per usare la nuova implementazione SAP di Samsung. Guarderò i miei rapporti sugli arresti anomali e ti informerò se visualizzeranno nuovamente gli arresti anomali. Teoricamente non dovrebbe accadere, ma ci sono sempre dettagli di cui potremmo non essere a conoscenza.

AGGIORNARE Non ci sono più arresti anomali segnalati dal Play Store. Significa che JobScheduler / JobService non hanno questo problema e il passaggio a questo modello è l'approccio giusto per sbarazzarsi del problema startForegroundService una volta per sempre. Spero che Google / Android lo legga e alla fine commenterà / consigli / fornirà una guida ufficiale per tutti.

AGGIORNAMENTO 2

Per coloro che usano SAP e chiedono come SAP V2 utilizza JobService, la spiegazione è di seguito.

Nel tuo codice personalizzato dovrai inizializzare SAP (è Kotlin):

SAAgentV2.requestAgent(App.app?.applicationContext, 
   MessageJobs::class.java!!.getName(), mAgentCallback)

Ora devi decompilare il codice Samsung per vedere cosa succede dentro. In SAAgentV2 dai un'occhiata all'implementazione di requestAgent e alla seguente riga:

SAAgentV2.d var3 = new SAAgentV2.d(var0, var1, var2);

where d defined as below

private SAAdapter d;

Vai subito alla classe SAAdapter e trova la funzione onServiceConnectionRequested che pianifica un lavoro utilizzando la seguente chiamata:

SAJobService.scheduleSCJob(SAAdapter.this.d, var11, var14, var3, var12); 

SAJobService è solo un'implementazione di JobService Android e questo è quello che fa una pianificazione dei lavori:

private static void a(Context var0, String var1, String var2, long var3, String var5, SAPeerAgent var6) {
    ComponentName var7 = new ComponentName(var0, SAJobService.class);
    Builder var10;
    (var10 = new Builder(a++, var7)).setOverrideDeadline(3000L);
    PersistableBundle var8;
    (var8 = new PersistableBundle()).putString("action", var1);
    var8.putString("agentImplclass", var2);
    var8.putLong("transactionId", var3);
    var8.putString("agentId", var5);
    if (var6 == null) {
        var8.putStringArray("peerAgent", (String[])null);
    } else {
        List var9;
        String[] var11 = new String[(var9 = var6.d()).size()];
        var11 = (String[])var9.toArray(var11);
        var8.putStringArray("peerAgent", var11);
    }

    var10.setExtras(var8);
    ((JobScheduler)var0.getSystemService("jobscheduler")).schedule(var10.build());
}

Come vedi, l'ultima riga qui utilizza JobScheduler per Android per ottenere questo servizio di sistema e pianificare un lavoro.

Nella richiesta requestAgent abbiamo superato mAgentCallback, che è una funzione di callback che riceverà il controllo quando si verifica un evento importante. Ecco come viene definito il callback nella mia app:

private val mAgentCallback = object : SAAgentV2.RequestAgentCallback {
    override fun onAgentAvailable(agent: SAAgentV2) {
        mMessageService = agent as? MessageJobs
        App.d(Accounts.TAG, "Agent " + agent)
    }

    override fun onError(errorCode: Int, message: String) {
        App.d(Accounts.TAG, "Agent initialization error: $errorCode. ErrorMsg: $message")
    }
}

MessageJobs qui è una classe che ho implementato per elaborare tutte le richieste provenienti da uno smartwatch Samsung. Non è il codice completo, solo uno scheletro:

class MessageJobs (context:Context) : SAAgentV2(SERVICETAG, context, MessageSocket::class.java) {


    public fun release () {

    }


    override fun onServiceConnectionResponse(p0: SAPeerAgent?, p1: SASocket?, p2: Int) {
        super.onServiceConnectionResponse(p0, p1, p2)
        App.d(TAG, "conn resp " + p1?.javaClass?.name + p2)


    }

    override fun onAuthenticationResponse(p0: SAPeerAgent?, p1: SAAuthenticationToken?, p2: Int) {
        super.onAuthenticationResponse(p0, p1, p2)
        App.d(TAG, "Auth " + p1.toString())

    }


    override protected fun onServiceConnectionRequested(agent: SAPeerAgent) {


        }
    }

    override fun onFindPeerAgentsResponse(peerAgents: Array<SAPeerAgent>?, result: Int) {
    }

    override fun onError(peerAgent: SAPeerAgent?, errorMessage: String?, errorCode: Int) {
        super.onError(peerAgent, errorMessage, errorCode)
    }

    override fun onPeerAgentsUpdated(peerAgents: Array<SAPeerAgent>?, result: Int) {

    }

}

Come vedi, MessageJobs richiede anche la classe MessageSocket che dovrai implementare e che elabora tutti i messaggi provenienti dal tuo dispositivo.

In conclusione, non è così semplice e richiede un po 'di scavo per gli interni e la codifica, ma funziona e, soprattutto, non si blocca.


Buona risposta ma c'è un grosso problema, JobIntentServiceviene eseguito immediatamente come IntentServiceOreo inferiore, ma pianifica un lavoro su e sopra Oreo, quindi JobIntentServicenon inizia immediatamente. Maggiori informazioni
CopsOnRoad,

1
@CopsOnRoad È molto utile e inizia immediatamente nel mio caso, come ho scritto, viene utilizzato per le interazioni in tempo reale tra telefono e uno smartwatch. In nessun caso, un utente aspetterebbe 15 minuti per inviare dati tra questi due. Funziona molto bene e non si arresta mai.
Oleg Gryb,

1
Suona bene, renderebbe la tua risposta molto utile se puoi condividere un po 'di codice di esempio, sono nuovo su Android e ho scoperto che ci Job...vuole tempo per iniziare ed è la versione anticipata di AlarmManager.
CopsOnRoad,

2
Probabilmente lo farò quando il tempo lo permetterà: dovrò
disassociarlo

1
@batmaci - JobService viene utilizzato internamente da SAP. Vedere Aggiornamento 2 in OP per i dettagli.
Oleg Gryb, il

32

L'app si arresta in modo anomalo se si chiama Context.startForegroundService(...)e quindi si chiama Context.stopService(...)prima che Service.startForeground(...)venga chiamato.

Ho una chiara riproposizione qui ForegroundServiceAPI26

Ho aperto un bug su questo su: Tracker dei problemi di Google

Diversi bug sono stati aperti e chiusi. Non risolto.

Spero che il mio con chiari passaggi di riproduzione farà il taglio.

Informazioni fornite dal team di Google

Tracker dei problemi di Google Commento 36

Questo non è un bug del framework; è intenzionale. Se l'app avvia un'istanza di servizio con startForegroundService(), deve passare tale istanza di servizio allo stato di primo piano e mostrare la notifica. Se l'istanza del servizio viene arrestata prima che startForeground()venga richiamata, tale promessa non viene mantenuta: si tratta di un bug nell'app.

Per quanto riguarda il numero 31 , la pubblicazione di un servizio che altre app possono avviare direttamente non è sostanzialmente sicura. Puoi mitigarlo un po 'trattando tutte le azioni di avvio di quel servizio come richieste startForeground(), anche se ovviamente questo potrebbe non essere quello che avevi in ​​mente.

Tracker dei problemi di Google Commento 56

Ci sono un paio di scenari diversi che portano allo stesso risultato qui.

La vera questione semantica, che è semplicemente un errore per dare il via a qualcosa startForegroundService()ma trascurare di trasferirla effettivamente in primo piano via startForeground(), è proprio questo: una questione semantica. Viene trattato come un bug dell'app, intenzionalmente. L'arresto del servizio prima di spostarlo in primo piano è un errore dell'app. Questo è stato il punto cruciale del PO ed è per questo che questo problema è stato contrassegnato come "funzionante come previsto".

Tuttavia, ci sono anche domande sulla rilevazione spuria di questo problema. Questo viene trattato come un vero problema, anche se viene monitorato separatamente da questo particolare problema di tracciamento dei bug. Non siamo sordi al reclamo.


2
Il miglior aggiornamento che ho visto è issuetracker.google.com/issues/76112072#comment56 . Ho riscritto il mio codice per utilizzare Context.bindService che evita completamente la chiamata problematica a Context.startForegroundService. Puoi vedere il mio codice di esempio su github.com/paulpv/ForegroundServiceAPI26/tree/bound/app/src/…
swooby il

sì, come ho scritto, il bind dovrebbe funzionare, ma sono già passato a JobServive e non cambierò nulla a meno che anche questo non si rompa ':)
Oleg Gryb

20

Ho studiato questo per un paio di giorni e ho ottenuto la soluzione. Ora in Android O puoi impostare la limitazione dello sfondo come di seguito

Il servizio che chiama una classe di servizio

Intent serviceIntent = new Intent(SettingActivity.this,DetectedService.class);
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
    SettingActivity.this.startForegroundService(serviceIntent);
} else {
    startService(serviceIntent);
}

e la classe di servizio dovrebbe essere simile

public class DetectedService extends Service { 
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        return START_STICKY;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        int NOTIFICATION_ID = (int) (System.currentTimeMillis()%10000);
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            startForeground(NOTIFICATION_ID, new Notification.Builder(this).build());
        }


        // Do whatever you want to do here
    }
}

3
L'hai provato in qualche app che ha almeno diverse migliaia di utenti? Ho provato, alcuni utenti hanno ancora i problemi di crash.
Alex,

No no Sto usando il codice esatto e non vedo i client bloccarsi.
Ahmad Arslan,

Quanti utenti hai? Ho visto ~ 10-30 + crash giornalieri (con l'errore) in un'app che aveva 10k utenti al giorno. Naturalmente, succede solo per gli utenti con Android 8.0 e Android 8.1, nel caso targetSdkVersion sia 27. Tutti i problemi scompaiono a 0 arresti anomali subito dopo aver impostato targetSdkVersion su 25.
Alex

solo 2200 utenti: - / ma non ha avuto alcun incidente
Ahmad Arslan

1
@Jeeva, non proprio. Atm, uso targetSdkVersion 25 con compileSdkVersion 27. Sembra il modo migliore dopo molti esperimenti .... Spero che finiscano developer.android.com/topic/libraries/architecture/… prima di agosto 2018 perché android-developers.googleblog .com / 2017/12 /…
Alex

16

Ho un widget che esegue aggiornamenti relativamente frequenti quando il dispositivo è sveglio e ho visto migliaia di arresti anomali in pochi giorni.

L'innesco del problema

Ho anche notato il problema anche sul mio Pixel 3 XL quando non avrei pensato che il dispositivo avesse molto carico. E tutti i percorsi del codice sono stati coperti startForeground(). Ma poi ho capito che in molti casi il mio servizio fa il lavoro molto rapidamente.Credo che il fattore scatenante per la mia app sia stato il completamento del servizio prima che il sistema potesse effettivamente mostrare una notifica.

La soluzione alternativa

Sono stato in grado di sbarazzarmi di tutti gli incidenti. Quello che ho fatto è stato rimuovere la chiamata a stopSelf(). (Stavo pensando di ritardare l'arresto fino a quando non ero abbastanza sicuro che fosse mostrata la notifica, ma non voglio che l'utente visualizzi la notifica se non è necessaria.) Quando il servizio è rimasto inattivo per un minuto o il sistema lo distrugge normalmente senza generare eccezioni.

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    stopForeground(true);
} else {
    stopSelf();
}

11

Solo un avvertimento perché ho sprecato troppe ore in questo. Continuavo a ricevere questa eccezione anche se stavo chiamando startForeground(..)come prima cosa onCreate(..). Alla fine ho scoperto che il problema era causato dall'uso NOTIFICATION_ID = 0. L'uso di qualsiasi altro valore sembra risolverlo.


Nel mio caso stavo usando l'ID 1, grazie, il problema è stato risolto
Amin Pinjari,

4
Sto utilizzando l'ID 101, ma a volte ricevo questo errore.
CopsOnRoad,

9

Ho una soluzione per questo problema. Ho verificato questa correzione nella mia app (300K + DAU), che può ridurre almeno il 95% di questo tipo di crash, ma non è ancora possibile evitare questo problema al 100%.

Questo problema si verifica anche quando ti assicuri di chiamare startForeground () subito dopo l'avvio del servizio come documentato da Google. Potrebbe essere perché il processo di creazione e inizializzazione del servizio costa già più di 5 secondi in molti scenari, quindi indipendentemente da quando e dove si chiama il metodo startForeground (), questo arresto è inevitabile.

La mia soluzione è garantire che startForeground () verrà eseguito entro 5 secondi dal metodo startForegroundService (), indipendentemente da quanto tempo è necessario creare e inizializzare il servizio. Ecco la soluzione dettagliata.

  1. Non utilizzare startForegroundService in primo luogo, utilizzare bindService () con il flag auto_create. Attenderà l'inizializzazione del servizio. Ecco il codice, il mio servizio di esempio è MusicService:

    final Context applicationContext = context.getApplicationContext();
    Intent intent = new Intent(context, MusicService.class);
    applicationContext.bindService(intent, new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder binder) {
            if (binder instanceof MusicBinder) {
                MusicBinder musicBinder = (MusicBinder) binder;
                MusicService service = musicBinder.getService();
                if (service != null) {
                    // start a command such as music play or pause.
                    service.startCommand(command);
                    // force the service to run in foreground here.
                    // the service is already initialized when bind and auto_create.
                    service.forceForeground();
                }
            }
            applicationContext.unbindService(this);
        }
    
        @Override
        public void onServiceDisconnected(ComponentName name) {
        }
    }, Context.BIND_AUTO_CREATE);
  2. Quindi ecco l'implementazione di MusicBinder:

    /**
     * Use weak reference to avoid binder service leak.
     */
     public class MusicBinder extends Binder {
    
         private WeakReference<MusicService> weakService;
    
         /**
          * Inject service instance to weak reference.
          */
         public void onBind(MusicService service) {
             this.weakService = new WeakReference<>(service);
         }
    
         public MusicService getService() {
             return weakService == null ? null : weakService.get();
         }
     }
  3. La parte più importante, l'implementazione di MusicService, il metodo forceForeground () assicurerà che il metodo startForeground () venga chiamato subito dopo startForegroundService ():

    public class MusicService extends MediaBrowserServiceCompat {
    ...
        private final MusicBinder musicBind = new MusicBinder();
    ...
        @Override
        public IBinder onBind(Intent intent) {
            musicBind.onBind(this);
            return musicBind;
        }
    ...
        public void forceForeground() {
            // API lower than 26 do not need this work around.
            if (Build.VERSION.SDK_INT >= 26) {
                Intent intent = new Intent(this, MusicService.class);
                // service has already been initialized.
                // startForeground method should be called within 5 seconds.
                ContextCompat.startForegroundService(this, intent);
                Notification notification = mNotificationHandler.createNotification(this);
                // call startForeground just after startForegroundService.
                startForeground(Constants.NOTIFICATION_ID, notification);
            }
        }
    }
  4. Se si desidera eseguire lo snippet di codice passaggio 1 con un intento in sospeso, ad esempio se si desidera avviare un servizio di primo piano in un widget (un clic sul pulsante del widget) senza aprire l'app, è possibile racchiudere lo snippet di codice in un ricevitore broadcast e attiva un evento di trasmissione invece di avviare il comando di servizio.

Questo è tutto. Spero che sia d'aiuto. In bocca al lupo.



7

Dato che tutti quelli che visitano qui soffrono la stessa cosa, voglio condividere la mia soluzione che nessun altro ha mai provato prima (in questa domanda comunque). Posso assicurarti che funziona, anche su un punto di interruzione interrotto che conferma questo metodo.

Il problema è chiamare Service.startForeground(id, notification)dal servizio stesso, giusto? Android Framework purtroppo non garantisce di chiamare Service.startForeground(id, notification)entro Service.onCreate()5 secondi ma genera comunque l'eccezione, quindi ho escogitato questo modo.

  1. Associare il servizio a un contesto con un raccoglitore dal servizio prima di chiamare Context.startForegroundService()
  2. Se il bind ha esito positivo, chiama Context.startForegroundService() dalla connessione di servizio e chiamare immediatamente Service.startForeground() all'interno della connessione di servizio.
  3. NOTA IMPORTANTE: chiama il Context.bindService()metodo all'interno di un try-catch perché in alcune occasioni la chiamata può generare un'eccezione, nel qual caso devi fare affidamento sulla chiamata Context.startForegroundService()diretta e sperare che non fallisca. Un esempio può essere un contesto del destinatario della trasmissione, tuttavia ottenere il contesto dell'applicazione non genera un'eccezione in quel caso, ma l'utilizzo diretto del contesto lo fa.

Funziona anche quando aspetto un breakpoint dopo aver associato il servizio e prima di attivare la chiamata "startForeground". L'attesa tra 3-4 secondi non attiva l'eccezione mentre dopo 5 secondi genera l'eccezione. (Se il dispositivo non è in grado di eseguire due righe di codice in 5 secondi, allora è tempo di gettarlo nel cestino.)

Quindi, inizia con la creazione di una connessione di servizio.

// Create the service connection.
ServiceConnection connection = new ServiceConnection()
{
    @Override
    public void onServiceConnected(ComponentName name, IBinder service)
    {
        // The binder of the service that returns the instance that is created.
        MyService.LocalBinder binder = (MyService.LocalBinder) service;

        // The getter method to acquire the service.
        MyService myService = binder.getService();

        // getServiceIntent(context) returns the relative service intent 
        context.startForegroundService(getServiceIntent(context));

        // This is the key: Without waiting Android Framework to call this method
        // inside Service.onCreate(), immediately call here to post the notification.
        myService.startForeground(myNotificationId, MyService.getNotification());

        // Release the connection to prevent leaks.
        context.unbindService(this);
    }

    @Override
    public void onBindingDied(ComponentName name)
    {
        Log.w(TAG, "Binding has dead.");
    }

    @Override
    public void onNullBinding(ComponentName name)
    {
        Log.w(TAG, "Bind was null.");
    }

    @Override
    public void onServiceDisconnected(ComponentName name)
    {
        Log.w(TAG, "Service is disconnected..");
    }
};

All'interno del tuo servizio, crea un raccoglitore che restituisca l'istanza del tuo servizio.

public class MyService extends Service
{
    public class LocalBinder extends Binder
    {
        public MyService getService()
        {
            return MyService.this;
        }
    }

    // Create the instance on the service.
    private final LocalBinder binder = new LocalBinder();

    // Return this instance from onBind method.
    // You may also return new LocalBinder() which is
    // basically the same thing.
    @Nullable
    @Override
    public IBinder onBind(Intent intent)
    {
        return binder;
    }
}

Quindi, prova a associare il servizio da quel contesto. Se riesce, chiamerà il ServiceConnection.onServiceConnected()metodo dalla connessione di servizio che si sta utilizzando. Quindi, gestisci la logica nel codice mostrato sopra. Un codice di esempio sarebbe simile al seguente:

// Try to bind the service
try
{
     context.bindService(getServiceIntent(context), connection,
                    Context.BIND_AUTO_CREATE);
}
catch (RuntimeException ignored)
{
     // This is probably a broadcast receiver context even though we are calling getApplicationContext().
     // Just call startForegroundService instead since we cannot bind a service to a
     // broadcast receiver context. The service also have to call startForeground in
     // this case.
     context.startForegroundService(getServiceIntent(context));
}

Sembra funzionare sulle applicazioni che sviluppo, quindi dovrebbe funzionare anche quando ci provi.


5

Problema con Android O API 26

Se interrompi subito il servizio (quindi il tuo servizio non funziona davvero (formulazione / comprensione) e sei sotto l'intervallo ANR, devi comunque chiamare startForeground prima di fermarti

https://plus.google.com/116630648530850689477/posts/L2rn4T6SAJ5

Ho provato questo approccio ma crea ancora un errore: -

if (Util.SDK_INT > 26) {
    mContext.startForegroundService(playIntent);
} else {
    mContext.startService(playIntent);
}

Lo sto usando fino alla risoluzione dell'errore

mContext.startService(playIntent);

3
Dovrebbe essere Util.SDK_INT> = 26, non solo più grande
Andris

4

Sto affrontando lo stesso problema e dopo aver trascorso del tempo a trovare dei suggerimenti puoi provare di seguito il codice. Se Servicestai usando, inserisci questo codice in onCreate il tuo utilizzo, Intent Servicequindi inserisci questo codice in onHandleIntent.

if (Build.VERSION.SDK_INT >= 26) {
        String CHANNEL_ID = "my_app";
        NotificationChannel channel = new NotificationChannel(CHANNEL_ID,
                "MyApp", NotificationManager.IMPORTANCE_DEFAULT);
        ((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)).createNotificationChannel(channel);
        Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
                .setContentTitle("")
                .setContentText("").build();
        startForeground(1, notification);
    }

4

Ho studiato questo problema e questo è quello che ho scoperto finora. Questo arresto anomalo potrebbe verificarsi se disponiamo di un codice simile al seguente:

MyForegroundService.java

public class MyForegroundService extends Service {
    @Override
    public void onCreate() {
        super.onCreate();
        startForeground(...);
    }
}

MainActivity.java

Intent serviceIntent = new Intent(this, MyForegroundService.class);
startForegroundService(serviceIntent);
...
stopService(serviceIntent);

L'eccezione viene generata nel seguente blocco del codice:

ActiveServices.java

private final void bringDownServiceLocked(ServiceRecord r) {
    ...
    if (r.fgRequired) {
        Slog.w(TAG_SERVICE, "Bringing down service while still waiting for start foreground: "
                  + r);
        r.fgRequired = false;
        r.fgWaiting = false;
        mAm.mAppOpsService.finishOperation(AppOpsManager.getToken(mAm.mAppOpsService),
                    AppOpsManager.OP_START_FOREGROUND, r.appInfo.uid, r.packageName);
        mAm.mHandler.removeMessages(
                    ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG, r);
        if (r.app != null) {
            Message msg = mAm.mHandler.obtainMessage(
                ActivityManagerService.SERVICE_FOREGROUND_CRASH_MSG);
            msg.obj = r.app;
            msg.getData().putCharSequence(
                ActivityManagerService.SERVICE_RECORD_KEY, r.toString());
            mAm.mHandler.sendMessage(msg);
         }
    }
    ...
}

Questo metodo viene eseguito prima onCreate()di MyForegroundServicequanto gli orari Android la creazione del servizio sul gestore thread principale, ma bringDownServiceLockedviene chiamato su un BinderThread, che è una condizione di competizione. Significa che MyForegroundServicenon ha avuto la possibilità di chiamare startForegroundche causerà l'incidente.

Per risolvere questo problema dobbiamo assicurarci che bringDownServiceLockednon sia stato chiamato prima onCreate()di MyForegroundService.

public class MyForegroundService extends Service {

    private static final String ACTION_STOP = "com.example.MyForegroundService.ACTION_STOP";

    private final BroadcastReceiver stopReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            context.removeStickyBroadcast(intent);
            stopForeground(true);
            stopSelf();
        }
    };

    @Override
    public void onCreate() {
        super.onCreate();
        startForeground(...);
        registerReceiver(
            stopReceiver, new IntentFilter(ACTION_STOP));
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        unregisterReceiver(stopReceiver);
    }

    public static void stop(Context context) {
        context.sendStickyBroadcast(new Intent(ACTION_STOP));
    }
}

Utilizzando broadcast permanenti facciamo in modo che la trasmissione non si perde e stopReceiverriceve la fermata intento non appena è stato registrato in onCreate()su MyForegroundService. A questo punto abbiamo già chiamato startForeground(...). Dobbiamo anche rimuovere quella trasmissione appiccicosa per evitare che la prossima volta StopReceiver venga avvisato.

Si noti che il metodo sendStickyBroadcastè obsoleto e lo uso solo come soluzione temporanea per risolvere questo problema.


Dovresti chiamarlo invece di context.stopService (serviceIntent) quando vuoi interrompere il servizio.
makovkastar,

4

Tante risposte, ma nessuna ha funzionato nel mio caso.

Ho iniziato il servizio in questo modo.

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    startForegroundService(intent);
} else {
    startService(intent);
}

E al mio servizio in onStartCommand

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        Notification.Builder builder = new Notification.Builder(this, ANDROID_CHANNEL_ID)
                .setContentTitle(getString(R.string.app_name))
                .setContentText("SmartTracker Running")
                .setAutoCancel(true);
        Notification notification = builder.build();
        startForeground(NOTIFICATION_ID, notification);
    } else {
        NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
                .setContentTitle(getString(R.string.app_name))
                .setContentText("SmartTracker is Running...")
                .setPriority(NotificationCompat.PRIORITY_DEFAULT)
                .setAutoCancel(true);
        Notification notification = builder.build();
        startForeground(NOTIFICATION_ID, notification);
    }

E non dimenticare di impostare NOTIFICATION_ID diverso da zero

private static final String ANDROID_CHANNEL_ID = "com.xxxx.Location.Channel";
private static final int NOTIFICATION_ID = 555;

Quindi tutto era perfetto ma continuava a bloccarsi su 8.1 quindi la causa era come di seguito.

     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            stopForeground(true);
        } else {
            stopForeground(true);
        }

Ho chiamato stop foreground con remove notificaton ma una volta rimosso il servizio di notifica diventa background e il servizio in background non può essere eseguito in Android O dallo sfondo. avviato dopo la ricezione della spinta.

Quindi è una parola magica

   stopSelf();

Finora, qualsiasi motivo per cui il tuo servizio si arresta in modo anomalo, segui tutti i passaggi precedenti e divertiti.


if (Build.VERSION.SDK_INT> = Build.VERSION_CODES.O) {stopForeground (true); } else {stopForeground (true); } a cosa serve questo? in entrambi i casi stavi facendo lo stesso, stopForeground (true);
user3135923,

Credo che intendevi mettere stopSelf (); all'interno del blocco else invece di stopForeground (true);
Justin Stanley,

1
Sì, @JustinStanley usa stopSelf ();
sabbioso

Non dovrebbe startForegroundessere chiamato onCreate?
Aba,

Sto usando NotificationManager e non la classe di notifica. Come posso implementarlo?
Prajwal W,

3

https://developer.android.com/reference/android/content/Context.html#startForegroundService(android.content.Intent)

Simile a startService (Intent), ma con una promessa implicita che il Servizio chiamerà startForeground (int, android.app.Notification) quando inizierà a funzionare. A tal fine, al servizio viene assegnato un intervallo di tempo paragonabile all'intervallo ANR, altrimenti il ​​sistema interromperà automaticamente il servizio e dichiarerà l'app ANR.

A differenza del normale startService (Intent), questo metodo può essere utilizzato in qualsiasi momento, indipendentemente dal fatto che l'app che ospita il servizio sia in primo piano.

assicurati di chiamare Service.startForeground(int, android.app.Notification)onCreate () in modo da assicurarti che verrà chiamato..se hai qualche condizione che potrebbe impedirti di farlo, allora faresti meglio a usare il normale Context.startService(Intent)e chiamare ilService.startForeground(int, android.app.Notification) te stesso.

Sembra che Context.startForegroundService()aggiunge un cane da guardia per assicurarsi di aver chiamato Service.startForeground(int, android.app.Notification)prima che fosse distrutto ...


3

Per favore non chiamare alcun metodo StartForgroundServices all'interno di onCreate () , devi chiamare i servizi StartForground in onStartCommand () dopo aver creato il thread di lavoro altrimenti otterrai ANR sempre, quindi per favore non scrivere un login complesso nel thread principale di onStartCommand () ;

public class Services extends Service {

    private static final String ANDROID_CHANNEL_ID = "com.xxxx.Location.Channel";
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }


    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            Notification.Builder builder = new Notification.Builder(this, ANDROID_CHANNEL_ID)
                    .setContentTitle(getString(R.string.app_name))
                    .setContentText("SmartTracker Running")
                    .setAutoCancel(true);
            Notification notification = builder.build();
            startForeground(0, notification);
            Log.e("home_button","home button");
        } else {
            NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
                    .setContentTitle(getString(R.string.app_name))
                    .setContentText("SmartTracker is Running...")
                    .setPriority(NotificationCompat.PRIORITY_DEFAULT)
                    .setAutoCancel(true);
            Notification notification = builder.build();
            startForeground(0, notification);
            Log.e("home_button_value","home_button_value");

        }
        return super.onStartCommand(intent, flags, startId);

    }
}

EDIT: attenzione! La funzione startForeground non può accettare 0 come primo argomento, genererà un'eccezione! questo esempio contiene una chiamata di funzione errata, modifica 0 nella tua const che non può essere 0 o essere maggiore di Max (Int32)


2

Anche dopo aver chiamato startForegroundin Service, si arresta in modo anomalo su alcuni dispositivi se chiamiamo stopServiceprima che onCreatevenga chiamato. Quindi, ho risolto questo problema avviando il servizio con un flag aggiuntivo:

Intent intent = new Intent(context, YourService.class);
intent.putExtra("request_stop", true);
context.startService(intent);

e aggiunto un check-in su StartCommand per vedere se è stato avviato per interrompere:

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    //call startForeground first
    if (intent != null) {
        boolean stopService = intent.getBooleanExtra("request_stop", false);
        if (stopService) {
            stopSelf();
        }
    }

    //Continue with the background task
    return START_STICKY;
}

PS Se il servizio non fosse in esecuzione, avvierebbe prima il servizio, che è un sovraccarico.


2

Devi aggiungere un'autorizzazione come qui sotto per il dispositivo Android 9 quando usi target sdk 28 o successivo o l'eccezione si verificherà sempre:

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

1

Controllo solo il valore PendingIntentnull oppure no prima di chiamare la context.startForegroundService(service_intent)funzione.

questo funziona per me

PendingIntent pendingIntent=PendingIntent.getBroadcast(context,0,intent,PendingIntent.FLAG_NO_CREATE);

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && pendingIntent==null){
            context.startForegroundService(service_intent);
        }
        else
        {
            context.startService(service_intent);
        }
}

In realtà, questo avvia il servizio a startService anziché startForegroundService se null. L'istruzione if dovrebbe essere nidificata, altrimenti l'app si arresterebbe in modo anomalo nel tentativo di utilizzare i servizi su una versione successiva ad un certo punto.
a54studio

1

basta chiamare il metodo startForeground immediatamente dopo la creazione di Service o IntentService. come questo:

import android.app.Notification;
public class AuthenticationService extends Service {

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

1
ECCEZIONE FATALE: principale android.app.RemoteServiceException: notifica non valida per startForeground: java.lang.RuntimeException: canale non valido per la notifica del servizio: notifica (channel = null pri = 0 contentView = null vibrate = null sound = null default = 0x0 flags = 0x40 color = 0x00000000 vis = PRIVATE) su android.app.ActivityThread $ H.handleMessage (ActivityThread.java:1821) su android.os.Handler.dispatchMessage (Handler.java:106) su android.os.Looper.loop (Looper. java: 164) su android.app.ActivityThread.main (ActivityThread.java:6626)
Gerry

1

Ok, qualcosa che ho notato su questo potrebbe aiutare anche alcuni altri. Questo è strettamente dai test per vedere se sono riuscito a capire come risolvere le occorrenze che sto vedendo. Per semplicità, diciamo che ho un metodo che chiama questo dal presentatore.

context.startForegroundService(new Intent(context, TaskQueueExecutorService.class));

try {
    Thread.sleep(10000);
} catch (InterruptedException e) {
    e.printStackTrace();
}       

Questo si bloccherà con lo stesso errore. Il servizio NON si avvierà fino a quando il metodo non sarà completo, quindi nessun onCreate()servizio.

Quindi, anche se aggiorni l'interfaccia utente dal thread principale, SE hai qualcosa che potrebbe trattenere quel metodo dopo di esso, non si avvierà in tempo e ti darà il temuto errore di primo piano. Nel mio caso stavamo caricando alcune cose su una coda e ognuna chiamava startForegroundService, ma una parte della logica era coinvolta con ciascuna in background. Quindi, se la logica impiegava troppo tempo a finire quel metodo da quando erano stati richiamati, il tempo di crash. Il vecchio lo ha startServicesemplicemente ignorato e ha proseguito per la sua strada e dato che lo chiamavamo ogni volta, il round successivo sarebbe finito.

Questo mi ha lasciato meravigliato, se avessi chiamato il servizio da un thread in background, potrebbe non essere completamente legato all'avvio ed eseguito immediatamente, quindi ho iniziato a sperimentare. Anche se questo NON lo avvia immediatamente, non si arresta in modo anomalo.

new Handler(Looper.getMainLooper()).post(new Runnable() {
        public void run() {
               context.startForegroundService(new Intent(context, 
           TaskQueueExecutorService.class));
               try {
                   Thread.sleep(10000);
               } catch (InterruptedException e) {
                  e.printStackTrace();
              }       
        }
});

Non pretenderò di sapere perché non si blocca, anche se sospetto che questo lo costringa ad aspettare fino a quando il thread principale non sarà in grado di gestirlo in modo tempestivo. So che non è l'ideale per legarlo al thread principale, ma dal momento che il mio utilizzo lo chiama in background, non sono davvero preoccupato se attende fino a quando non può essere completato piuttosto che arrestarsi in modo anomalo.


stai iniziando il tuo servizio da background o in attività?
Mateen Chaudhry,

Sfondo. Il problema è che Android non può garantire che il servizio verrà avviato nel tempo assegnato. Si potrebbe pensare che avrebbero semplicemente gettato un errore per ignorarlo se lo si desidera, ma purtroppo non Android. Deve essere fatale. Questo ci ha aiutato nella nostra app, ma non è stato risolto completamente.
a54studio

1

Sto aggiungendo del codice nella risposta @humazed. Quindi non c'è nessuna notifica iniziale. Potrebbe essere una soluzione alternativa, ma funziona per me.

@Override
 public void onCreate() {
        super.onCreate();

        if (Build.VERSION.SDK_INT >= 26) {
            String CHANNEL_ID = "my_channel_01";
            NotificationChannel channel = new NotificationChannel(CHANNEL_ID,
                    "Channel human readable title",
                    NotificationManager.IMPORTANCE_DEFAULT);

            ((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)).createNotificationChannel(channel);

            Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
                    .setContentTitle("")
                    .setContentText("")
                    .setColor(ContextCompat.getColor(this, R.color.transparentColor))
                    .setSmallIcon(ContextCompat.getColor(this, R.color.transparentColor)).build();

            startForeground(1, notification);
        }
}

Sto aggiungendo transparentColor in una piccola icona e il colore su notifica. Funzionerà.


0

Ho risolto il problema con l'avvio del servizio startService(intent)invece di Context.startForeground()e la chiamata startForegound()subito dopo super.OnCreate(). Inoltre, se si avvia il servizio all'avvio, è possibile avviare l'attività che avvia il servizio nella trasmissione di avvio. Sebbene non sia una soluzione permanente, funziona.


in alcuni dispositivi Xiaomi le attività non possono essere avviate in background
Mateen Chaudhry

0

Aggiornamento dei dati in onStartCommand(...)

onBind (...)

onBind(...)è un evento del ciclo di vita migliore da avviare startForegroundvs. onCreate(...)perché onBind(...)passa in un Intentche può contenere dati importanti Bundlenecessari per inizializzare il file Service. Tuttavia, non è necessario come onStartCommand(...)viene chiamato quando Serviceviene creato per la prima volta o chiamato dopo in seguito.

onStartCommand (...)

startForegroundin onStartCommand(...)è importante per aggiornare Serviceuna volta che è già stato creato.

quando ContextCompat.startForegroundService(...) viene chiamato dopo che Serviceè stato creato un onBind(...)e onCreate(...)non vengono chiamati. Pertanto, i dati aggiornati possono essere passati onStartCommand(...)tramite Intent Bundleper aggiornare i dati in Service.

Campione

Sto usando questo modello per implementare il PlayerNotificationManager app di notizie sulla criptovaluta Coinverse.

Activity / Fragment.kt

context?.bindService(
        Intent(context, AudioService::class.java),
        serviceConnection, Context.BIND_AUTO_CREATE)
ContextCompat.startForegroundService(
        context!!,
        Intent(context, AudioService::class.java).apply {
            action = CONTENT_SELECTED_ACTION
            putExtra(CONTENT_SELECTED_KEY, contentToPlay.content.apply {
                audioUrl = uri.toString()
            })
        })

AudioService.kt

private var uri: Uri = Uri.parse("")

override fun onBind(intent: Intent?) =
        AudioServiceBinder().apply {
            player = ExoPlayerFactory.newSimpleInstance(
                    applicationContext,
                    AudioOnlyRenderersFactory(applicationContext),
                    DefaultTrackSelector())
        }

override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
    intent?.let {
        when (intent.action) {
            CONTENT_SELECTED_ACTION -> it.getParcelableExtra<Content>(CONTENT_SELECTED_KEY).also { content ->
                val intentUri = Uri.parse(content.audioUrl)
                // Checks whether to update Uri passed in Intent Bundle.
                if (!intentUri.equals(uri)) {
                    uri = intentUri
                    player?.prepare(ProgressiveMediaSource.Factory(
                            DefaultDataSourceFactory(
                                    this,
                                    Util.getUserAgent(this, getString(app_name))))
                            .createMediaSource(uri))
                    player?.playWhenReady = true
                    // Calling 'startForeground' in 'buildNotification(...)'.          
                    buildNotification(intent.getParcelableExtra(CONTENT_SELECTED_KEY))
                }
            }
        }
    }
    return super.onStartCommand(intent, flags, startId)
}

// Calling 'startForeground' in 'onNotificationStarted(...)'.
private fun buildNotification(content: Content): Unit? {
    playerNotificationManager = PlayerNotificationManager.createWithNotificationChannel(
            this,
            content.title,
            app_name,
            if (!content.audioUrl.isNullOrEmpty()) 1 else -1,
            object : PlayerNotificationManager.MediaDescriptionAdapter {
                override fun createCurrentContentIntent(player: Player?) = ...
                override fun getCurrentContentText(player: Player?) = ...
                override fun getCurrentContentTitle(player: Player?) = ...
                override fun getCurrentLargeIcon(player: Player?,
                                                 callback: PlayerNotificationManager.BitmapCallback?) = ...
            },
            object : PlayerNotificationManager.NotificationListener {
                override fun onNotificationStarted(notificationId: Int, notification: Notification) {
                    startForeground(notificationId, notification)
                }
                override fun onNotificationCancelled(notificationId: Int) {
                    stopForeground(true)
                    stopSelf()
                }
            })
    return playerNotificationManager.setPlayer(player)
}

-1

Servizio

class TestService : Service() {

    override fun onCreate() {
        super.onCreate()
        Log.d(TAG, "onCreate")

        val nBuilder = NotificationCompat.Builder(this, "all")
            .setSmallIcon(R.drawable.ic_launcher_foreground)
            .setContentTitle("TestService")
            .setPriority(NotificationCompat.PRIORITY_DEFAULT)
        startForeground(1337, nBuilder.build())
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        val rtn = super.onStartCommand(intent, flags, startId)

        if (intent?.action == STOP_ACTION) {
            Log.d(TAG, "onStartCommand -> STOP")
            stopForeground(true)
            stopSelf()
        } else {
            Log.d(TAG, "onStartCommand -> START")
        }

        return rtn
    }

    override fun onDestroy() {
        Log.d(TAG, "onDestroy")
        super.onDestroy()
    }

    override fun onBind(intent: Intent?): IBinder? = null

    companion object {

        private val TAG = "TestService"
        private val STOP_ACTION = "ly.zen.test.TestService.ACTION_STOP"

        fun start(context: Context) {
            ContextCompat.startForegroundService(context, Intent(context, TestService::class.java))
        }

        fun stop(context: Context) {
            val intent = Intent(context, TestService::class.java)
            intent.action = STOP_ACTION
            ContextCompat.startForegroundService(context, intent)
        }

    }

}

Tester

val nChannel = NotificationChannel("all", "All", NotificationManager.IMPORTANCE_NONE)
val nManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
nManager.createNotificationChannel(nChannel)

start_test_service.setOnClickListener {
    TestService.start(this@MainActivity)
    TestService.stop(this@MainActivity)
}

Risultato

D/TestService: onCreate
D/TestService: onStartCommand -> START
D/TestService: onStartCommand -> STOP
D/TestService: onDestroy
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.