web-dev-qa-db-fra.com

Android: garder le service en marche quand l'application est tuée

Je veux garder un IntentService en tâche de fond même lorsque l'application est supprimée. Et par "tué" je veux dire appuyez sur le bouton d'accueil pendant un long moment -> voir toutes les applications en cours d'exécution -> balayez mon application de côté -> application supprimée OR appuyez sur le bouton Précédent pendant longtemps -> application supprimée

Mon code va comme suit. Dans mon activité principale:

Intent intent = new Intent(this, MyService.class);
this.startService(intent);

Dans mon MyService:

public class MyService extends IntentService {

@Override
protected void onHandleIntent(Intent intent) {
    System.out.println("MyService started");
    run();
}

private void run() {
    while (true){
        System.out.println("MyService still running");
        doSomething();
        waitSomeTime();
    }
}

}

Je vois que le service est en cours d'exécution lorsque l'application est ouverte. Il est toujours en cours d'exécution lorsque je minimise l'application via le bouton d'accueil. Il est toujours en cours d'exécution quand je ferme l'application via le bouton Précédent. Mais ça va s'arrêter si je le tue comme mentionné ci-dessus. Comment résoudre ce problème?

37
user2078872

Si votre application est lancée par votre application, votre service s'exécute en réalité sur le processus principal. Ainsi, lorsque l'application sera supprimée, le service sera également arrêté. Vous pouvez donc envoyer la diffusion à partir de la méthode onTaskRemoved de votre service de la manière suivante:

 Intent intent = new Intent("com.Android.ServiceStopped");
 sendBroadcast(intent);

et avoir un récepteur de diffusion qui démarrera à nouveau un service. J'ai essayé le service redémarre à partir de tout type de destruction.

30
BhendiGawaar

Toutes les réponses semblent correctes donc je vais aller de l'avant et donner un complet répondez ici.

Premièrement, le moyen le plus simple de faire ce que vous essayez de faire est de lancer une diffusion dans Android lorsque app est tué manuellement, et définissez une coutume BroadcastReceiver pour déclencher un redémarrage du service suivant.

Passons maintenant au code.


Créez votre service dans YourService.Java

Notez la méthode onCreate(), où nous démarrons un service de premier plan différemment pour les versions de build supérieures à Android Oreo. Ceci en raison des règles de notification strictes récemment introduites, selon lesquelles nous devons définir notre propre canal de notification pour les afficher correctement.

La this.sendBroadcast(broadcastIntent); de la méthode onDestroy() est l'instruction qui envoie de manière asynchrone une diffusion avec le nom de l'action "restartservice". Nous utiliserons cela plus tard comme déclencheur pour redémarrer notre service.

Ici, nous avons défini une tâche simple de minuterie, qui affiche une valeur de compteur toutes les 1 seconde dans le Log en s’incrémentant à chaque impression.

public class YourService extends Service {
public int counter=0;

    @Override
    public void onCreate() {
        super.onCreate();
        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.O)
            startMyOwnForeground();
        else
            startForeground(1, new Notification());
    }

    @RequiresApi(Build.VERSION_CODES.O)
    private void startMyOwnForeground()
    {
        String NOTIFICATION_CHANNEL_ID = "example.permanence";
        String channelName = "Background Service";
        NotificationChannel chan = new NotificationChannel(NOTIFICATION_CHANNEL_ID, channelName, NotificationManager.IMPORTANCE_NONE);
        chan.setLightColor(Color.BLUE);
        chan.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);

        NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
        assert manager != null;
        manager.createNotificationChannel(chan);

        NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID);
        Notification notification = notificationBuilder.setOngoing(true)
                .setContentTitle("App is running in background")
                .setPriority(NotificationManager.IMPORTANCE_MIN)
                .setCategory(Notification.CATEGORY_SERVICE)
                .build();
        startForeground(2, notification);
    }


    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        super.onStartCommand(intent, flags, startId);
        startTimer();
        return START_STICKY;
    }


    @Override
    public void onDestroy() {
        super.onDestroy();
        stoptimertask();

        Intent broadcastIntent = new Intent();
        broadcastIntent.setAction("restartservice");
        broadcastIntent.setClass(this, Restarter.class);
        this.sendBroadcast(broadcastIntent);
    }



    private Timer timer;
    private TimerTask timerTask;
    public void startTimer() {
        timer = new Timer();
        timerTask = new TimerTask() {
            public void run() {
                Log.i("Count", "=========  "+ (counter++));
            }
        };
        timer.schedule(timerTask, 1000, 1000); //
    }

    public void stoptimertask() {
        if (timer != null) {
            timer.cancel();
            timer = null;
        }
    }

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

