web-dev-qa-db-fra.com

Background Service se faire tuer dans Android

Nous avons développé une application Android qui implique un service en arrière-plan. Pour implémenter ce service en tâche de fond, nous avons utilisé IntentService. Nous voulons que l'application interroge le serveur tous les 60 seconds. Donc, dans la IntentService, le serveur est interrogé dans une boucle while. À la fin de la boucle while, nous avons utilisé Thread.sleep(60000) pour que la prochaine itération ne commence qu'après 60 secondes.
Mais dans la Logcat, je vois que parfois il faut plus de 5 minutes à l'application pour se réveiller (sortir de ce sommeil et commencer la prochaine itération). Ce n'est jamais 1 minute comme nous le voulons.

Quelle est la raison pour ça? Les services de base doivent-ils être mis en œuvre de manière différente?

Problème2

Android tue ce processus d'arrière-plan (service d'intention) après un certain temps. Je ne peux pas dire quand exactement. Mais parfois, cela prend des heures et des jours avant que le service d'arrière-plan ne soit tué. J'apprécierais que vous me disiez la raison de cela. Parce que les services ne sont pas faits pour être tués. Ils sont conçus pour fonctionner en arrière-plan aussi longtemps que nous le voulons.

Code:

@Override
 protected void onHandleIntent(Intent intent) {
  boolean temp=true;
  while(temp==true) {
    try {
      //connect to the server 
      //get the data and store it in the sqlite data base
    }
    catch(Exception e) {
      Log.v("Exception", "in while loop : "+e.toString());
    }
    //Sleep for 60 seconds
    Log.v("Sleeping", "Sleeping");
    Thread.sleep(60000);
    Log.v("Woke up", "Woke up");

    //After this a value is extracted from a table
    final Cursor cur=db.query("run_in_bg", null, null, null, null, null, null);
    cur.moveToLast();
    String present_value=cur.getString(0);
    if(present_value==null) {
       //Do nothing, let the while loop continue  
    }
    else if( present_value.equals("false") || present_value.equals("False") ) {
       //break out of the while loop
       db.close();
       temp=false;
       Log.v("run_in_bg", "false");
       Log.v("run_in_bg", "exiting while loop");
       break;
    }
  }

}

Mais chaque fois que le service est tué, cela se produit lorsque le processus est endormi. Le dernier journal lit - Sleeping : Sleeping. Pourquoi le service est tué?

33
Ashwin

Le problème principal est que nous ne pouvons pas dire 

Les services ne sont pas faits pour être tués. Ils sont conçus pour fonctionner en arrière-plan aussi longtemps que nous le voulons. 

Fondamentalement, ce n'est pas vrai. Le système peut toujours mettre fin au service avec peu de mémoire et éventuellement d’autres situations .. Il existe deux façons de remédier à cela:

  1. Si vous implémentez le service, remplacez onStartCommand() et renvoyez START_STICKY comme résultat. Il dira au système que même s'il souhaite tuer votre service en raison d'une mémoire insuffisante, il devrait le recréer dès que la mémoire sera revenue à la normale.
  2. Si vous n'êtes pas sûr que la première approche fonctionnera, vous devrez utiliser AlarmManager http://developer.Android.com/reference/Android/app/AlarmManager.html . Il s’agit d’un service système, qui exécutera des actions lorsque vous le signalerez, par exemple périodiquement. Cela garantira que si votre service est arrêté, voire l'ensemble du processus mourra (par exemple avec la fermeture forcée), AlarmManager le relancera à 100%.

Bonne chance

78
AlexN

Vous pouvez utiliser ScheduledExecutorService spécialement conçu à cet effet.

N'utilisez pas les timers, comme indiqué dans "Java Concurrency in Practice" ils peuvent être très imprécis.

6

IntentService n'est pas destiné à continuer à fonctionner dans une boucle while. L'idée est de réagir à une Intent, d'effectuer un traitement et d'arrêter le service une fois terminé.

Cela ne signifie pas que cela ne fonctionne pas et je ne peux pas vous dire pourquoi vous voyez des retards aussi longs, mais la solution la plus propre consiste à utiliser une source externe pour contrôler le service périodiquement. Outre les méthodes Java de Vanilla, vous pouvez également consulter le AlarmManager ou un Handler comme indiqué dans la documentation AlarmManager.

La manière Handler fonctionnerait comme ceci

public class TriggerActivity extends Activity implements Handler.Callback {
    // repeat task every 60 seconds
    private static final long REPEAT_TIME = 60 * 1000;
    // define a message id
    private static final int MSG_REPEAT = 42;

    private Handler mHandler;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mHandler = new Handler(this);
    }

    @Override
    protected void onStart() {
        super.onStart();
        // start cycle immediately
        mHandler.sendEmptyMessage(MSG_REPEAT);
    }

    @Override
    protected void onStop() {
        super.onStop();
        // stop cycle
        mHandler.removeMessages(MSG_REPEAT);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mHandler = null;
    }

    @Override
    public boolean handleMessage(Message msg) {
        // enqueue next cycle
        mHandler.sendEmptyMessageDelayed(MSG_REPEAT, REPEAT_TIME);
        // then trigger something
        triggerAction();
        return true;
    }

    private void triggerAction() {
        // trigger the service
        Intent serviceIntent = new Intent(this, MyService.class);
        serviceIntent.setAction("com.test.intent.OPTIONAL_ACTION");
        startService(serviceIntent);
    }
}

Un simple Activity (qui pourrait être étendu pour avoir cette fonctionnalité dans toutes vos activités) qui s'envoie une Message tout le temps pendant son exécution (ici entre onStart et onStop)

