¿Por qué se reinicia mi servicio de Android cuando se cancela el proceso, aunque utilicé START_NOT_STICKY?
Mi aplicación utiliza un patrón en el que inicio un servicio con Contexto # startService () así como enlazar con Context # bindService () . Esto es para que pueda controlar la vida útil del servicio independientemente de si algún cliente está actualmente vinculado a él. Sin embargo, me di cuenta recientemente que cada vez que mi aplicación es asesinada por el sistema, pronto reinicia los servicios que se estaban ejecutando. En este punto el servicio nunca se le dirá que se detenga, y esto está causando el drenaje de la batería cuando sucede. Aquí hay un ejemplo mínimo:
Encontré a alguien con un problema similar aquí , pero nunca fue diagnosticado o resuelto.
- ¿Cómo puedo llamar a startIntentSenderForResult en el servicio correctamente?
- Notificación de primer plano en Android inicia nueva Actividad (vía pendingIntent) en lugar de una existente
- Cree sólo una instancia de Service (Android)
- Servicio de Android - startService se llama varias veces y hace que el valor mixup ...
- Nombre de la Actividad o Receptor que llama al Servicio
Servicio:
@Override public void onCreate() { Toast.makeText(this, "onCreate", Toast.LENGTH_LONG).show(); } @Override public int onStartCommand(Intent intent, int flags, int startId) { return START_NOT_STICKY; } @Override public IBinder onBind(Intent intent) { return new Binder(); }
Actividad:
@Override protected void onStart() { super.onStart(); Intent service = new Intent(this, BoundService.class); startService(service); bindService(service, mServiceConnection, 0); } @Override protected void onStop() { unbindService(mServiceConnection); Toast.makeText(this, "unbindService", Toast.LENGTH_SHORT).show(); super.onStop(); }
Para probarlo, lancé la aplicación, que inició el servicio y se unió a ella. Entonces me retiré de la aplicación, que desvincula (pero deja el servicio en marcha). Entonces lo hice
$ adb shell am kill com.tavianator.servicerestart
Y con seguridad, 5 segundos más tarde, el "onCreate" brindis aparece, lo que indica que el servicio comenzó de nuevo. Logcat muestra esto:
$ adb logcat | grep BoundService W/ActivityManager( 306): Scheduling restart of crashed service com.tavianator.servicerestart/.BoundService in 5000ms I/ActivityManager( 306): Start proc com.tavianator.servicerestart for service com.tavianator.servicerestart/.BoundService: pid=20900 uid=10096 gids={1028}
Si reemplazo el patrón startService () con BIND_AUTO_CREATE, el problema no se produce (incluso si bloqueo la aplicación mientras todavía está enlazada al servicio). También funciona si nunca me enlazo con el servicio. Pero la combinación de empezar, enlazar y desatar parece no dejar que mi servicio muera.
El uso de dumpsys antes de matar la aplicación muestra lo siguiente:
$ adb shell dumpsys activity services com.tavianator.servicerestart ACTIVITY MANAGER SERVICES (dumpsys activity services) Active services: * ServiceRecord{43099410 com.tavianator.servicerestart/.BoundService} intent={cmp=com.tavianator.servicerestart/.BoundService} packageName=com.tavianator.servicerestart processName=com.tavianator.servicerestart baseDir=/data/app/com.tavianator.servicerestart-2.apk dataDir=/data/data/com.tavianator.servicerestart app=ProcessRecord{424fb5c8 20473:com.tavianator.servicerestart/u0a96} createTime=-20s825ms lastActivity=-20s825ms executingStart=-5s0ms restartTime=-20s825ms startRequested=true stopIfKilled=true callStart=true lastStartId=1 Bindings: * IntentBindRecord{42e5e7c0}: intent={cmp=com.tavianator.servicerestart/.BoundService} binder=android.os.BinderProxy@42aee778 requested=true received=true hasBound=false doRebind=false
- ¿Cuándo se destruye un servicio iniciado y enlazado?
- Android: Comprobar la conexión con el servidor
- Si android reinicia un servicio es onCreate llamado de nuevo?
- Problema con android: protectionLevel = "firma"
- Android: Mejor manera de obtener datos del servidor (Widget + aplicación)
- Subir varias imágenes al servidor en una cola
- Aplicación de Android como servicio sin actividad
- Servicio de Android con la ubicación de los servicios de Google Play que hace que la aplicación se abra al azar
Consulte la sección de este documento : Cambios en el ciclo de vida del servicio (desde 1.6)
actualizado
Después de algunas investigaciones (han creado el proyecto, ejecutar el comando descrito paso a paso, etc) he encontrado que el código está funcionando exactamente como se describe en "Service lifecycle changes"
lo que encontré:
adb shell am kill com.tavianator.servicerestart
no mata nada
( adb shell
, entonces en shell am kill com.tavianator.servicerestart
Se enfrentará con el mensaje de error Error: Unknown command: kill
)
asi que,
Ejecutar su aplicación,
Ejecutar adb shell
En shell ejecutar ps
comando
Encuentra el número PID de tu aplicación
En el comando de ejecución de shell kill <app_xx_PID>
Dónde está tu número de identificación personal
Repetir los pasos de matar para el servicio (si se está ejecutando en su propio proceso)
Verifique si el servicio está en ejecución (no debería), reiniciado después de 5-7 segundos
actualizar
Una solución (no lo suficientemente bueno, pero utilizable en algunos casos) es stopSelf()
por ejemplo:
@Override public int onStartCommand(Intent intent, int flags, int startId) { Toast.makeText(this, "onStartCommand", Toast.LENGTH_LONG).show(); stopSelf(); return START_NOT_STICKY; }
actualizar
Solución actualizada
void writeState(int state) { Editor editor = getSharedPreferences("serviceStart", MODE_MULTI_PROCESS) .edit(); editor.clear(); editor.putInt("normalStart", state); editor.commit(); } int getState() { return getApplicationContext().getSharedPreferences("serviceStart", MODE_MULTI_PROCESS).getInt("normalStart", 1); } @Override public int onStartCommand(Intent intent, int flags, int startId) { if (getState() == 0) { writeState(1); stopSelf(); } else { writeState(0); Toast.makeText(this, "onStartCommand", Toast.LENGTH_LONG).show(); } return START_NOT_STICKY; }
¿Por qué el servicio se restringe cuando se cancela el proceso?
Según este documento :
Cuando se inicia un servicio, tiene un ciclo de vida independiente del componente que lo inició y el servicio puede ejecutarse en segundo plano indefinidamente, incluso si el componente que lo inició se destruye. Como tal, el servicio debe detenerse cuando su trabajo se realiza llamando a stopSelf () , u otro componente puede detenerlo llamando a stopService () .
Precaución : Es importante que la aplicación detenga sus servicios cuando se hace funcionar, para evitar desperdiciar recursos del sistema y consumir energía de la batería. Si es necesario, otros componentes pueden detener el servicio llamando a stopService (). Incluso si habilita la vinculación para el servicio, siempre debe detener el servicio usted mismo si alguna vez recibió una llamada a onStartCommand ()
De otra mano el documento dice:
* START_NOT_STICKY * – Si el sistema mata el servicio después de devuelto onStartCommand (), no vuelva a crear el servicio, a menos que haya intenciones pendientes de entregar. Esta es la opción más segura para evitar ejecutar su servicio cuando no es necesario y cuando la aplicación simplemente puede reiniciar cualquier trabajo sin terminar.
Por lo tanto, después de leer este documento y algunos experimentos creo que el sistema trata a los servicios manualmente muertos como inacabados (se bloqueó: @see W/ActivityManager(306): Scheduling restart of crashed service
bloqueado) y lo reinicia a pesar del valor devuelto por onStartCommand.
StopSelf () o stopService () – no se reinicia , ¿por qué no si el trabajo hecho?
Creo que el problema aquí es que para un servicio que se inició llamando a startService()
, la contabilidad interna de sus enlaces (afectados por las llamadas a bindService()
/ unbindService()
) están de alguna manera impidiendo que el servicio se derribe correctamente después de que Está programado para reiniciarse cuando el proceso se cancela.
Dependiendo de su preferencia, parece que hay tres alternativas para evitar este problema (usted mencionó los dos primeros ya en su pregunta):
-
Utilice sólo
startService()
/stopService()
, no utilicebindService()
/unbindService()
. -
Utilice
bindService()
/unbindService()
solamente (conBIND_AUTO_CREATE
), no utilicestartService()
/stopService()
en absoluto. -
Si necesita
startService()
/stopService()
ybindService()
/unbindService()
, anule el métodoonUnbind()
en su servicio y llame astopSelf()
@Override public boolean onUnbind(Intent intent) { stopSelf(); return super.onUnbind(intent); }
De mis pruebas:
-
Utilizando la primera alternativa se imprimirá esta línea en el registro:
W / ActivityManager (nnn): Programación de reinicio del servicio bloqueado xxxx en 5000ms
Pero el servicio no será realmente reiniciado después de eso.
-
La segunda alternativa hará que su servicio sea destruido después de desvincularse (en la actividad
onStop()
en su ejemplo). -
La tercera alternativa básicamente hará que su código se comporte como la segunda alternativa (sin necesidad de cambiarla por mucho).
¡Espero que esto ayude!
¿Qué tal reconsiderar su patrón ?
El indicador START_NOT_STICKY apareció en algún momento durante la evolución de Android (después de 1.6?). Lo más probable es que enfrente una sutil regresión en el ciclo de vida del servicio, que se introdujo en ese momento. Por lo tanto, incluso si una solución igualmente sutil se encuentra por ahora – y verificado mágicamente para trabajar en todos los dispositivos posibles – no necesariamente funcionará bajo nuevas versiones de Android.
De todos modos, ese patrón de ciclo de vida parece inusual, y que podría ser la razón de su ejecución en este problema exótico.
Consideremos dos casos.
-
Normal (?). Alguna actividad inicia el servicio. Pronto después de eso consigue atado, usado, unbound – entonces qué? Detenido de alguna manera? ¿Cómo es que no hay un drenaje significativo de la batería en este caso?
-
Anormal. Servicio muerto en algún punto (al azar?). Cuando se reinicia, se implementa alguna suposición equivocada y se mantiene activo, haciendo algo que drena la batería?
Todo parece bastante extraño.
Yo analizaría los procesos concurrentes en su producto. Todos los casos significativos. Algunos diagramas, etc. Lo más probable es que, como resultado, se eliminaría la necesidad de tal patrón.
De lo contrario, parece que cualquier solución será un hack frágil.
- Android – aplicar selectableItemBackground en xml con soporte v7
- Deshabilitar gesture listener en DrawerLayout