Créez un récepteur de diffusion pour répondre à vos émissions personnalisées définies dans Restarter.Java

La diffusion avec le nom de l'action "restartservice" que vous venez de définir dans YourService.Java Est maintenant supposée déclencher une méthode qui redémarrez votre service . Ceci est fait en utilisant BroadcastReceiver dans Android.

Nous remplaçons la méthode onRecieve() intégrée dans BroadcastReceiver pour ajouter l'instruction qui redémarrera le service. La startService() ne fonctionnera pas comme prévu à partir de Android Oreo 8.1, en tant que strict conditions de fond va bientôt mettre fin au service après le redémarrage, une fois l'application tuée. Par conséquent, nous utilisons startForegroundService() pour les versions supérieures et affichons une notification continue pour que le service continue à fonctionner.

public class Restarter extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        Log.i("Broadcast Listened", "Service tried to stop");
        Toast.makeText(context, "Service restarted", Toast.LENGTH_SHORT).show();

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            context.startForegroundService(new Intent(context, YourService.class));
        } else {
            context.startService(new Intent(context, YourService.class));
        }
    }
}

Définissez votre MainActivity.Java Pour appeler le service au démarrage de l'application.

Ici, nous définissons une méthode isMyServiceRunning() séparée pour vérifier le statut actuel du service en arrière-plan. Si le service est en cours d'exécution pas, nous le démarrons en utilisant startService().

Étant donné que l'application fonctionne déjà à l'avant-plan, nous n'avons pas besoin de lancer le service en tant que service à l'avant-plan pour éviter son interruption.

Notez que dans onDestroy(), nous appelons dédoublement stopService(), de sorte que notre méthode surchargée est invoqué. Si cela n’avait pas été fait, le service aurait pris fin automatiquement après la suppression de l’application sans appeler notre méthode modifiée onDestroy() dans YourService.Java

public class MainActivity extends AppCompatActivity {
    Intent mServiceIntent;
    private YourService mYourService;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mYourService = new YourService();
        mServiceIntent = new Intent(this, mYourService.getClass());
        if (!isMyServiceRunning(mYourService.getClass())) {
            startService(mServiceIntent);
        }
    }

    private boolean isMyServiceRunning(Class<?> serviceClass) {
        ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
        for (ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
            if (serviceClass.getName().equals(service.service.getClassName())) {
                Log.i ("Service status", "Running");
                return true;
            }
        }
        Log.i ("Service status", "Not running");
        return false;
    }


    @Override
    protected void onDestroy() {
        stopService(mServiceIntent);
        super.onDestroy();
    }
}

Enfin, enregistrez-les dans votre AndroidManifest.xml

Les trois classes ci-dessus doivent être enregistrées séparément dans AndroidManifest.xml.

Notez que nous définissons un intent-filter Avec le nom de l'action comme "restartservice" Où le Restarter.Java est enregistré en tant que receiver. Cela garantit que notre coutume BroadcastReciever est appelée chaque fois que le système rencontre une émission avec le nom de l'action =.

