webentwicklung-frage-antwort-db.com.de

Minimaler Android-Vordergrunddienst auf High-End-Telefon gestorben

Ich versuche, eine App zu erstellen, mit der Benutzer Routen (Standorte/GPS) protokollieren können. Um sicherzustellen, dass Standorte auch bei ausgeschaltetem Bildschirm protokolliert werden, habe ich einen foreground service für die Standortprotokollierung erstellt. Ich speichere die Standorte in einem Room Database, der mit Dagger2 in meinen Dienst eingefügt wird.

Allerdings wird dieser Dienst von Android getötet, was natürlich nicht gut ist. Ich könnte Warnungen zu wenig Arbeitsspeicher abonnieren, aber das Grundproblem, dass mein Dienst nach ca. 30 Minuten auf einem modernen High-End-Telefon mit Android 8.0 beendet wird, ist damit nicht behoben

Ich habe ein minimales Projekt mit nur einer "Hallo Welt" -Aktivität und dem Dienst erstellt: https://github.com/RandomStuffAndCode/AndroidForegroundService

Der Dienst wird in meiner Application-Klasse gestartet, und die Routenprotokollierung wird über eine Binder gestartet:

// Application
@Override
public void onCreate() {
    super.onCreate();
    mComponent = DaggerAppComponent.builder()
            .appModule(new AppModule(this))
            .build();

    Intent startBackgroundIntent = new Intent();
    startBackgroundIntent.setClass(this, LocationService.class);
    startService(startBackgroundIntent);
}

// Binding activity
bindService(new Intent(this, LocationService.class), mConnection, Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT);
// mConnection starts the route logging through `Binder` once connected. The binder calls startForeground()

Wahrscheinlich brauche ich die BIND_AUTO_CREATE-Flagge nicht. Ich habe verschiedene Flaggen getestet, um meinen Dienst nicht töten zu lassen - bisher kein Glück.

Mit dem Profiler scheint es nicht so zu sein, als hätte ich Speicherlecks. Die Speichernutzung ist stabil bei ~ 35 MB:

 profiler

Mit adb Shell dumpsys activity processes > tmp.txt kann ich bestätigen, dass foregroundServices=true und mein Dienst auf Platz 8 der LRU-Liste aufgeführt sind:

Proc # 3: prcp F/S/FGS trm: 0 31592:com.example.foregroundserviceexample/u0a93 (fg-service)

Es scheint nicht möglich zu sein, einen Vordergrunddienst zu erstellen, bei dem Sie darauf vertrauen können, dass er nicht getötet wird. Also was können wir tun? Gut...

  1. Versetzen Sie den Dienst in einen separaten Prozess, in dem versucht wird, Android die Benutzeroberfläche/Aktivitäten beenden zu lassen, während der Dienst in Ruhe gelassen wird. Würde wahrscheinlich helfen, scheint aber keine Garantie zu sein
  2. Behalten Sie alles im Dienst bei, z. eine Raumdatenbank. Jede Variable, jede benutzerdefinierte Klasse, jedes Mal, wenn sich etwas ändert, und dann den Dienst mit START_STICKY starten. Dies scheint verschwenderisch und führt nicht zu sehr schönem Code, aber es würde wahrscheinlich funktionieren ... etwas. Je nachdem, wie lange Android benötigt, um den Dienst nach dem Beenden neu zu erstellen, kann ein großer Teil der Standorte verloren gehen.

Ist das wirklich der aktuelle Stand der Dinge im Hintergrund auf Android? Gibt es keinen besseren Weg?

BEARBEITEN: Das Whitelisting der App zur Batterieoptimierung (Deaktivieren) verhindert nicht, dass mein Dienst beendet wird

BEARBEITEN: Die Verwendung von Context.startForegroundService() zum Starten des Dienstes verbessert die Situation nicht

EDIT: Das tritt also zwar nur bei einigen Geräten auf, aber es tritt konsequent bei ihnen auf. Ich denke, Sie müssen sich entscheiden, ob Sie nicht eine große Anzahl von Benutzern unterstützen oder wirklich hässlichen Code schreiben möchten. Genial.

16
user1202032

