Android: Läuft der Dienst weiter, wenn die App beendet wird


76

Ich möchte IntentServiceim Hintergrund laufen, auch wenn die App beendet ist. Und mit "getötet" meine ich, dass Sie lange auf die Home-Taste drücken -> alle laufenden Apps anzeigen -> meine App beiseite wischen -> App getötet ODER die Zurück-Taste für eine lange Zeit drücken -> App getötet

Mein Code lautet wie folgt. In meiner Hauptaktivität:

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

In meinem 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();
    }
}

}

Ich sehe, dass der Dienst ausgeführt wird, wenn die App geöffnet ist . Es läuft noch, wenn ich die App über die Home-Taste minimiere . Es läuft noch, wenn ich die App über den Zurück-Button schließe. Aber es wird aufhören, wenn ich es wie oben erwähnt töte. Wie löse ich das?



Ich habe es versucht (Beispiel heruntergeladen): Ja, der Dienst überlebt einen Kill über den Task-Manager, aber er überlebt den Kill "Drücken Sie die Zurück-Taste für eine lange Zeit" nicht. Hast du eine Idee?
user2078872


Vielleicht ist meine Antwort für jemanden nützlich: stackoverflow.com/a/64113820/2877427
AnasSafi

1
Was funktioniert heutzutage in allen Antworten?
Ahmad

Antworten:


70

Alle Antworten scheinen richtig zu sein, also werde ich weitermachen und eine vollständige geben hier Antwort geben.

Erstens ist der einfachste Weg, das zu tun, was Sie versuchen, eine Sendung in Android zu starten, wenn die App manuell beendet wird, und eine benutzerdefinierte zu definierenBroadcastReceiver zu , um anschließend einen Neustart des Dienstes auszulösen.

Lassen Sie uns nun in den Code springen.


Erstellen Sie Ihren Service in YourService.java

Beachten Sie die onCreate()Methode, bei der wir einen Vordergrunddienst für Build-Versionen, die größer als Android Oreo sind, anders starten . Dies liegt an den kürzlich eingeführten strengen Benachrichtigungsrichtlinien, bei denen wir unseren eigenen Benachrichtigungskanal definieren müssen , um sie korrekt anzuzeigen.

Die this.sendBroadcast(broadcastIntent);in der onDestroy()Methode ist die Anweisung, die asynchron eine Sendung mit dem Aktionsnamen sendet "restartservice". Wir werden dies später als Auslöser verwenden, um unseren Dienst neu zu starten.

Hier haben wir eine einfache Timer - Task definiert, der druckt einen Zählerwert jedes 1 Sekunde in die Logwährend selbst druckt es jedes Mal erhöht wird .

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;
    }
}

Erstellen Sie einen Broadcast-Empfänger, um auf Ihre benutzerdefinierten Broadcasts in zu antworten Restarter.java

Die Sendung mit dem Aktionsnamen, "restartservice"den Sie gerade definiert haben, YourService.javasoll nun eine Methode auslösen, mit der Ihr Dienst neu gestartet wird . Dies geschieht mitBroadcastReceiver Android.

Wir überschreiben die integrierte onRecieve()Methode BroadcastReceiver, um die Anweisung hinzuzufügen, mit der der Dienst neu gestartet wird. Das startService()wird nicht wie vorgesehen in und über Android Oreo 8.1, als strenge Hintergrund Politik wird in Kürze den Dienst nach dem Neustart beenden , sobald die App getötet wird. Daher verwenden wir die startForegroundService()für höhere Versionen und zeigen eine fortlaufende Benachrichtigung an, um den Dienst am Laufen zu halten.

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));
        }
    }
}

Definieren Sie MainActivity.java, dass Sie den Dienst beim Start der App aufrufen möchten.

Hier definieren wir eine separate isMyServiceRunning()Methode, um den aktuellen Status des Hintergrunddienstes zu überprüfen. Wenn der Dienst nicht ausgeführt wird, starten wir ihn mitstartService() .

Da die App bereits im Vordergrund ausgeführt wird, müssen wir den Dienst nicht als Vordergrunddienst starten , um zu verhindern, dass er selbst beendet wird.

Beachten Sie, dass onDestroy()wir in dediziert aufrufen stopService(), damit unsere überschriebene Methode aufgerufen wird. Wenn dies nicht getan worden wäre, hätte der Dienst automatisch beendet , nachdem die App beendet wurde, ohne unsere geänderte onDestroy()Methode in aufzurufenYourService.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);
        Intent broadcastIntent = new Intent();
        broadcastIntent.setAction("restartservice");
        broadcastIntent.setClass(this, Restarter.class);
        this.sendBroadcast(broadcastIntent);
        super.onDestroy();
    }
}

Registrieren Sie sie schließlich in Ihrem AndroidManifest.xml

Alle oben genannten drei Klassen müssen separat angemeldet werden AndroidManifest.xml.

Beachten Sie, dass wir eine Definition intent-filtermit dem Aktionsnamen wie , "restartservice"wo der Restarter.javaals registriert ist receiver. Dies stellt sicher, dass unser Benutzerdefiniert BroadcastRecieveraufgerufen wird, wenn das System auf eine Sendung mit dem angegebenen Aktionsnamen stößt .

<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>

Dies sollte Ihren Dienst jetzt erneut starten, wenn die App vom Task-Manager beendet wurde. Dieser Dienst wird im Hintergrund ausgeführt, solange der Benutzer Force Stopdie App nicht über die Anwendungseinstellungen verwendet .