<application
    Android:allowBackup="true"
    Android:icon="@mipmap/ic_launcher"
    Android:label="@string/app_name"
    Android:supportsRtl="true"
    Android:theme="@style/AppTheme">

    <receiver
        Android:name="Restarter"
        Android:enabled="true"
        Android:exported="true">
        <intent-filter>
            <action Android:name="restartservice" />
        </intent-filter>
    </receiver>

    <activity Android:name="MainActivity">
        <intent-filter>
            <action Android:name="Android.intent.action.MAIN" />
            <category Android:name="Android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>

    <service
        Android:name="YourService"
        Android:enabled="true" >
    </service>
</application>

Cela devrait maintenant redémarrer votre service si l'application a été supprimée du gestionnaire de tâches. Ce service continuera à fonctionner en arrière-plan tant que l'utilisateur ne Force Stop l'appli de Paramètres de l'application .

UPDATE: Félicitations à Dr.jacky pour le signaler. La façon susmentionnée ne fonctionnera que si la onDestroy() du service est appelée, ce qui pourrait ne pas être le cas à certains moments, ce qui Je n'étais pas au courant. Merci.

23
Sayan Sil

Dans votre service, ajoutez le code suivant.

@Override
public void onTaskRemoved(Intent rootIntent){
    Intent restartServiceIntent = new Intent(getApplicationContext(), this.getClass());
    restartServiceIntent.setPackage(getPackageName());

    PendingIntent restartServicePendingIntent = PendingIntent.getService(getApplicationContext(), 1, restartServiceIntent, PendingIntent.FLAG_ONE_SHOT);
    AlarmManager alarmService = (AlarmManager) getApplicationContext().getSystemService(Context.ALARM_SERVICE);
    alarmService.set(
    AlarmManager.ELAPSED_REALTIME,
    SystemClock.elapsedRealtime() + 1000,
    restartServicePendingIntent);

    super.onTaskRemoved(rootIntent);
 }
6
Iman Marashi

à l'intérieur de la commande onstart put START_STICKY... Ce service ne tuera pas s'il ne fait pas trop de travail et que le noyau veut le tuer pour ça ...

@Override
        public int onStartCommand(Intent intent, int flags, int startId) {
            Log.i("LocalService", "Received start id " + startId + ": " + intent);
            // We want this service to continue running until it is explicitly
            // stopped, so return sticky.
            return START_STICKY;
        }
5
kiturk3

La raison en est que vous essayez d'utiliser un service IntentService. Voici la ligne de la API Docs

IntentService effectue les opérations suivantes:

Arrête le service une fois que toutes les demandes de démarrage ont été traitées. Vous ne devez donc jamais appeler stopSelf ().

Ainsi, si vous souhaitez que votre service fonctionne indéfiniment, je vous suggère plutôt d’étendre la classe Service. Toutefois, cela ne garantit pas que votre service fonctionnera indéfiniment. Votre service aura toujours une chance d'être tué par le noyau dans un état de mémoire insuffisante s'il est prioritaire. Vous avez donc deux options:
1) Laissez-le en marche au premier plan en appelant la méthode startForeground().
2) Redémarrez le service s'il est tué. Voici une partie de l'exemple de la documentation où ils parlent de redémarrer le service après sa suppression.

 public int onStartCommand(Intent intent, int flags, int startId) {
      Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();

      // For each start request, send a message to start a job and deliver the 
      // start ID so we know which request we're stopping when we finish the job 
      Message msg = mServiceHandler.obtainMessage();
      msg.arg1 = startId;
      mServiceHandler.sendMessage(msg);

      // If we get killed, after returning from here, restart 
      return START_STICKY;
  }  
4
Rishit Shah

Vous pouvez utiliser Android:stopWithTask="false"dans le manifeste ci-dessous, cela signifie que même si l'utilisateur tue une application en la supprimant de la liste des tâches, votre service ne s'arrêtera pas.

 <service Android:name=".service.StickyService"
                  Android:stopWithTask="false"/>
1
Melbourne Lopes