Ein von startForeground gestarteter Dienst gehört zur zweitwichtigsten Gruppe Sichtbarer Prozess :

  1. Ein sichtbarer Prozess führt die Arbeit aus, die dem Benutzer derzeit bekannt ist, ein Töten hätte einen spürbaren negativen Einfluss auf den Benutzer Erfahrung. Ein Prozess wird im Folgenden als sichtbar betrachtet Bedingungen:

    1. Es führt eine Aktivität aus, die für den Benutzer auf dem Bildschirm sichtbar ist, jedoch nicht im Vordergrund (die onPause () -Methode wurde aufgerufen). Diese kann beispielsweise auftreten, wenn die Vordergrundaktivität als .__ angezeigt wird. Ein Dialog, der es ermöglicht, die vorherige Aktivität dahinter zu sehen.

    2. Es hat einen Dienst, der als Vordergrunddienst ausgeführt wird, über Service.startForeground () (der das System auffordert, den -Dienst als etwas zu behandeln, das dem Benutzer bekannt ist oder für .__ im Wesentlichen sichtbar ist.) .

    3. Es hostet einen Dienst, den das System für eine bestimmte Funktion verwendet, die der Benutzer kennt, z. B. ein Live-Hintergrundbild, Eingabemethode Service usw.

    Die Anzahl dieser Prozesse im System ist weniger begrenzt als Vordergrundprozesse, aber immer noch relativ kontrolliert. Diese Prozesse gelten als äußerst wichtig und werden nicht abgebrochen es sei denn, dies ist erforderlich, um alle Vordergrundprozesse laufen zu lassen.

Davon abgesehen können Sie nie sicher sein, dass Ihr Dienst zu keinem Zeitpunkt beendet wird. Z.B. Gedächtnisdruck, schwache Batterie usw. Siehe wer-lebt-und-wer-stirbt


Im Grunde haben Sie die Frage selbst beantwortet. Der Weg ist START_STICKY :

Für gestartete Dienste gibt es zwei weitere Hauptmodi von Operation können sie entscheiden, ob sie ausgeführt werden. Rückkehr von onStartCommand():START_STICKY wird für Dienste verwendet, die werden nach Bedarf explizit gestartet und gestoppt, während START_NOT_STICKY oder START_REDELIVER_INTENT werden für Dienste verwendet, die nur während der Verarbeitung aller an sie gesendeten Befehle weiterarbeiten. Siehe die verlinkte Dokumentation für mehr Details zur Semantik.

Als allgemeine Richtlinie sollten Sie im Hintergrund (orer Vordergrund) so wenig wie möglich tun, d. H. Nur die Standortverfolgung durchführen und alles andere in Ihrer Vordergrundaktivität behalten. Nur das Tracking sollte sehr wenig Konfiguration erfordern und kann schnell geladen werden. Je kleiner Ihr Service ist, desto unwahrscheinlicher wird er getötet. Ihre Aktivität wird von dem System in dem Zustand wiederhergestellt, der vor dem Hintergrund war, solange es nicht ebenfalls getötet wird. Ein "Kaltstart" der Vordergrundaktivität hingegen sollte kein Problem sein. 
Ich halte das nicht für hässlich, weil dies garantiert, dass das Telefon dem Benutzer immer die beste Erfahrung bietet. Das ist das Wichtigste, was es zu tun hat. Dass einige Geräte Dienste nach 30 Minuten schließen (möglicherweise ohne Benutzerinteraktion), ist unglücklich.

Wie Sie gesagt haben, müssen Sie also

Behalten Sie alles im Dienst in z. eine Raumdatenbank. Jeden Variable, jede benutzerdefinierte Klasse, jedes Mal, wenn sich eine von ihnen ändert, und dann Starten Sie den Dienst mit START_STICKY. 

Siehe Erstellen eines nie endenden Dienstes

Implizite Frage: 

Abhängig davon, wie lange es dauert, bis Android das .__ neu erstellt. Wenn Sie den Service beenden, kann ein großer Teil der Standorte verloren gehen.

Dies dauert in der Regel nur sehr kurz. Vor allem, weil Sie den Fused Location Provider Api für die Standortaktualisierungen verwenden können. Dies ist ein unabhängiger Systemdienst, und es ist sehr unwahrscheinlich, dass er beendet wird. Es hängt also hauptsächlich von der Zeit ab, die Sie benötigen, um den Dienst in onStartCommand neu zu erstellen. 

