Android 9.0: Dienst darf nicht gestartet werden: App befindet sich im Hintergrund .. nach onResume ()

73

Ich habe einen Musik-Player, der versucht, ein ServiceIn onResume()eines zu starten Activity. Ich habe aus Gründen der Übersichtlichkeit einige Zeilen entfernt, aber der Code ist effektiv:

@Override
protected void onResume() {
    super.onResume();

    startService(new Intent(this, MusicService.class));
}

Laut den Absturzprotokollen löst dies auf einigen Geräten mit Android P eine Ausnahme aus:

Caused by java.lang.IllegalStateException: Not allowed to start service Intent { cmp=another.music.player/com.simplecity.amp_library.playback.MusicService }: app is in background uid UidRecord{6a4a9c6 u0a143 TPSL bg:+3m25s199ms idle change:cached procs:1 seq(1283,1283,1283)}
       at android.app.ContextImpl.startServiceCommon(ContextImpl.java:1577)
       at android.app.ContextImpl.startService(ContextImpl.java:1532)
       at android.content.ContextWrapper.startService(ContextWrapper.java:664)
       at android.content.ContextWrapper.startService(ContextWrapper.java:664)
       at com.simplecity.amp_library.utils.MusicServiceConnectionUtils.bindToService(SourceFile:36)
       at com.simplecity.amp_library.ui.activities.BaseActivity.bindService(SourceFile:129)
       at com.simplecity.amp_library.ui.activities.BaseActivity.onResume(SourceFile:96)

Wie ist es möglich, dass sich meine App unmittelbar nach dem Aufruf von onResume()(und super.onResume()) im Hintergrund befindet?

Das ergibt für mich keinen Sinn. Könnte dies ein Plattformfehler sein? Alle über 3500 Benutzer, die von diesem Absturz betroffen sind, sind auf Android P.

Tim Malseed
quelle
7
Ich habe keine gute Antwort für Sie, aber ich kann bestätigen, dass wir das auch sehen. Wir haben es nie intern reproduziert, aber wir sehen es auch, wenn wir den Dienst in onResume () starten. Ich vermute, dies ist ein Fehler in Android P.
Kevin Coppock
9
OK, ich bin froh, dass es nicht nur ich bin. Dieses Problem wurde gemeldet: issuetracker.google.com/issues/113122354
Tim Malseed
2
Mögliches Duplikat von Android 8.0: java.lang.IllegalStateException: Dienst Intent darf nicht gestartet werden . Es wurde später gefragt, hat aber eine Antwort.
Zugriff verweigert
3
Außerdem müssen Apps, die auf Android 9 oder höher abzielen und Vordergrunddienste verwenden, die Berechtigung FOREGROUND_SERVICE anfordern. Dies ist eine normale Berechtigung, daher erteilt das System sie automatisch der anfordernden App. Von developer.android.com/about/versions/pie/android-9.0-changes-28
Zugriff verweigert
2
@iaindownie Ja, es scheint funktioniert zu haben, es ist jetzt schon eine kurze Zeit in Arbeit, und neue Instanzen, die ich nicht behoben hatte (von denen ich damals noch nichts wusste), tauchten auf, seit es weiter fortgeschritten war.
Ben987654

Antworten:

12

Es gibt eine Problemumgehung von Google:

Das Problem wurde in zukünftigen Android-Versionen behoben.

Es gibt eine Problemumgehung, um einen Absturz der Anwendung zu vermeiden. Anwendungen können den Prozessstatus in Activity.onResume () durch Aufrufen von ActivityManager.getRunningAppProcesses () abrufen und das Starten des Dienstes vermeiden, wenn die Wichtigkeitsstufe unter ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND liegt. Wenn das Gerät nicht vollständig aktiviert ist, werden die Aktivitäten sofort angehalten und nach dem vollständigen Aktivieren wieder fortgesetzt.

Also ich denke es sollte so aussehen:

