android - servicios - ¿Cómo forzar a un IntentService a detenerse inmediatamente con un botón de cancelación de una Actividad?
intentservice vs service (9)
Aquí hay un código de muestra para iniciar / detener el servicio
Para comenzar,
Intent GPSService = new Intent(context, TrackGPS.class);
context.startService(GPSService);
Para detener,
context.stopService(GPSService);
Tengo un IntentService que se inicia desde una actividad y me gustaría poder detener el servicio inmediatamente desde la actividad con el botón "cancelar" en la actividad. Tan pronto como se presione el botón "cancelar", quiero que el servicio deje de ejecutar líneas de código.
He encontrado una serie de preguntas similares a esta (es decir, here , here , here , here ), pero no hay buenas respuestas. Activity.stopService()
y Service.stopSelf()
ejecutan el método onHandleIntent()
inmediato, pero luego dejan que el código en onHandleIntent()
termine completamente antes de destruir el servicio.
Como aparentemente no hay una forma garantizada de terminar el hilo del servicio inmediatamente, la única solución recomendada que puedo encontrar ( here ) es tener una variable booleana en el servicio que se puede cambiar en el método onDestroy()
, y luego tener casi cada línea del código en onHandleIntent()
envuelve en su propia cláusula "si" mirando esa variable. Esa es una manera horrible de escribir código.
¿Alguien sabe de una mejor manera de hacer esto en un IntentService?
Como @budius ya mencionó en su comentario, debe establecer un valor booleano en el Service
cuando hace clic en ese botón:
// your Activity.java
public boolean onClick() {
//...
mService.performTasks = false;
mService.stopSelf();
}
Y en su manejo de Intent
, antes de realizar la importante tarea de enviar / enviar la información de intención, solo use ese booleano:
// your Service.java
public boolean performTasks = true;
protected void onHandleIntent(Intent intent) {
Bundle intentInfo = intent.getBundle();
if (this.performTasks) {
// Then handle the intent...
}
}
De lo contrario, el Servicio hará su tarea de procesar ese Intent
. Así es como debía ser usado, porque de lo contrario no veo cómo podrías resolverlo si miras el código central.
Detener un hilo o un proceso inmediatamente a menudo es algo sucio. Sin embargo, debería estar bien si su servicio es apátrida.
Declare el servicio como un proceso separado en el manifiesto:
<service
android:process=":service"
...
Y cuando quiera detener su ejecución, simplemente mate ese proceso:
ActivityManager am = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
List<RunningAppProcessInfo> runningAppProcesses = am.getRunningAppProcesses();
Iterator<RunningAppProcessInfo> iter = runningAppProcesses.iterator();
while(iter.hasNext()){
RunningAppProcessInfo next = iter.next();
String pricessName = getPackageName() + ":service";
if(next.processName.equals(pricessName)){
Process.killProcess(next.pid);
break;
}
}
En el caso de IntentService , no se detiene ni toma ninguna otra solicitud a través de alguna acción de intención hasta que su método onHandleIntent
complete la solicitud anterior.
Si intentamos iniciar IntentService nuevamente con alguna otra acción, onHandleIntent
se onHandleIntent
cuando finalice el intento / tarea anterior.
También stopService(intent);
o stopSelf();
no funciona hasta que el método onHandleIntent()
finaliza su tarea.
Así que creo que aquí la mejor solución es usar el Service
normal aquí.
¡Espero que ayude!
Este es el truco, haga uso de una variable estática volátil y verifique la condición de continuación en algunas líneas de su servicio para que el servicio continue se compruebe:
class MyService extends IntentService {
public static volatile boolean shouldContinue = true;
public MyService() {
super("My Service");
}
@Override
protected void onHandleIntent(Intent intent) {
doStuff();
}
private void doStuff() {
// do something
// check the condition
if (shouldContinue == false) {
stopSelf();
return;
}
// continue doing something
// check the condition
if (shouldContinue == false) {
stopSelf();
return;
}
// put those checks wherever you need
}
}
y en tu actividad haz esto para detener tu servicio,
MyService.shouldContinue = false;
He usado un BroadcastReceiver dentro del servicio que simplemente pone un stop booleano en verdadero. Ejemplo:
private boolean stop=false;
public class StopReceiver extends BroadcastReceiver {
public static final String ACTION_STOP = "stop";
@Override
public void onReceive(Context context, Intent intent) {
stop = true;
}
}
@Override
protected void onHandleIntent(Intent intent) {
IntentFilter filter = new IntentFilter(StopReceiver.ACTION_STOP);
filter.addCategory(Intent.CATEGORY_DEFAULT);
StopReceiver receiver = new StopReceiver();
registerReceiver(receiver, filter);
// Do stuff ....
//In the work you are doing
if(stop==true){
unregisterReceiver(receiver);
stopSelf();
}
}
Luego, desde la llamada a la actividad:
//STOP SERVICE
Intent sIntent = new Intent();
sIntent.setAction(StopReceiver.ACTION_STOP);
sendBroadcast(sIntent);
Para detener el servicio
PD: utilizo un booleano porque en mi caso dejo el servicio mientras estoy en un bucle, pero probablemente pueda llamar a unregisterReceiver y stopSelf en onReceive.
PD2: no olvides llamar a anular el registro de Receptor si el servicio finaliza su trabajo normalmente o si obtienes un error de IntentReceiver filtrado.
Si IntentService
un IntentService
, entonces creo que está trabado haciendo algo como usted describe, donde el código onHandleIntent()
tiene que sondear para su señal de "detener".
Si su tarea de fondo es potencialmente larga, y si necesita poder detenerla, creo que es mejor que use un Service
simple. En un nivel alto, escriba su Servicio a:
- Exponga un Intento de "inicio" para iniciar una AsyncTask para realizar su trabajo en segundo plano, guardando una referencia a esa AsyncTask recién creada.
- Exponga un Intento "cancelar" para invocar
AsyncTask.cancel(true)
, o invoque onDestroy ()AsyncTask.cancel(true)
. - La actividad puede enviar la intención de "cancelar" o simplemente llamar a stopService ().
A cambio de la capacidad de cancelar el trabajo de fondo, el Servicio asume las siguientes responsabilidades:
- AsyncTask
doInBackground()
tendrá que gestionar elegantemente InterruptedException y / o comprobar periódicamente Thread.interrupted () y devolver "early". - El Servicio deberá asegurarse de
stopSelf()
(tal vez en AsyncTask onPostExecute / onCancelled).
@Override
protected void onHandleIntent(Intent intent) {
String action = intent.getAction();
if (action.equals(Action_CANCEL)) {
stopSelf();
} else if (action.equals(Action_START)) {
//handle
}
}
Espero que funcione.
context.stopService(GPSService);