J'essaie de créer une application permettant aux utilisateurs de consigner des itinéraires (emplacements/GPS). Pour que les emplacements soient enregistrés même lorsque l'écran est éteint, j'ai créé un foreground service
pour l'enregistrement des emplacements. Je stocke les emplacements dans un Room Database
qui est injecté dans mon service à l'aide de Dagger2
.
Cependant, ce service est détruit par Android, ce qui, bien sûr, n’est pas bon. Je pourrais souscrire à des avertissements de mémoire insuffisante, mais cela ne résout pas le problème sous-jacent de la mort de mon service après environ 30 minutes d'utilisation d'un téléphone haut de gamme sous Android 8.0.
J'ai créé un projet minimal avec uniquement une activité "Hello world" et le service: https://github.com/RandomStuffAndCode/AndroidForegroundService
Le service est démarré dans ma classe Application
et l'enregistrement de l'itinéraire via une Binder
:
// 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()
Je n'ai probablement pas besoin du drapeau BIND_AUTO_CREATE
, j'ai déjà testé différents drapeaux pour ne pas faire tuer mon service - pas de chance jusqu'à présent.
En utilisant le profileur, il ne semble pas y avoir de fuite de mémoire, l'utilisation de la mémoire est stable à ~ 35 Mo.
En utilisant adb Shell dumpsys activity processes > tmp.txt
, je peux confirmer que foregroundServices=true
et mon service figurent en 8e position dans la liste des LRU:
Proc # 3: prcp F/S/FGS trm: 0 31592:com.example.foregroundserviceexample/u0a93 (fg-service)
Il semble qu'il ne soit pas possible de créer un service de premier plan auquel vous pouvez faire confiance pour ne pas être tué. Alors, que pouvons-nous faire? Bien...
START_STICKY
. Cela semble une sorte de gaspillage et ne conduit pas à un très beau code, mais cela fonctionnerait probablement ... un peu. En fonction du temps nécessaire à Android pour recréer le service après l'avoir tué, une grande partie des emplacements peut être perdue.Est-ce vraiment l'état actuel de faire des choses en arrière-plan sur Android? N'y a-t-il pas un meilleur moyen?
EDIT: La liste blanche de l'application pour l'optimisation de la batterie (la désactivation) n'empêche pas mon service d'être tué
EDIT: L'utilisation de Context.startForegroundService()
pour démarrer le service n'améliore pas la situation
EDIT: Donc, cela ne se produit effectivement que sur some , mais cela se produit systématiquement sur eux. Je suppose que vous devez choisir entre ne pas prendre en charge un grand nombre d'utilisateurs ou écrire un code vraiment moche. Impressionnant.
Un service démarré par startForeground
correspond au deuxième groupe le plus important processus visible :
Un processus visible est un travail dont l'utilisateur est actuellement conscient, le tuer aurait donc un impact négatif notable sur l'utilisateur expérience. Un processus est considéré comme visible dans ce qui suit conditions:
Il exécute une activité visible à l'écran par l'utilisateur mais pas au premier plan (sa méthode onPause () a été appelée). Ce peut se produire, par exemple, si l’activité de premier plan est affichée en tant que dialogue qui permet de voir l’activité précédente derrière elle.
Il a un service qui fonctionne en tant que service de premier plan, via Service.startForeground () (qui demande au système de traiter le service Comme quelque chose que l'utilisateur connaît ou qui est essentiellement visible pour Les.) .
- Il héberge un service utilisé par le système pour une fonction particulière connue de l'utilisateur, telle qu'une méthode de saisie de fond d'écran animé service, etc.
Le nombre de ces processus en cours d'exécution dans le système est moins limité que les processus de premier plan, mais toujours relativement contrôlée. Celles-ci les processus sont considérés comme extrêmement importants et ne seront pas tués à moins que cela ne soit nécessaire pour maintenir tous les processus de premier plan en cours d'exécution.
Cela étant dit, vous ne pouvez jamais être sûr que votre service n'est tué à aucun moment. Par exemple. pression de la mémoire, pile faible, etc. Voir qui-vit-et-qui-meurt }.
En gros, vous avez répondu vous-même à la question. Le chemin à parcourir est START_STICKY
:
Pour les services démarrés, il existe deux autres modes principaux de opération, ils peuvent décider de courir, en fonction de la valeur qu'ils return from
onStartCommand():
START_STICKY
est utilisé pour les services qui sont explicitement démarrés et arrêtés selon les besoins, tandis queSTART_NOT_STICKY
ouSTART_REDELIVER_INTENT
sont utilisés pour des services qui devraient uniquement rester en cours pendant le traitement des commandes qui leur sont envoyées. Voir le documentation liée pour plus de détails sur la sémantique.
En règle générale, vous devez utiliser le moins possible le service d’arrière-plan (au premier plan), c’est-à-dire ne faire que le suivi de la position et conserver tout le reste dans votre activité au premier plan. Seul le suivi devrait nécessiter très peu de configuration et peut être chargé rapidement. Aussi, plus votre service est petit, moins il risque d'être tué. Votre activité sera restaurée par le système dans l'état qui était avant de passer à l'arrière-plan, à condition qu'elle ne soit pas également supprimée. Un "démarrage à froid" de l'activité de premier plan ne devrait en revanche pas poser de problème.
Je ne considère pas cela comme moche, car cela garantit que le téléphone offre toujours la meilleure expérience à l'utilisateur. C’est la chose la plus importante à faire. Il est regrettable que certains appareils ferment les services après 30 minutes (éventuellement sans interaction de l'utilisateur).
Donc, comme vous l'avez dit, vous devez
Conserver tout dans le service, par exemple une base de données de salle. Chaque variable, chaque classe personnalisée, chaque fois qu’un d’eux change, puis démarrez le service avec START_STICKY.
Voir création d'un service sans fin
Question implicite:
Selon le temps nécessaire à Android pour recréer le fichier service après l'avoir tué, une grande partie des lieux peut être perdue.
Cela prend généralement très peu de temps. Surtout parce que vous pouvez utiliser le Api du fournisseur d'emplacement fusionné pour les mises à jour d'emplacement, qui est un service système indépendant et qui a très peu de chances d'être tué. Donc, cela dépend principalement du temps dont vous avez besoin pour recréer le service dans onStartCommand
.
Notez également qu'à partir d'Android 8.0, vous devez utiliser un fichier
forground service
en raison de emplacement d'arrière-plan limites _.
Edit: Comme récemment couvert dans les nouvelles: Certains fabricants peuvent vous donner du fil à retordre pour que votre service fonctionne. Le site https://dontkillmyapp.com/ garde une trace des fabricants et des solutions possibles pour votre appareil. Oneplus _ est actuellement (29.01.19) l'un des pires contrevenants.
Lors de la sortie de leurs téléphones 1 + 5 et 1 + 6, OnePlus a introduit l’un des limites de fond les plus sévères sur le marché à ce jour, même en nanisme ceux réalisés par Xiaomi ou Huawei. Les utilisateurs doivent non seulement activer paramètres supplémentaires pour que leurs applications fonctionnent correctement, mais ces paramètres même réinitialiser avec mise à jour du firmware afin que les applications cassent à nouveau et les utilisateurs sont requis pour réactiver ces paramètres régulièrement.
Solution pour les utilisateurs
Désactivez Paramètres système> Applications> Icône engrenage> Accès spécial> Batterie Optimisation.
malheureusement il y a
Aucune solution connue du côté développeur
Je vous suggère d’utiliser celles-ci: AlarmManager
, PowerManager
, WakeLock
, Thread
, WakefulBroadcastReceiver
, Handler
, Looper
Je suppose que vous utilisez déjà ces "processus séparés" et d’autres réglages également.
Donc, dans votre classe Application
:
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);
}
}
Et ensuite à votre service:
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);
}
}
Ignorez "Deprecation" et tout ça, utilisez-les quand vous n'avez pas le choix ... Je pense que le code est clair et qu'il n'a pas besoin d'être expliqué. Il s’agit simplement de suggestions de solutions de contournement.