// hack for https://issuetracker.google.com/issues/113122354
    List<ActivityManager.RunningAppProcessInfo> runningAppProcesses = activityManager.getRunningAppProcesses();
    if (runningAppProcesses != null) {
        int importance = runningAppProcesses.get(0).importance;
        // higher importance has lower number (?)
        if (importance <= ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND)
            URLPlayerService.startActionBroadcastServiceData(PlayerActivity.this);
    }

Ich habe den Handler als Problemumgehung verwendet und er funktioniert ziemlich gut, aber nicht zu 100%:

// hack for https://issuetracker.google.com/issues/113122354
   handler.postDelayed(() -> URLPlayerService.startService(PlayerActivity.this),200);
Mateusz Kaflowski
quelle
4
Wie im issuetracker gesagt: "Aktivitäten würden sofort unterbrochen und schließlich wieder aufgenommen, nachdem sie vollständig wach sind." Daher wird die Aktivität wieder aufgenommen und die Prozessbedeutung wird IMPORTANCE_FOREGROUND sein, denke ich. Sie müssen also nicht handhaben. ist es wahr?
David
2
Der obige 'Hack' hat bei mir funktioniert (Hinweis: Alle in diesem Fall für dieses Problem gemeldeten Abstürze waren auf Samsung-Plattformen mit Android 9+). Fügen Sie der Vollständigkeit halber die folgende Zeile über dem 'Hack' hinzu, um den activityManager zu definieren: "ActivityManager activityManager = (ActivityManager) this.getSystemService (Context.ACTIVITY_SERVICE);"
GoZo
@ David Hast du es ohne den Handler versucht?
Xi Wei
@ XiWei Ja, ich habe es getestet und es ist in Ordnung. Verwenden Sie einfach startService, nachdem Sie die Wichtigkeit in onResume überprüft haben.
David
6

UPDATE: Dies funktioniert für uns in Prod, aber es ist nicht 100%. Ich habe in den letzten anderthalb Monaten einen Absturzbericht erhalten, in dem es sonst weit über hundert gegeben hätte. Bis dies richtig behoben ist, scheint dies unsere derzeit beste Option zu sein. Vielleicht wäre ein Absturz nie passiert, wenn ich die Zeit über 300 hinaus erhöht hätte?

Wir testen dies gerade, was bisher zu funktionieren scheint. Wird aktualisiert, sobald wir weitere Ergebnisse sehen

class ResumingServiceManager(val lifecycle: Lifecycle) : LifecycleObserver {

    init {
        lifecycle.addObserver(this)
    }

    val disposable: CompositeDisposable = CompositeDisposable()

