webentwicklung-frage-antwort-db.com.de

START_STICKY funktioniert nicht mit Android KitKat

Eine meiner Apps verfügt über einen Backgrouod-Dienst, der den Rückkehrcode START_STICKY Von onStartCommand verwendet, um automatisch neu zu starten, wenn das System ihn beendet. Es scheint, dass dies nicht mehr funktioniert Android KitKat. Gibt es eine Lösung für dieses Problem? Sollte ich etwas anderes in KitKat tun, um den Dienst am Laufen zu halten?

Hinweis: Es gibt eine ähnliche Diskussion in der Android-Devlopers-Gruppe über das Wischen der App aus der Liste der zuletzt verwendeten Apps. Könnten diese beiden Probleme zusammenhängen? https://groups.google.com/forum/#!topic/Android-developers/H-DSQ4-tiac

Edit: Sah, dass es offene Bugs auf Android Issue Tracker:

https://code.google.com/p/Android/issues/detail?id=6379https://code.google.com/p/Android/issues/detail? id = 63618

Edit2: Das gleiche passiert auch, wenn der Dienst mit startForeground in einem separaten Prozess und mit dem Flag Android:stopWithTask="false" In der AndroidManifest.xml-Datei ausgeführt wird ...

Edit3: Weitere verwandte Bugs bei Android Issue Tracker:

https://code.google.com/p/Android/issues/detail?id=62091https://code.google.com/p/Android/issues/detail? id = 5331https://code.google.com/p/Android/issues/detail?id=104308

Gibt es eine Problemumgehung, um das vorherige Verhalten zu erhalten?

51
Muzikant

Dies ist keine 100% funktionierende Lösung, aber die beste, da sie das Problem fast vollständig beseitigt. Bisher habe ich diese Lösung zusammen mit dem Überschreiben von onTaskRemoved (Siehe diese Antwort ) und einer Keep-Alive-Benachrichtigung (Siehe diese Antwort ) integriert. Zusätzliche Antworten sind sehr willkommen!

Nach weiteren Untersuchungen scheint es, dass der Fehler in Jelly Bean bereits vorhanden ist, und es scheint, dass es eine Lösung dafür gibt (zumindest in meinem Fall scheint dies zu funktionieren. Ich werde weiter testen und die Antwort aktualisieren, falls erforderlich).

Soweit ich weiß, geschieht dies nur mit Diensten, die von AlarmManager gesetzte Sendungen empfangen.

Gehen Sie folgendermaßen vor, um den Fehler zu reproduzieren:

  1. Starten Sie die App
  2. starten Sie den Dienst als Vordergrunddienst (verwenden Sie dazu startForeground) in der App
  3. Wischen Sie die App von der Liste "Zuletzt verwendete Apps"
  4. Senden Sie eine Sendung, die vom Dienst verarbeitet wird
  5. Der Dienst wird getötet!

Mit adb Shell dumpsys >C:\dumpsys.txt Können Sie den Status des Dienstes zwischen den verschiedenen Schritten überwachen. (suchen Sie in der dumpsys-Ausgabe nach Process LRU list) In den Schritten 2 und 3 sehen Sie ungefähr Folgendes:

Proc # 2: prcp  F/S/IF trm: 0 11073:<your process name>/u0a102 (fg-service)

Beachten Sie insbesondere den F/S/IF Und den (fg-service), Die angeben, dass der Dienst als Vordergrunddienst ausgeführt wird (weitere Informationen zum Analysieren des dumpsys finden Sie unter diesem Link: https: // stackoverflow .com/a/14293528/624109 ).

Nach Schritt 4 sehen Sie Ihren Dienst nicht mehr im Process LRU list. Stattdessen können Sie sich das Geräteprotokoll ansehen und Folgendes sehen:

I/ActivityManager(449): Killing 11073:<your process name>/u0a102 (adj 0): remove task

Was dieses Verhalten zu verursachen scheint, ist die Tatsache, dass die empfangene Sendung den Dienst aus dem Vordergrund entfernt und dann beendet.

Um dies zu vermeiden, können Sie diese einfache Lösung verwenden, wenn Sie PendingIntent für AlarmManager erstellen (Quelle: - https://code.google.com/p/Android/issues/detail?id=53313#c7 )

AlarmManager am = (AlarmManager)getSystemService(Context.ALARM_SERVICE);
Intent intent = new Intent("YOUR_ACTION_NAME");
intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 1, intent, 0);

Beachten Sie die folgenden Schritte:

  1. Rufe addFlags auf und verwende FLAG_RECEIVER_FOREGROUND
  2. Verwenden Sie in PendingIntent.getBroadcast einen Anforderungscode ungleich Null

Wenn Sie einen dieser Schritte weglassen, funktioniert dies nicht.

Beachten Sie, dass FLAG_RECEIVER_FOREGROUND in API 16 (Jelly Bean) hinzugefügt wurde. Daher ist es sinnvoll, dass dies der Zeitpunkt ist, an dem der Fehler zum ersten Mal auftrat ...

Höchstwahrscheinlich ist KitKat nur aggressiver, wenn es um das Töten von Prozessen geht. Aus diesem Grund wurde es bei KitKat betont, aber es scheint, dass dies bereits für Jelly Bean relevant war.

Hinweis 2: Beachten Sie die Details in der Frage zur Dienstkonfiguration, die in einem separaten Prozess als Vordergrunddienst ausgeführt wird, wobei endWithTask im Manifest auf false festgelegt ist.

Hinweis 3: Dasselbe passiert, wenn die App die Nachricht Android.appwidget.action.APPWIDGET_CONFIGURE Empfängt und eine Konfigurationsaktivität für ein neues Widget anzeigt (Ersetzen Sie Schritt 4 oben durch das Erstellen eines neuen Widgets). Ich habe festgestellt, dass dies nur geschieht, wenn der Widget-Anbieter (der Empfänger, der Android.appwidget.action.APPWIDGET_UPDATE Verarbeitet) so eingestellt ist, dass er auf einem anderen Prozess als dem Aktivitätsprozess ausgeführt wird. Nachdem dies so geändert wurde, dass sich sowohl die Konfigurationsaktivität als auch der Widget-Anbieter auf demselben Prozess befinden, geschieht dies nicht mehr.

16
Muzikant

Scheint, dass dies ein Fehler ist, der in Android 4.4) vorhanden ist und mit folgendem umgangen wurde:

@Override
public void onTaskRemoved(Intent rootIntent) {
    Intent restartService = new Intent(getApplicationContext(),
            this.getClass());
    restartService.setPackage(getPackageName());
    PendingIntent restartServicePI = PendingIntent.getService(
            getApplicationContext(), 1, restartService,
            PendingIntent.FLAG_ONE_SHOT);
    AlarmManager alarmService = (AlarmManager)getApplicationContext().getSystemService(Context.ALARM_SERVICE);
    alarmService.set(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime() +1000, restartServicePI);

}

Fand diese Antwort von dieser Beitrag

28

Das Problem hier scheint bei AOSP-basierten ROMs nicht aufzutreten. Das heißt, ich kann dies problemlos auf einem CyanogenMod 11-basierten ROM nachbilden, aber auf einem AOSP ROM (und auf einem Emulator)) verhält sich START_STICKY genau so, wie ich es erwartet hätte Wenn Sie Berichte von Leuten in Nexus 5 sehen, die dieses Verhalten zu bemerken scheinen, ist es möglicherweise immer noch ein Problem in AOSP.

In einem Emulator und in einem AOSP-ROM wird in einem Logcat Folgendes angezeigt, wenn ich einen Kill 5838 gegen den Prozess ausführe (wie erwartet):

12-22 18:40:14.237 D/Zygote  (   52): Process 5838 terminated by signal (15)
12-22 18:40:14.247 I/ActivityManager(  362): Process com.xxxx (pid 5838) has died.
12-22 18:40:14.247 W/ActivityManager(  362): Scheduling restart of crashed service com.xxxx/com.xxxx.NotifyingService in 5000ms
12-22 18:40:19.327 I/ActivityManager(  362): Start proc com.xxxx for service xxxx.pro/com.xxxx.NotifyingService: pid=5877 uid=10054 gids={50054, 3003, 3002, 1028}

Ich sehe das gleiche Neustartverhalten, wenn ich die Aufgabe beende, indem ich aus der Liste der zuletzt ausgeführten Aufgaben wische. Das ist also alles gut - es bedeutet, dass sich der Kern-AOSP-Code so verhält wie in früheren Levels.

Ich schaue mir den Cyanogenmod-Servicecode an, um herauszufinden, warum ein Neustart nicht geplant ist - noch kein Glück. Es scheint, dass es es neu planen sollte. Cyanogenmod verwendet eine Service-Map, die von AOSP nicht verwendet wird - es ist jedoch unklar, ob dies ein Problem ist oder nicht (zweifelhaft) https://github.com/CyanogenMod/Android_frameworks_base/blob/cm-11.0/services/Java/com /Android/server/am/ActiveServices.Java#L2092

Eine etwas hackige Lösung ist, einen ähnlichen Mechanismus wie Ihren onTaskRemoved-AlarmService zu verwenden, um einen Alarm für X Minuten später zu aktivieren. Dann können Sie alle paar Minuten, während Ihre App läuft, den Alarm zurücksetzen. Er wird also nur dann ausgelöst, wenn die Dinge wirklich beendet und nicht neu gestartet wurden. Dies ist nicht kinderleicht - wenn Sie einen Handler verwenden, haben Sie eine längere Betriebszeit als den Alarmdienst, der Echtzeit verwendet. Daher kann Ihr Alarm ausgelöst werden, obwohl er zu einem längeren Zeitpunkt als Ihr 'Reset'-Handler eingestellt wurde. Wenn Sie jedoch eine zusätzliche Absicht festlegen, können Sie den Befehl onStartCommand ignorieren, wenn Ihr Dienst bereits aktiv war, und dies in einen Noop verwandeln.