Beachten Sie auch, dass Sie ab Android 8.0 eine .__ verwenden müssen. forground service wegen der Hintergrundposition Grenzen .


Edit: Wie kürzlich in den Nachrichten beschrieben: Einige Hersteller geben Ihnen möglicherweise Schwierigkeiten, Ihren Service aufrecht zu erhalten. Die Site https://dontkillmyapp.com/ verfolgt die Hersteller und mögliche Abhilfemaßnahmen für Ihr Gerät. Oneplus ist derzeit (29.01.19) einer der schlimmsten Straftäter.

Bei der Veröffentlichung der Telefone 1 + 5 und 1 + 6 führte OnePlus eines der .__ ein. die bisher strengsten Hintergrundlimits auf dem Markt, sogar Zwerge die von Xiaomi oder Huawei. Benutzer mussten nicht nur .__ aktivieren. zusätzliche Einstellungen, damit ihre Apps ordnungsgemäß funktionieren, aber diese Einstellungen Sie können sogar mit einem Firmware-Update zurückgesetzt werden, so dass Apps erneut brechen und Benutzer sind erforderlich, um diese Einstellungen regelmäßig wieder zu aktivieren.

Lösung für Benutzer

Deaktivieren Sie Systemeinstellungen> Apps> Zahnradsymbol> Spezieller Zugriff> Batterie Optimierung.

leider gibt es

Keine bekannte Lösung auf der Entwicklerseite

5
leonardkraemer

Ich weiß, dass es spät ist, aber das könnte jemandem helfen. Auch ich hatte das Problem, den Vordergrunddienst am Leben zu erhalten, ohne von Betriebssystemen verschiedener Hersteller getötet zu werden. Die meisten Betriebssysteme chinesischer Hersteller beenden den Vordergrunddienst, auch wenn er zu den Ausnahmelisten (Batterie, Reiniger usw.) hinzugefügt wird und automatisch gestartet werden darf.

Ich fand dieses Link , das mir das langjährige Problem der Aufrechterhaltung des Dienstes ersetzte.

Sie müssen lediglich Ihren Vordergrunddienst in einem separaten Prozess ausführen. Das ist es.

Sie können dies tun, indem Sie Android:process Zu Ihrem Dienst in Ihrer AndroidManifest.xml hinzufügen.

Zum Beispiel:

<service Android:name=".YourService"
        Android:process=":yourProcessName" />

Sie können auf docs verweisen, um mehr über Android:process Zu erfahren.

BEARBEITEN: SharedPreferences funktionieren nicht über mehrere Prozesse hinweg. In diesem Fall müssen Sie IPC (Inter Process Communication) -Methoden verwenden, oder Sie können ContentProviders zum Speichern und Zugreifen auf Ihre Daten verwenden, um sie prozessübergreifend zu verwenden. Empfohlen von docs .

2
Joshua

Ich schlage vor, dass Sie diese verwenden: AlarmManager, PowerManager, WakeLock, Thread, WakefulBroadcastReceiver, Handler, Looper

Ich gehe davon aus, dass Sie diesen "separaten Prozess" und andere Optimierungen bereits verwenden.

Also in deiner Application Klasse:

MyApp.Java:

import Android.app.AlarmManager;
import Android.app.Application;
import Android.app.PendingIntent;
import Android.content.Context;
import Android.content.Intent;
import Android.os.PowerManager;
import Android.util.Log; 

public final class MyApp extends Application{

public static PendingIntent pendingIntent = null;
public static Thread                infiniteRunningThread;
public static PowerManager          pm;
public static PowerManager.WakeLock wl;


@Override
public void onCreate(){
    try{
        Thread.setDefaultUncaughtExceptionHandler(
                (thread, e)->restartApp(this, "MyApp uncaughtException:", e));
    }catch(SecurityException e){
        restartApp(this, "MyApp uncaughtException SecurityException", e);
        e.printStackTrace();
    }
    pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
    if(pm != null){
        wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "TAG");
        wl.acquire(10 * 60 * 1000L /*10 minutes*/);
    }