    fun startService(context: Context, intent: Intent) {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
            context.startService(intent)
        } else {
            Single.just(true)
                    .delaySubscription(300, TimeUnit.MILLISECONDS)
                    .subscribeOn(AndroidSchedulers.mainThread())
                    .observeOn(AndroidSchedulers.mainThread())
                    .subscribeBy(
                            onSuccess = {
                                context.startService(intent)
                            }

                    ).addTo(disposable)
        }
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
    fun stopped() {
        disposable.clear()
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
    fun destroy() {
        lifecycle.removeObserver(this)
    }
}

In onCreate()initialize es und will dann , wann immer Sie einen Dienst in onResume nur Anruf startenresumingServiceManager.startService(this, intent)

Es ist lebenszyklusabhängig, sodass das Einwegprodukt gelöscht wird, wenn der onSuccess nicht mehr ausgelöst wird, wenn er möglicherweise mit einem sofortigen Öffnen / Schließen in den Hintergrund tritt.

Ben987654
quelle
1
Wäre dies für den Fall Oreo + gleichbedeutend mit: handler.postDelayed({context.startService(intent)}, 300)und in der stopped()Berufung handler.removeCallbacksAndMessages(null)?
Carmen
1
Ich glaube das würde ja genauso funktionieren. Sie können es jederzeit testen, indem Sie zusätzliche Protokollierung hinzufügen und die Zeit auf ein oder zwei Sekunden einstellen, um die Verzögerung / ordnungsgemäße Entfernung und die Fähigkeit zum Auslösen zu bestätigen, wenn Sie die App wiederholt öffnen / schließen.
Ben987654
Kann jemand bestätigen, dass die oben veröffentlichte Nicht-Rx-Version mit handler.postDelayed
Rinav
@rinav Ich würde an dieser Stelle die Verwendung der von Googles offiziell empfohlenen Lösung prüfen. Ich habe die obige Lebenszykluslogik herausgenommen und ihre Korrektur in den else-Zweig anstelle des Rx gestellt, um Abstürze zu vermeiden. Die Frage ist nur, ob es absolut zuverlässig ist oder nicht, was ich noch nicht weiß, aber wir werden es herausfinden, sobald wir bald eine Veröffentlichung machen. Die obige Rx-Version reduzierte zwar Abstürze drastisch, aber ich bekam immer noch 3-4 pro Monat, als ich jeden Anruf ersetzte, anstatt 400-500. Die gute Nachricht ist jedoch, dass das Weiterleiten aller Anrufe über diese eine Datei das Austauschen von Korrekturen erleichtert hat!
Ben987654
4

Dies wurde im Android Issue Tracker als "behoben" markiert:

Vermutlich wird das Update in einer der Android Q-Versionen veröffentlicht.

Laut dem Googler, der die Ausgabe geschlossen hat,

Es gibt eine Problemumgehung, um einen Absturz der Anwendung zu vermeiden. Anwendungen können den Prozessstatus Activity.onResume()durch Aufrufen erhalten ActivityManager.getRunningAppProcesses()und das Starten des Dienstes vermeiden, wenn die Wichtigkeitsstufe niedriger als ist ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND. Wenn das Gerät nicht vollständig aktiviert ist, werden die Aktivitäten sofort angehalten und nach dem vollständigen Aktivieren wieder fortgesetzt.

Tim Malseed
quelle
3

Unser Team hatte das gleiche Problem. Mein Kalender zeigt den 5. April 2019 an, aber das Problem wird immer noch auf meinem Samsung Galaxy S9 +, Android 9.0 (One UI) reproduziert.

Wir starten den Service in onResume und lösen die Bindung von onPause.

Wie zu reproduzieren

Sperren Sie einfach Ihr Gerät, wenn Aktivitäten mit dieser Logik auf dem Bildschirm angezeigt werden, und berühren Sie 10-15 Minuten nicht. Nach dem Entsperren stürzt die App ab.

Wie repariert man

Wir haben eine Lösung gefunden, die tatsächlich funktioniert. Starten Sie seit Android 8+ Ihren Dienst in android.os.Handler.post (...)

Beispiel (Kotlin):

override fun onResume() {
    super.onResume()
    Handler().post {
        val serviceIntent = Intent(activity, SomeService::class.java)
        activity?.startService(serviceIntent)
        activity?.bindService(serviceIntent, serviceConnection, Context.BIND_AUTO_CREATE)
    }
}

Viel Glück!

Рома Богдан
quelle
Ich habe diese Änderung vorgenommen, aber SM-A750FN-Telefone unter Android 9 stürzen immer noch mit demselben Fehler ab.
Vajk Hermecz
0

Es scheint, dass die Fehlermeldung nicht eindeutig ist, und ich habe festgestellt, dass sich der letzte Teil der Nachricht auf die Anwendung bezieht, in der sich der Dienst befindet, und NICHT auf die Anwendung, die versucht, den Dienst zu starten und an ihn zu binden.

Der Fehler, den ich erhalte, wenn die Anwendung des Dienstes nicht im Vordergrund steht (obwohl der Dienst selbst ein Hintergrunddienst ist):

W/System.err: java.lang.IllegalStateException: Not allowed to start service Intent { cmp=com.mycompany.myserviceapp/.MyService }: app is in background uid null
        at android.app.ContextImpl.startServiceCommon(ContextImpl.java:1505)
        at android.app.ContextImpl.startService(ContextImpl.java:1461)
        at android.content.ContextWrapper.startService(ContextWrapper.java:644)

Wenn ich nun die Anwendung mit dem Dienst ausführe, sodass sie unmittelbar vor dem Start der "Client" -App im Vordergrund steht, die versucht, den Dienst zu starten, funktioniert alles gut für den Dienststart.

Wenn es sich bei dem Dienst um einen "Hintergrunddienst" handelt und die App für den Dienst nicht im Vordergrund steht, wird der Dienst möglicherweise beendet. Daher reicht es nicht aus, den Dienst zu starten, wenn Sie ihn benötigen, um in der Nähe zu bleiben.

Dies scheint ein Ergebnis der Änderungen von Android 7 zu Android 8 zu sein, bei denen in Oreo begonnen wurde, die Dinge ein wenig einzuschränken.

Alles wird hier in den Android-Dokumenten zu den Hintergrundausführungsgrenzen von Oreo erklärt

In den Dokumenten wird empfohlen, Ihre Logik auf geplante Jobs zu migrieren oder den Dienst zu einem Vordergrunddienst zu machen. Dort wird visuell angezeigt, dass der Dienst ausgeführt wird.

m0bl
quelle
-3

Vielleicht könnte android.arch.lifecycle als Workaround für diesen Android 9-Fehler verwendet werden?

public class MyActivity extends Activity implements LifecycleObserver {