Ich bin überhaupt kein Fan des folgenden Hack - aber es sollte keinen wirklichen Schaden anrichten. Wenn der Benutzer ein explizites Schließen erzwingt, zerstört der Alarmmanager alle eingestellten Alarme, sodass der Dienst nicht neu gestartet wird (was der Benutzer wünscht).

Erstellen Sie zunächst eine Hilfsmethode, mit der ein Alarm für 20 Minuten festgelegt wird, wodurch onStartCommand für Ihren Dienst ausgelöst wird. Alle 2 Minuten muss ein Handler den 20-Minuten-Alarm zurücksetzen. Wenn der Handler innerhalb von 20 Minuten in Echtzeit ausgeführt wird, wird der Alarm niemals ausgelöst. Es wird jedoch nicht garantiert, dass der Handler ausgeführt wird, wenn das Gerät schläft (was gut ist).

private void ensureServiceStaysRunning() {
    // KitKat appears to have (in some cases) forgotten how to honor START_STICKY
    // and if the service is killed, it doesn't restart.  On an emulator & AOSP device, it restarts...
    // on my CM device, it does not - WTF?  So, we'll make sure it gets back
    // up and running in a minimum of 20 minutes.  We reset our timer on a handler every
    // 2 minutes...but since the handler runs on uptime vs. the alarm which is on realtime,
    // it is entirely possible that the alarm doesn't get reset.  So - we make it a noop,
    // but this will still count against the app as a wakelock when it triggers.  Oh well,
    // it should never cause a device wakeup.  We're also at SDK 19 preferred, so the alarm
    // mgr set algorithm is better on memory consumption which is good.
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KitKat)
    {
        // A restart intent - this never changes...        
        final int restartAlarmInterval = 20*60*1000;
        final int resetAlarmTimer = 2*60*1000;
        final Intent restartIntent = new Intent(this, NotifyingService.class);
        restartIntent.putExtra("ALARM_RESTART_SERVICE_DIED", true);
        final AlarmManager alarmMgr = (AlarmManager)getSystemService(Context.ALARM_SERVICE);
        Handler restartServiceHandler = new Handler()
        {
            @Override
            public void handleMessage(Message msg) {
                // Create a pending intent
                PendingIntent pintent = PendingIntent.getService(getApplicationContext(), 0, restartIntent, 0);
                alarmMgr.set(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime() + restartAlarmInterval, pintent);
                sendEmptyMessageDelayed(0, resetAlarmTimer);
            }            
        };
        restartServiceHandler.sendEmptyMessageDelayed(0, 0);  
    }
}

In Ihrem onCreate können Sie diese Methode aufrufen. Außerdem sollten Sie dies in Ihrem onStartCommand ignorieren, wenn Ihr Dienst bereits aktiv ist. Z.B:

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    ...
    if ((intent != null) && (intent.getBooleanExtra("ALARM_RESTART_SERVICE_DIED", false)))
    {
        Log.d(TAG, "onStartCommand after ALARM_RESTART_SERVICE_DIED");
        if (IS_RUNNING)
        {
            Log.d(TAG, "Service already running - return immediately...");
            ensureServiceStaysRunning();
            return START_STICKY;
        }
    }
    // Do your other onStartCommand stuff..
    return START_STICKY;
}
22
George Tanner

ich fand diesen einfachen Trick, um dieses Problem zu lösen, ohne AlarmManager zu verwenden.

  1. erstellen Sie einen Rundfunkempfänger, der bei jedem Aufruf der Methode onDestroy() in service die Rundfunkübertragung abhört:

    public class RestartService extends BroadcastReceiver {
    
    private static final String TAG = "RestartService";
    
    public RestartService() {
    }
    
    @Override
    public void onReceive(Context context, Intent intent) {
    Log.e(TAG, "onReceive");
    context.startService(new Intent(context, YourService.class));
    }
    }
    
  2. fügen Sie Ihrem Manifest benutzerdefinierte Broadcast-Absichten hinzu

    <receiver
        Android:name=".RestartService"
        Android:enabled="true" >
        <intent-filter>
            <action Android:name="restartApps" />
        </intent-filter>
    </receiver>
    
  3. dann sende eine Sendung von onDestroy(), wahrscheinlich so:

    @Override
    public void onDestroy() {
    Intent intent = new Intent("restartApps");
    sendBroadcast(intent);
    super.onDestroy();
    stopThread();
    }
    
  4. rufe onDestroy() von onTaskRemoved(Intent intent) auf

dieser Trick startet Ihren Dienst jedes Mal neu, wenn der Benutzer den Dienst vom Task-Manager aus schließt und das Schließen der Einstellungen erzwingt. Ich hoffe, dies hilft Ihnen auch

8
baskara