5
zapl

Une meilleure solution serait de déclencher un AlarmManager toutes les 60 secondes. AlarmManager démarre ensuite le service qui interroge le serveur, puis lance un nouveau AlarmManager, une solution récursive qui fonctionne assez bien. 

Cette solution sera plus fiable, car le système d’exploitation Android ne vous menacera pas, vous menaçant. Selon l'API: Alarm Manager est conçu pour les cas dans lesquels vous voulez que votre code d'application soit exécuté à une heure spécifique, même si votre application n'est pas en cours d'exécution.

Dans votre interface utilisateur/activité principale, etc., configurez ce minuteur pour qu'il s'éteigne dans 60 secondes:

long ct = System.currentTimeMillis(); //get current time
AlarmManager mgr=(AlarmManager)getApplicationContext().getSystemService(Context.ALARM_SERVICE);
Intent i= new Intent(getApplicationContext(), yourservice.class);
PendingIntent pi=PendingIntent.getService(getApplicationContext(), 0, i, 0);

   mgr.set(AlarmManager.RTC_WAKEUP, ct + 60000 , pi); //60 seconds is 60000 milliseconds

Dans yourservice.class, vous pourriez avoir ceci, il vérifie l'état de la connexion, s'il est bon, le temporisateur s'éteindra dans 60 secondes:

    public class yourservice extends IntentService {

                    public yourservice() { //needs this constructor
                        super("server checker");
                    }

                    @Override
                    protected void onHandleIntent(Intent intent) {
                        WifiManager wificheck = (WifiManager) this.getSystemService(Context.WIFI_SERVICE);

                        if(check for a certain condition your app needs etc){
            //could check connection state here and stop if needed etc
                              stopSelf(); //stop service
                        }
                           else{ //poll the server again in 60 seconds
            long ct = System.currentTimeMillis();
            AlarmManager mgr=(AlarmManager)getApplicationContext().getSystemService(Context.ALARM_SERVICE);
            Intent i= new Intent(getApplicationContext(), yourservice.class);
            PendingIntent pi=PendingIntent.getService(getApplicationContext(), 0, i, 0);

           mgr.set(AlarmManager.RTC_WAKEUP, ct + 60000 , pi); 
           stopSelf(); //stop service since its no longer needed and new alarm is set
                               }
                }
}

Les services sont tués. Comme l'application se fait tuer. Selon la philosophie Android, vous pouvez être tué à tout moment.

Comme d’autres l’ont écrit, ne supposez pas que votre service d’arrière-plan fonctionne pour toujours.

Mais vous pouvez utiliser un service fore ground pour réduire considérablement le risque d'être tué/redémarré. Notez que cela force une notification qui est toujours visible. Par exemple, les lecteurs de musique, les applications VPN et Sportstracker utilisent cette API.

2
plaisthos

Pour le problème 1, de Vanilla Java, Thread.Sleep() est garanti pour réactiver le thread après son expiration, mais pas exactement après son expiration, il peut être plus tard dépendant principalement des statuts des autres threads, de la priorité, etc. Donc, si vous dormez une seconde dans votre fil, il dormira au moins une seconde, mais cela peut être 10 en fonction de nombreux facteurs. Je ne suis pas très au courant du développement Android, mais je suis presque sûr que c'est la même situation. .

Pour le problème 2, les services peuvent être supprimés lorsque la mémoire devient faible ou manuellement par l'utilisateur. Par conséquent, comme d'autres l'ont fait remarquer, utiliser AlarmManager pour redémarrer votre service après un certain temps vous aidera à le faire fonctionner tout le temps.

1
Rafael

Vous pouvez essayer d'implémenter Jobscheduler avec JobService exécuté en arrière-plan, ce qui est recommandé ci-dessus sous Android O.

0
abhi.nalwade

On dirait que vous devriez utiliser un Service au lieu d’un IntentService mais si vous voulez utiliser un IntentService et le faire fonctionner toutes les 60 secondes, vous devez utiliser le AlarmManager au lieu de il suffit de demander à la Thread de dormir .. IntentServices veulent s’arrêter, laissons-la et laisser AlarmManager la réactiver quand elle devrait fonctionner à nouveau.

0
JustinMorris
It could be probably for two reasons..
  1. Soit la boucle while crée un problème, elle oblige le gestionnaire à fonctionner jusqu'au temp==true
  2. Ajoute à cela des threads, ce qui crée long delays jusqu'à 6 seconds. Si le système fonctionne pour une base de données volumineuse, créer long delays entre chaque requête ajoutera de la mémoire système . Si énorme que le system memory gets low, le système doit terminate the process ..

    Solution au problème ..

  3. Vous pouvez remplacer ci-dessus par Alarm Manager pour révoquer les services système après un intervalle de temps donné en utilisant Alarm Manager.

  4. Également, pour récupérer l’intention après que le système ait récupéré l’application de la fin, vous devez utiliser START_REDELIVER_INTENT. Il s'agit de récupérer votre dernière intention de travail une fois l'application terminée. pour son utilisation, étudiez https://developer.Android.com/reference/Android/app/Service.html#START_REDELIVER_INTENT
0
Sahil Mahajan Mj

Android est plutôt doué pour éliminer les services de longue durée. J'ai trouvé WakefulIntentService de CommonsWare utile dans mon application: https://github.com/commonsguy/cwac-wakeful

Il vous permet de spécifier un intervalle de temps comme vous essayez de le faire en veille.

0
benkdev