    protected void onResume() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            if (ProcessLifecycleOwner.get().getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.RESUMED)) {
                startService(intent);
            } else {
                ProcessLifecycleOwner.get().getLifecycle().addObserver(this);
            }
        } else {
            startService(intent);
        }
    }


    @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
    void onEnterForeground() {
        startService(intent);
        ProcessLifecycleOwner.get().getLifecycle().removeObserver(this);
    }
}

Ich habe es hier gefunden.

MarcinLe
quelle
1
Der Lebenszyklusbeobachter reagiert nur auf Ereignisse, die durch die Aktivität ausgelöst werden. Das alles läuft also wirklich darauf hinaus, nur den startService()Anruf zu verschieben onStart(). Wie Sie jedoch sagen, könnte dies eine praktische Problemumgehung sein.
Tim Malseed
1
Leider sehe ich die gleiche Ausnahme mit Lifecycle.State.STARTED. Ich werde es versuchen Lifecycle.State.RESUMEDund @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)das wird danach aufgerufen onResume()und sehen, ob das das Problem behebt.
MarcinLe
Vielen Dank, dass Sie diese Problemumgehung geteilt haben. Hatten Sie positive Ergebnisse bei der Verwendung von Lifecycle.State.RESUMED und @OnLifecycleEvent (Lifecycle.Event.ON_RESUME)?
Renso Lohuis
Ich habe meine App aktualisiert, um State.RESUMED zu verwenden. Für ein paar Tage hat es gut funktioniert, also hoffte ich, dass dies das Problem löst, aber ich habe gerade wieder einen Absturz auf Android 9 bemerkt :(
MarcinLe
Ja, überhaupt nicht überrascht. Auch hier werden Arch Lifecycle-Ereignisse nur durch die Lifecycle-Ereignisse des zugrunde liegenden Fragments / der zugrunde liegenden Aktivität ausgelöst. Dies unterscheidet sich also nicht vom Verschieben des startServiceAnrufs in onStart()oder onResume()oder wo auch immer, es sei denn, Sie berücksichtigen sehr winzige Zeitverzögerungen. Klingt wie der Anruf zu bewegen onStart()ist nicht eine Abhilfe.
Tim Malseed