telefono - Mínimo servicio de Android en primer plano matado en un teléfono de gama alta
mejores celulares gama media alta 2018 (3)
Estoy intentando crear una aplicación que permita a los usuarios registrar rutas (ubicaciones / GPS). Para garantizar que las ubicaciones se registren incluso cuando la pantalla está apagada, he creado un foreground service
para el registro de ubicaciones. Dagger2
las ubicaciones en una Room Database
que se inyecta en mi servicio utilizando Dagger2
.
Sin embargo, este servicio es eliminado por Android que, por supuesto, no es bueno. Podría suscribirme a advertencias de poca memoria, pero eso no resuelve el problema subyacente de que mi servicio muera después de ~ 30 minutos en un moderno teléfono de gama alta con Android 8.0
He creado un proyecto mínimo con solo una actividad "Hola mundo" y el servicio: https://github.com/RandomStuffAndCode/AndroidForegroundService
El servicio se inicia en mi clase de Application
y el registro de rutas se inicia a través de un Binder
:
// Application
@Override
public void onCreate() {
super.onCreate();
mComponent = DaggerAppComponent.builder()
.appModule(new AppModule(this))
.build();
Intent startBackgroundIntent = new Intent();
startBackgroundIntent.setClass(this, LocationService.class);
startService(startBackgroundIntent);
}
// Binding activity
bindService(new Intent(this, LocationService.class), mConnection, Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT);
// mConnection starts the route logging through `Binder` once connected. The binder calls startForeground()
Probablemente no necesito el indicador BIND_AUTO_CREATE
, he estado probando diferentes indicadores en un intento por no BIND_AUTO_CREATE
mi servicio, sin suerte hasta ahora.
Usando el generador de perfiles no parece que tenga ninguna pérdida de memoria, el uso de la memoria es estable en ~ 35 mb:
Al adb shell dumpsys activity processes > tmp.txt
puedo confirmar que foregroundServices=true
y mi servicio figuran en la octava posición en la lista de LRU:
Proc # 3: prcp F/S/FGS trm: 0 31592:com.example.foregroundserviceexample/u0a93 (fg-service)
Parece que no es posible crear un servicio de primer plano en el que puedas confiar para que no te maten. Entonces, ¿qué podemos hacer? Bien...
- Ponga el servicio en un proceso separado, en un intento de dejar que Android mate la UI / Actividades sin dejar el servicio solo. Probablemente ayudaría, pero no parece una garantía.
- Persistir todo en el servicio, por ejemplo, en una base de datos de habitaciones. Cada variable, cada clase personalizada, cada vez que cualquiera de los cambios y luego inicie el servicio con
START_STICKY
. Esto parece un poco inútil y no conduce a un código muy hermoso, pero probablemente funcionaría ... algo. Dependiendo de cuánto tiempo le lleve a Android volver a crear el servicio después de matarlo, se puede perder una gran parte de las ubicaciones.
¿Es este realmente el estado actual de hacer cosas en segundo plano en Android? ¿No hay una mejor manera?
EDITAR: La inclusión en la lista blanca de la aplicación para la optimización de la batería (deshabilitarla) no impide que se cancele mi servicio
EDITAR: el uso de Context.startForegroundService()
para iniciar el servicio no mejora la situación
EDIT: Así que, de hecho, esto solo ocurre en algunos dispositivos, pero ocurre constantemente en ellos. Supongo que tienes que elegir entre no admitir a un gran número de usuarios o escribir código realmente feo. Increíble.
Le sugiero que use estos: AlarmManager
, WakeLock
, WakeLock
, Thread
, WakefulBroadcastReceiver
, Handler
, Looper
Supongo que ya está utilizando esos "procesos separados" y otros ajustes también.
Así que en su clase de Application
:
MyApp.java
:
import android.app.AlarmManager;
import android.app.Application;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.PowerManager;
import android.util.Log;
public final class MyApp extends Application{
public static PendingIntent pendingIntent = null;
public static Thread infiniteRunningThread;
public static PowerManager pm;
public static PowerManager.WakeLock wl;
@Override
public void onCreate(){
try{
Thread.setDefaultUncaughtExceptionHandler(
(thread, e)->restartApp(this, "MyApp uncaughtException:", e));
}catch(SecurityException e){
restartApp(this, "MyApp uncaughtException SecurityException", e);
e.printStackTrace();
}
pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
if(pm != null){
wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "TAG");
wl.acquire(10 * 60 * 1000L /*10 minutes*/);
}
infiniteRunningThread = new Thread();
super.onCreate();
}
public static void restartApp(Context ctx, String callerName, Throwable e){
Log.w("TAG", "restartApp called from " + callerName);
wl.release();
if(pendingIntent == null){
pendingIntent =
PendingIntent.getActivity(ctx, 0,
new Intent(ctx, ActivityMain.class), 0);
}
AlarmManager mgr = (AlarmManager) ctx.getSystemService(Context.ALARM_SERVICE);
if(mgr != null){
mgr.set(AlarmManager.RTC_WAKEUP,
System.currentTimeMillis() + 10, pendingIntent);
}
if(e != null){
e.printStackTrace();
}
System.exit(2);
}
}
Y luego a su servicio:
ServiceTrackerTest.java
:
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.graphics.BitmapFactory;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.PowerManager;
import android.support.v4.app.NotificationCompat;
import android.support.v4.content.WakefulBroadcastReceiver;
public class ServiceTrackerTest extends Service{
private static final int SERVICE_ID = 2018;
private static PowerManager.WakeLock wl;
@Override
public IBinder onBind(Intent intent){
return null;
}
@Override
public void onCreate(){
super.onCreate();
try{
Thread.setDefaultUncaughtExceptionHandler(
(thread, e)->MyApp.restartApp(this,
"called from ServiceTracker onCreate "
+ "uncaughtException:", e));
}catch(SecurityException e){
MyApp.restartApp(this,
"called from ServiceTracker onCreate uncaughtException "
+ "SecurityException", e);
e.printStackTrace();
}
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
if(pm != null){
wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "TAG");
wl.acquire(10 * 60 * 1000L /*10 minutes*/);
}
Handler h = new Handler();
h.postDelayed(()->{
MyApp.infiniteRunningThread = new Thread(()->{
try{
Thread.setDefaultUncaughtExceptionHandler(
(thread, e)->MyApp.restartApp(this,
"called from ServiceTracker onCreate "
+ "uncaughtException "
+ "infiniteRunningThread:", e));
}catch(SecurityException e){
MyApp.restartApp(this,
"called from ServiceTracker onCreate uncaughtException "
+ "SecurityException "
+ "infiniteRunningThread", e);
e.printStackTrace();
}
Looper.prepare();
infiniteRunning();
Looper.loop();
});
MyApp.infiniteRunningThread.start();
}, 5000);
}
@Override
public void onDestroy(){
wl.release();
MyApp.restartApp(this, "ServiceTracker onDestroy", null);
}
@SuppressWarnings("deprecation")
@Override
public int onStartCommand(Intent intent, int flags, int startId){
if(intent != null){
try{
WakefulBroadcastReceiver.completeWakefulIntent(intent);
}catch(Exception e){
e.printStackTrace();
}
}
startForeground(SERVICE_ID, getNotificationBuilder().build());
return START_STICKY;
}
private void infiniteRunning(){
//do your stuff here
Handler h = new Handler();
h.postDelayed(this::infiniteRunning, 300000);//5 minutes interval
}
@SuppressWarnings("deprecation")
private NotificationCompat.Builder getNotificationBuilder(){
return new NotificationCompat.Builder(this)
.setContentIntent(MyApp.pendingIntent)
.setContentText(getString(R.string.notification_text))
.setContentTitle(getString(R.string.app_name))
.setLargeIcon(BitmapFactory.decodeResource(getResources(),
R.drawable.ic_launcher))
.setSmallIcon(R.drawable.ic_stat_tracking_service);
}
}
Ignore "Deprecation" y esas cosas, úselas cuando no tenga otra opción. Creo que el código es claro y no necesita ser explicado. Solo se trata de soluciones y sugerencias alternativas.
Sé que es tarde, pero esto puede ayudar a alguien. Yo también enfrenté el mismo problema de mantener vivo el servicio de primer plano sin ser eliminado por el sistema operativo de diferentes fabricantes. La mayoría del sistema operativo del fabricante chino mata el servicio de primer plano, incluso si se agrega a las listas de excepciones (Batería, Limpiador, etc.) y se permite que se inicie automáticamente.
Encontré este link que me resolvió este problema de larga data de mantener vivo el servicio.
Todo lo que tiene que hacer es ejecutar su servicio de primer plano en un proceso separado. Eso es.
Puede hacerlo agregando android:process
a su servicio en su AndroidManifest.xml.
Por ejemplo:
<service android:name=".YourService"
android:process=":yourProcessName" />
Puede consultar los docs para saber más sobre android:process
EDITAR: SharedPreferences no funcionará en varios procesos. En este caso, debe optar por los métodos de IPC (Inter Process Communication) o puede usar ContentProviders
para almacenar y acceder a sus datos para que se utilicen en todos los procesos. Referido de los docs .
Un servicio iniciado por startForeground
relaciona con el segundo proceso visible de grupo más importante:
Un proceso visible está haciendo un trabajo que el usuario conoce actualmente, por lo que matarlo tendría un impacto negativo notable en la experiencia del usuario. Un proceso se considera visible en las siguientes condiciones:
Está ejecutando una Actividad que es visible para el usuario en pantalla pero no en el primer plano (se ha llamado su método onPause ()). Esto puede ocurrir, por ejemplo, si la Actividad de primer plano se muestra como un cuadro de diálogo que permite ver la Actividad anterior detrás de ella.
Tiene un servicio que se ejecuta como un servicio en primer plano, a través de Service.startForeground () (que le pide al sistema que trate el servicio como algo que el usuario conoce, o que es esencialmente visible para ellos).
- Es el alojamiento de un servicio que el sistema está utilizando para una función particular que el usuario conoce, como un fondo de pantalla en vivo, un servicio de método de entrada, etc.
La cantidad de estos procesos que se ejecutan en el sistema es menos limitada que los procesos en primer plano, pero aún está relativamente controlada. Estos procesos se consideran extremadamente importantes y no se eliminarán a menos que sea necesario hacerlo para mantener en ejecución todos los procesos en primer plano .
Dicho esto, nunca puedes estar seguro de que tu servicio no se cancele en ningún momento. Ej. Presión de memoria, batería baja, etc. Vea who-lives-and-who-dies .
Para saber cómo manejarlo, básicamente respondiste la pregunta tú mismo. El camino a seguir es START_STICKY
:
Para los servicios iniciados, hay dos modos de operación principales adicionales en los que pueden decidir ejecutarse, dependiendo del valor que devuelvan de
onStartCommand():
START_STICKY
se usa para servicios que se inician y detienen explícitamente según sea necesario, mientras queSTART_NOT_STICKY
oSTART_REDELIVER_INTENT
se usan para servicios que solo deben permanecer en ejecución mientras se procesan los comandos que se les envían . Vea la documentación vinculada para más detalles sobre la semántica.
Como guía general, debe hacer lo menos posible en el servicio en segundo plano (primer plano de mineral), es decir, solo hacer el seguimiento de la ubicación y mantener todo lo demás en su actividad de primer plano. Solo el seguimiento debe requerir muy poca configuración y se puede cargar rápidamente. Además, cuanto más pequeño sea el servicio, menor será la probabilidad de matarlo. Su actividad será restaurada por el sistema en el estado en que se encontraba antes de que entrara en segundo plano, siempre y cuando no se elimine también. Un "arranque en frío" de la actividad de primer plano, por otro lado, no debería ser un problema.
No lo considero tan feo, porque esto garantiza que el teléfono siempre brinda la mejor experiencia al usuario. Esto es lo más importante que tiene que hacer. Que algunos dispositivos cierren los servicios después de 30 minutos (posiblemente sin interacción del usuario) es desafortunado.
Así que, como dijiste, tienes que
Persistir todo en el servicio, por ejemplo, en una base de datos de habitaciones. Cada variable, cada clase personalizada, cada vez que cualquiera de ellas cambia y luego inicia el servicio con START_STICKY.
Ver creando un servicio sin fin.
Pregunta implícita:
Dependiendo de cuánto tiempo le lleve a Android volver a crear el servicio después de matarlo, se puede perder una gran parte de las ubicaciones.
Esto generalmente toma muy poco tiempo. Especialmente porque puede usar la Api del proveedor de ubicación fusionada para las actualizaciones de ubicación, que es un servicio de sistema independiente y es muy poco probable que se cancele. Por lo tanto, depende principalmente del tiempo que necesite para volver a crear el servicio en onStartCommand
.
También tenga en cuenta que a partir de Android 8.0 necesita usar un
forground service
debido a los límites de ubicación de fondo .
Edit: Como se mencionó recientemente en las noticias: algunos fabricantes le pueden dar dificultades para mantener su servicio en funcionamiento. El sitio https://dontkillmyapp.com/ realiza un seguimiento de los fabricantes y las posibles mitigaciones para su dispositivo. Oneplus es actualmente (29.01.19) uno de los peores delincuentes.
Al lanzar sus teléfonos 1 + 5 y 1 + 6, OnePlus introdujo uno de los límites de fondo más severos en el mercado hasta la fecha, eclipsando incluso los realizados por Xiaomi o Huawei. Los usuarios no solo tenían que habilitar configuraciones adicionales para que sus aplicaciones funcionen correctamente, sino que esas configuraciones incluso se restablecen con la actualización del firmware, de modo que las aplicaciones vuelven a fallar y se requiere que los usuarios vuelvan a habilitar esas configuraciones con regularidad.
Solución para los usuarios.
Desactive Configuración del sistema> Aplicaciones> Ícono de engranaje> Acceso especial> Optimización de la batería.
lamentablemente hay
No hay solución conocida en el extremo desarrollador