UPDATE: Ein großes Lob an Dr. Jacky für den Hinweis. Der oben erwähnte Weg funktioniert nur, wenn der onDestroy()Dienst aufgerufen wird, was zu bestimmten Zeiten, die mir nicht bekannt waren , möglicherweise nicht der Fall ist. Vielen Dank.


2
Dies funktioniert jedoch nicht im Hintergrund, wenn die App beendet wird, oder im Hintergrund in oreo. Es funktioniert nur, wenn die App im Vordergrund steht. .. bitte helfen Sie dies zu schieben.
Rama Tulasi

4
Die Dienste werden onDestroymöglicherweise nicht aufgerufen: stackoverflow.com/a/7237522/421467
Dr.jacky

1
Vielen Dank! Ich habe meine Antwort aktualisiert, um sicherzustellen, dass diese Möglichkeit bekannt ist. Ich habe heute etwas Neues gelernt.
Sayan Sil

1
Wenn wir START_STICKY bereits von onStartCommand () zurückgegeben haben, bedeutet dies, dass das Betriebssystem den Dienst neu startet, wenn er beendet wird. Warum brauchen wir dann eine Sendung, die einen Dienst in onDestroy () des Dienstes startet? Wird der Neustart nicht bereits vom Betriebssystem erledigt?
DevAndroid

1
Es funktioniert nicht, meins ruft onDestroy () nicht auf. Wie können wir also den Dienst auf unbestimmte Zeit im Hintergrund ausführen?
mas bro

46

Wenn Ihr Dienst von Ihrer App gestartet wird, wird Ihr Dienst tatsächlich im Hauptprozess ausgeführt. Wenn die App beendet wird, wird auch der Dienst gestoppt. Sie können also eine Sendung von der onTaskRemovedMethode Ihres Dienstes wie folgt senden :

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

und einen Rundfunkempfänger haben, der erneut einen Dienst startet. Ich habe es versucht. Der Dienst wird von allen Arten von Kills neu gestartet.


19
Das ist die richtige Antwort. Ich habe ein einfaches Beispiel in einem Blogeintrag geschrieben
FabioC

Bitte sagen Sie mir, was ich in Absicht schreibe, bedeutet "com.android.ServiceStopped". Was es bedeutet, welche Absicht ich in der sendBroadcast-Methode aufgerufen habe, ist eine neue Absicht zu erstellen, um den Dienst zu stoppen
Ahmad Joyia

1
@ BhendiGawaar Ich mache das Gleiche wie du gesagt hast. Aber es funktioniert nicht auf benutzerdefinierten OS-Telefonen wie vivo, OPO, MIUI usw.
Rohan Shinde

3
Manchmal, wenn der Android angibt, dass er einige Prozesse abbrechen muss, um mehr RAM freizugeben, ruft er nicht einmal den onTaskRemovedFormulardienst auf. Es tötet es einfach.
Mahdi-Malv

1
Ich habe Lenovo TB3-730X verwendet. onTaskRemoved wird nicht aufgerufen, wenn die App vom Benutzer beendet wird, und ich habe es auch mit onDestroy versucht. Es funktioniert auch nicht. Bitte schlagen Sie mir eine Lösung vor
Megala Mani

13

Fügen Sie in Ihrem Dienst den folgenden Code hinzu.

@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);
 }

5

inside onstart command put START_STICKY... Dieser Dienst wird nicht beendet, es sei denn, er erledigt zu viele Aufgaben und der Kernel möchte ihn dafür beenden ...

@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;
        }

funktioniert für den Kill über den Task-Manager, aber der Dienst überlebt einen "Drücken der Zurück-Taste für eine lange Zeit" Kill nicht: /
user2078872

start sticky führt den Dienst nach dem Herunterfahren der App erneut aus, sodass der Dienst in gewisser Weise neu gestartet wird.
Fahad Alkamli

6
Irgendwann, wenn der Benutzer die App beendet, sollten wir den Benutzer vielleicht respektieren und den Dienst sterben lassen.
Marcel Falliere

4

Der Grund dafür ist, dass Sie versuchen, einen IntentService zu verwenden. Hier ist die Zeile aus den API-Dokumenten

Der IntentService führt Folgendes aus:

Stoppt den Dienst, nachdem alle Startanforderungen verarbeitet wurden, sodass Sie stopSelf () nie mehr aufrufen müssen.

Wenn Sie also möchten, dass Ihr Dienst auf unbestimmte Zeit ausgeführt wird, empfehlen wir Ihnen, stattdessen die Dienstklasse zu erweitern. Dies garantiert jedoch nicht, dass Ihr Dienst auf unbestimmte Zeit ausgeführt wird. Ihr Dienst hat weiterhin die Möglichkeit, vom Kernel in einem Zustand mit geringem Arbeitsspeicher beendet zu werden, wenn er eine niedrige Priorität hat. Sie haben also zwei Möglichkeiten:
1) Lassen Sie ihn durch Aufrufen der startForeground() Methode im Vordergrund laufen .
2) Starten Sie den Dienst neu, wenn er beendet wird. Hier ist ein Teil des Beispiels aus den Dokumenten, in denen über den Neustart des Dienstes nach dessen Beendigung gesprochen wird

 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;
  }  

-3

Sie können das android:stopWithTask="false"Manifest als Balg verwenden. Dies bedeutet, dass Ihr Dienst nicht beendet wird, selbst wenn der Benutzer die App beendet, indem er sie von der Aufgabenliste entfernt.

 <service android:name=".service.StickyService"
                  android:stopWithTask="false"/>

29
Es ist standardmäßig falsch !!
Kishore Jethava
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.