    infiniteRunningThread = new Thread();

    super.onCreate();
}

public static void restartApp(Context ctx, String callerName, Throwable e){
    Log.w("TAG", "restartApp called from " + callerName);
    wl.release();
    if(pendingIntent == null){
        pendingIntent =
                PendingIntent.getActivity(ctx, 0,
                                          new Intent(ctx, ActivityMain.class), 0);
    }
    AlarmManager mgr = (AlarmManager) ctx.getSystemService(Context.ALARM_SERVICE);
    if(mgr != null){
        mgr.set(AlarmManager.RTC_WAKEUP,
                System.currentTimeMillis() + 10, pendingIntent);
    }
    if(e != null){
        e.printStackTrace();
    }
    System.exit(2);
}
}

Und dann in deinem Dienst:

ServiceTrackerTest.Java:

import Android.app.Service;
import Android.content.Context;
import Android.content.Intent;
import Android.graphics.BitmapFactory;
import Android.os.Handler;
import Android.os.IBinder;
import Android.os.Looper;
import Android.os.PowerManager;
import Android.support.v4.app.NotificationCompat;
import Android.support.v4.content.WakefulBroadcastReceiver;

public class ServiceTrackerTest extends Service{

private static final int SERVICE_ID = 2018;
private static PowerManager.WakeLock wl;

@Override
public IBinder onBind(Intent intent){
    return null;
}

@Override
public void onCreate(){
    super.onCreate();
    try{
        Thread.setDefaultUncaughtExceptionHandler(
                (thread, e)->MyApp.restartApp(this,
                                              "called from ServiceTracker onCreate "
                                              + "uncaughtException:", e));
    }catch(SecurityException e){
        MyApp.restartApp(this,
                         "called from ServiceTracker onCreate uncaughtException "
                         + "SecurityException", e);
        e.printStackTrace();
    }
    PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
    if(pm != null){
        wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "TAG");
        wl.acquire(10 * 60 * 1000L /*10 minutes*/);
    }


    Handler h = new Handler();
    h.postDelayed(()->{

        MyApp.infiniteRunningThread = new Thread(()->{
            try{
                Thread.setDefaultUncaughtExceptionHandler(
                        (thread, e)->MyApp.restartApp(this,
                                                      "called from ServiceTracker onCreate "
                                                      + "uncaughtException "
                                                      + "infiniteRunningThread:", e));
            }catch(SecurityException e){
                MyApp.restartApp(this,
                                 "called from ServiceTracker onCreate uncaughtException "
                                 + "SecurityException "
                                 + "infiniteRunningThread", e);
                e.printStackTrace();
            }

            Looper.prepare();
            infiniteRunning();
            Looper.loop();
        });
        MyApp.infiniteRunningThread.start();
    }, 5000);
}

@Override
public void onDestroy(){
    wl.release();
    MyApp.restartApp(this, "ServiceTracker onDestroy", null);
}

@SuppressWarnings("deprecation")
@Override
public int onStartCommand(Intent intent, int flags, int startId){
    if(intent != null){
        try{
            WakefulBroadcastReceiver.completeWakefulIntent(intent);
        }catch(Exception e){
            e.printStackTrace();
        }
    }
    startForeground(SERVICE_ID, getNotificationBuilder().build());
    return START_STICKY;
}


private void infiniteRunning(){
    //do your stuff here
    Handler h = new Handler();
    h.postDelayed(this::infiniteRunning, 300000);//5 minutes interval
}

@SuppressWarnings("deprecation")
private NotificationCompat.Builder getNotificationBuilder(){
    return new NotificationCompat.Builder(this)
                   .setContentIntent(MyApp.pendingIntent)
                   .setContentText(getString(R.string.notification_text))
                   .setContentTitle(getString(R.string.app_name))
                   .setLargeIcon(BitmapFactory.decodeResource(getResources(),
                                                              R.drawable.ic_launcher))
                   .setSmallIcon(R.drawable.ic_stat_tracking_service);
}

}

Ignoriere "Deprecation" und so weiter, benutze sie, wenn du keine Wahl hast . Ich denke, der Code ist klar und muss nicht erklärt werden. Es geht nur um Vorschläge und Lösungen für die Problemumgehung. 

0
M D P