Android 8.0: java.lang.IllegalStateException: no se permite iniciar la intención del servicio

360

Al iniciar la aplicación, la aplicación inicia el servicio que debería realizar alguna tarea de red. Después de apuntar al nivel 26 de API, mi aplicación no puede iniciar el servicio en Android 8.0 en segundo plano.

Causado por: java.lang.IllegalStateException: No se permite iniciar el servicio Intención {cmp = my.app.tt / com.my.service}: la aplicación está en segundo plano uidRecord {90372b1 u0a136 CEM inact procs: 1 seq (0,0 , 0)}

según tengo entendido relacionado con: Límites de ejecución en segundo plano

El método startService () ahora arroja una IllegalStateException si una aplicación dirigida a Android 8.0 intenta usar ese método en una situación en la que no está permitido crear servicios en segundo plano.

" en una situación en la que no está permitido ", ¿qué significa realmente? Y como arreglarlo. No quiero establecer mi servicio como "primer plano"

phnmnn
fuente
44
Significa que no puede iniciar un servicio cuando su aplicación está en segundo plano
Tim
22
esto no tiene nada que ver con los permisos de tiempo de ejecución
Tim
10
Usar en startForegroundService()lugar de startService().
frogatto
2
Puede intentar usar targetSdkVersion 25 pero compilar con compileSdkVersion 26. De esta manera puede usar nuevas clases de Android 8 y la biblioteca de soporte más reciente, pero su aplicación no estará limitada por los Límites de ejecución en segundo plano.
Kacper Dziubek
2
@KacperDziubek Eso debería funcionar, pero es una solución temporal, ya que se requerirá apuntar a SDK26 en otoño de 2018.
RightHandedMonkey

Respuestas:

194

Las situaciones permitidas son una lista blanca temporal donde el servicio en segundo plano se comporta igual que antes de Android O.

En determinadas circunstancias, una aplicación en segundo plano se coloca en una lista blanca temporal durante varios minutos. Mientras una aplicación está en la lista blanca, puede iniciar servicios sin limitación, y sus servicios en segundo plano pueden ejecutarse. Una aplicación se coloca en la lista blanca cuando maneja una tarea que es visible para el usuario, como:

  • Manejo de un mensaje de alta prioridad de Firebase Cloud Messaging (FCM).
  • Recibir una transmisión, como un mensaje SMS / MMS.
  • Ejecutar un PendingIntent desde una notificación.
  • Iniciar un VpnService antes de que la aplicación VPN pase a primer plano.

Fuente: https://developer.android.com/about/versions/oreo/background.html

En otras palabras, si su servicio en segundo plano no cumple con los requisitos de la lista blanca, debe usar el nuevo JobScheduler . Básicamente es lo mismo que un servicio en segundo plano, pero se llama periódicamente en lugar de ejecutarse en segundo plano continuamente.

Si está utilizando un IntentService, puede cambiar a un JobIntentService. Vea la respuesta de @ kosev a continuación .

Murat Karagöz
fuente
Tengo un bloqueo después de querer iniciar el servicio justo después de recibir el mensaje GCM de "alto" prio. Todavía uso GCM: "com.google.android.gms: play-services-gcm: 11.4.2", no 'com.google.firebase: firebase-messaging: 11.4.2'. Aunque no estoy seguro de que importe ...
Alex Radzishevsky
"Es básicamente lo mismo que un servicio en segundo plano, pero se llama periódicamente en lugar de ejecutarse en segundo plano continuamente". - No estoy seguro de lo que quiere decir con esto, ya que los servicios de Android nunca se han ejecutado continuamente. Comienzan, corren, luego se apagan.
Melllvar
2
¿Es FirebaseInstanceIdService y su onTokenRefreshmétodo un mensaje FCM de alta prioridad?
Cord Rehn el
@phnmnn no, GCMTaskService realmente no sigue a FCM, por lo tanto, no funcionan.
Abhinav Upadhyay
44
¿No debería usar WorkManager (aquí: developer.android.com/topic/libraries/architecture/workmanager ) en lugar de JobScheduler u otros? Me refiero a esto: youtu.be/IrKoBFLwTN0
desarrollador de Android
256

Tengo solución Para dispositivos anteriores a 8.0, solo tiene que usar startService(), pero para dispositivos posteriores a 7.0, debe usar startForgroundService(). Aquí hay una muestra de código para iniciar el servicio.

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

Y en la clase de servicio, agregue el siguiente código para la notificación:

@Override
public void onCreate() {
    super.onCreate();
    startForeground(1,new Notification());
}

Donde O es la versión 26 de Android.

Sagar Kacha
fuente
99
Un servicio en primer plano es algo que el usuario conocerá y que necesita una notificación. También ANR si se ejecuta demasiado tiempo. Por lo tanto, no es realmente una respuesta adecuada si la aplicación ya se está ejecutando en segundo plano.
SimonH
80
Hay una ContextCompat.startForegroundService(...)lib de soporte que se puede usar en su lugar.
jayeffkay
37
Esa no es una solución.
JacksOnF1re
17
También estoy de acuerdo en que esto no es una solución. Es una solución alternativa y ayuda, pero los límites de fondo en Oreo se introdujeron por una razón. Omitir esos límites de esta manera definitivamente no es el enfoque correcto (a pesar de que funciona). La mejor manera es usar JobScheduler (consulte la respuesta aceptada).
Vratislav Jindra
66
No creo que sea una buena experiencia de usuario si debe mostrar una notificación en primer plano vacía. Teniendo en cuenta el hecho de que debes hacerlo. - Android 8.0 presenta el nuevo método startForegroundService () para iniciar un nuevo servicio en primer plano. Después de que el sistema ha creado el servicio, la aplicación tiene cinco segundos para llamar al método startForeground () del servicio para mostrar la notificación visible para el usuario del nuevo servicio. Si la aplicación no llama a startForeground () dentro del límite de tiempo, el sistema detiene el servicio y declara que la aplicación es ANR.
heeleeaz
85

La mejor manera es usar JobIntentService que utiliza el nuevo JobScheduler para Oreo o los servicios antiguos si no están disponibles.

Declara en tu manifiesto:

<service android:name=".YourService"
         android:permission="android.permission.BIND_JOB_SERVICE"/>

Y en su servicio debe reemplazar onHandleIntent por onHandleWork:

public class YourService extends JobIntentService {

    public static final int JOB_ID = 1;

    public static void enqueueWork(Context context, Intent work) {
        enqueueWork(context, YourService.class, JOB_ID, work);
    }

    @Override
    protected void onHandleWork(@NonNull Intent intent) {
        // your code
    }

}

Entonces comienzas tu servicio con:

YourService.enqueueWork(context, new Intent());
kosev
fuente
¿Cómo puede llamar a un método no estático dentro de un método estático? ¿Puede usted explicar por favor?
Maddy
@Maddy enqueueWork(...)es un método estático también.
hgoebl
2
¿Dónde llamaría a YourService.enqueueWork (context, new Intent ()); ? ¿Del receptor de transmisión?
TheLearner
No creo que esta sea la solución más fácil. Vea mi comentario a continuación sobre WorkManager. Utiliza JobIntentService cuando corresponde, pero tiene mucha menos placa de caldera.
TALE
36

Si el servicio se ejecuta en un subproceso en segundo plano extendiéndolo IntentService, puede reemplazarlo IntentServicecon el JobIntentServiceque se proporciona como parte de la Biblioteca de soporte de Android

La ventaja de usar JobIntentServicees que se comporta como un IntentServicedispositivo pre-O y en O y superior, lo envía como un trabajo

JobSchedulertambién se puede usar para trabajos periódicos / bajo demanda. Pero, asegúrese de manejar la compatibilidad con versiones anteriores, ya que JobSchedulerAPI solo está disponible desde API 21

Harini S
fuente
1
El problema con JobIntentService es que Android puede programar su trabajo de manera bastante arbitraria, y no puede iniciarse implícitamente sin algunos ajustes, a diferencia de un IntentService. Ver stackoverflow.com/questions/52479262/…
kilokahn el
15

En Oreo, Android define límites a los servicios en segundo plano .

Para mejorar la experiencia del usuario, Android 8.0 (API nivel 26) impone limitaciones sobre lo que las aplicaciones pueden hacer mientras se ejecutan en segundo plano.

Aún así, si necesita un servicio siempre en ejecución, puede utilizar el servicio en primer plano.

Limitaciones del servicio en segundo plano: mientras una aplicación está inactiva, existen límites para el uso de los servicios en segundo plano. Esto no se aplica a los servicios en primer plano, que son más notorios para el usuario.

Para que pueda hacer un servicio en primer plano . Deberá mostrar una notificación al usuario cuando su servicio se esté ejecutando. Ver esta respuesta (Hay muchas otras)

Una solución si -

no quieres una notificación por tu servicio?

Puede realizar tareas periódicas, 1. inicia su servicio, 2. el servicio hará su trabajo y 3. se detendrá. De este modo, su aplicación no se considerará agotada.

Puede usar tareas periódicas con Alarm Manager , Job Scheduler , Evernote-Jobs o Work Manager .

He probado el servicio en ejecución permanente con Work-Manager.

Khemraj
fuente
WorkManager parece ser el mejor camino a seguir, suponiendo que el trabajo no tiene que ejecutarse de inmediato. Es compatible con API 14, utiliza JobScheduler en dispositivos con API 23+ y una combinación de BroadcastReceiver + AlarmManager en dispositivos con API 14-22
James Allen
la clave de WorkManager es que WorkManager está diseñado para tareas que son diferibles, es decir, que no es necesario que se ejecuten de inmediato
1919
13

Sí, eso se debe a que ya no puede iniciar servicios en segundo plano en API 26. Por lo tanto, puede iniciar ForegroundService por encima de API 26.

Tendrás que usar

ContextCompat.startForegroundService(...)

y publicar una notificación mientras procesa la fuga.

PK
fuente
1
El OP dijo específicamente que no quiere como primer plano. Esto se debe poner como un comentario o como parte de una respuesta más completa.
Ricardo A.
7

Como @kosev dijo en su respuesta , puede usar JobIntentService. Pero uso una solución alternativa: capto IllegalStateException e inicio el servicio como primer plano. Por ejemplo, esta función inicia mi servicio:

@JvmStatic
protected fun startService(intentAction: String, serviceType: Class<*>, intentExtraSetup: (Intent) -> Unit) {
    val context = App.context
    val intent = Intent(context, serviceType)
    intent.action = intentAction
    intentExtraSetup(intent)
    intent.putExtra(NEED_FOREGROUND_KEY, false)

    try {
        context.startService(intent)
    }
    catch (ex: IllegalStateException) {
        intent.putExtra(NEED_FOREGROUND_KEY, true)
        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            context.startForegroundService(intent)
        }
        else {
            context.startService(intent)
        }
    }
}

y cuando proceso Intent, hago tal cosa:

override fun onHandleIntent(intent: Intent?) {
    val needToMoveToForeground = intent?.getBooleanExtra(NEED_FOREGROUND_KEY, false) ?: false
    if(needToMoveToForeground) {
        val notification = notificationService.createSyncServiceNotification()
        startForeground(notification.second, notification.first)

        isInForeground = true
    }

    intent?.let {
        getTask(it)?.process()
    }
}
Alex Shevelev
fuente
Me gusta tu intento de capturar la solución. Para mí es una solución, porque a veces context.startServicefunciona en segundo plano - a veces no - esto se ve como la única mejor manera de lo contrario se tienen que implementar más código en su clase principal extending Applicationy implementing ActivityLifecycleCallbacksy realizar un seguimiento de si la aplicación está en el primer o segundo plano y comenzar su intención en consecuencia.
Pierre
¿Se puede atrapar esta excepción?
thecr0w
5

Según las notas de la versión de Firebase , afirman que el soporte para Android O se lanzó por primera vez en 10.2.1 (aunque recomendaría usar la versión más reciente).

agregue nuevas dependencias de mensajería firebase para Android O

compile 'com.google.firebase:firebase-messaging:11.6.2'

Actualice los servicios de Google Play y los repositorios de Google si es necesario.

Dhaval Jivani
fuente
Esto no responde a la pregunta, ni la pregunta tiene nada que ver con firebase. Se debe poner como un comentario.
Ricardo A.
5

Si alguna intención funcionaba bien antes cuando la aplicación está en segundo plano, ya no será el caso de Android 8 y superior. Solo se refiere a la intención que tiene que hacer algún procesamiento cuando la aplicación está en segundo plano.

Deben seguirse los siguientes pasos:

  1. La intención mencionada anteriormente debe usarse en JobIntentServicelugar de IntentService.
  2. La clase que se extiende JobIntentServicedebe implementar el onHandleWork(@NonNull Intent intent)método - y debe tener debajo del método, que invocará el onHandleWorkmétodo:

    public static void enqueueWork(Context context, Intent work) {
        enqueueWork(context, xyz.class, 123, work);
    }
  3. Llame enqueueWork(Context, intent)desde la clase donde se define su intención.

    Código de muestra:

    Public class A {
    ...
    ...
        Intent intent = new Intent(Context, B.class);
        //startService(intent); 
        B.enqueueWork(Context, intent);
    }

La siguiente clase estaba extendiendo previamente la clase de Servicio

Public Class B extends JobIntentService{
...

    public static void enqueueWork(Context context, Intent work) {
        enqueueWork(context, B.class, JobId, work);
    }

    protected void onHandleWork(@NonNull Intent intent) {
        ...
        ...
    }
}
  1. com.android.support:support-compates necesario para JobIntentService- yo uso 26.1.0 V.

  2. Lo más importante es asegurarse de que la versión de las bibliotecas de Firebase esté activa, al menos 10.2.1, tuve problemas con 10.2.0- si es que tienes alguno

  3. Su manifiesto debe tener el siguiente permiso para la clase de Servicio:

    service android:name=".B"
    android:exported="false"
    android:permission="android.permission.BIND_JOB_SERVICE"

Espero que esto ayude.

Chitraju Chaithanya
fuente
4

Veo muchas respuestas que recomiendan simplemente usar un ForegroundService. Para usar un Servicio Foreground, debe haber una notificación asociada a él. Los usuarios verán esta notificación. Dependiendo de la situación, pueden molestarse con su aplicación y desinstalarla.

La solución más fácil es usar el nuevo Componente de Arquitectura llamado WorkManager. Puede consultar la documentación aquí: https://developer.android.com/topic/libraries/architecture/workmanager/

Simplemente define su clase de trabajador que extiende a Trabajador.

public class CompressWorker extends Worker {

    public CompressWorker(
        @NonNull Context context,
        @NonNull WorkerParameters params) {
        super(context, params);
    }

    @Override
    public Worker.Result doWork() {

        // Do the work here--in this case, compress the stored images.
        // In this example no parameters are passed; the task is
        // assumed to be "compress the whole library."
        myCompress();

        // Indicate success or failure with your return value:
        return Result.SUCCESS;

        // (Returning RETRY tells WorkManager to try this task again
        // later; FAILURE says not to try again.)
    }
}

Luego, programa cuándo quieres ejecutarlo.

    OneTimeWorkRequest compressionWork = 
        new OneTimeWorkRequest.Builder(CompressWorker.class)
            .build();
    WorkManager.getInstance().enqueue(compressionWork);

¡Fácil! Hay muchas formas de configurar trabajadores. Admite trabajos recurrentes e incluso puede hacer cosas complejas como encadenar si lo necesita. Espero que esto ayude.

CUENTO
fuente
3
Actualmente WorkManager sigue siendo alfa.
pzulw
3
5 de marzo de 2019: versión estable de WorkManager 1.0.0.
phnmnn
debería usar WorkManager en lugar de usar interservice o JobIntentService
sivaBE35
1
WorkManager is intended for tasks that are deferrable—that is, not required to run immediately... podría ser más fácil, sin embargo, mi aplicación necesita un servicio en segundo plano que ejecute las solicitudes de los usuarios de inmediato.
Alguien en algún lugar
Si necesita que la tarea se complete de inmediato, debe utilizar un servicio en primer plano. El usuario verá una notificación y sabrá que está trabajando. Consulte los documentos si necesita ayuda para decidir qué usar. Tienen una muy buena guía para el procesamiento en segundo plano. developer.android.com/guide/background
TALE
4

Solución alternativa mediante JobScheduler, puede iniciar el servicio en segundo plano en intervalos regulares de tiempo.

En primer lugar, crea la clase llamada Util.java

import android.app.job.JobInfo;
import android.app.job.JobScheduler;
import android.content.ComponentName;
import android.content.Context;

public class Util {
// schedule the start of the service every 10 - 30 seconds
public static void schedulerJob(Context context) {
    ComponentName serviceComponent = new ComponentName(context,TestJobService.class);
    JobInfo.Builder builder = new JobInfo.Builder(0,serviceComponent);
    builder.setMinimumLatency(1*1000);    // wait at least
    builder.setOverrideDeadline(3*1000);  //delay time
    builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED);  // require unmetered network
    builder.setRequiresCharging(false);  // we don't care if the device is charging or not
    builder.setRequiresDeviceIdle(true); // device should be idle
    System.out.println("(scheduler Job");

    JobScheduler jobScheduler = null;
    if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
        jobScheduler = context.getSystemService(JobScheduler.class);
    }
    jobScheduler.schedule(builder.build());
   }
  }

Luego, haga la clase JobService llamada TestJobService.java

import android.app.job.JobParameters;
import android.app.job.JobService;
import android.widget.Toast;

  /**
   * JobService to be scheduled by the JobScheduler.
   * start another service
   */ 
public class TestJobService extends JobService {
@Override
public boolean onStartJob(JobParameters params) {
    Util.schedulerJob(getApplicationContext()); // reschedule the job
    Toast.makeText(this, "Bg Service", Toast.LENGTH_SHORT).show();
    return true;
}

@Override
public boolean onStopJob(JobParameters params) {
    return true;
  }
 }

Después de esa clase de receptor BroadCast llamada ServiceReceiver.java

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;

 public class ServiceReceiver extends BroadcastReceiver {
 @Override
public void onReceive(Context context, Intent intent) {
    Util.schedulerJob(context);
 }
}

Actualizar archivo de manifiesto con servicio y código de clase del receptor

<receiver android:name=".ServiceReceiver" >
        <intent-filter>
            <action android:name="android.intent.action.BOOT_COMPLETED" />
        </intent-filter>
    </receiver>
    <service
        android:name=".TestJobService"
        android:label="Word service"
        android:permission="android.permission.BIND_JOB_SERVICE" >

    </service>

Izquierda main_intent launcher al archivo mainActivity.java que se crea por defecto, y los cambios en el archivo MainActivity.java son

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

public class MainActivity extends AppCompatActivity {

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

WOOAAH !! El servicio en segundo plano comienza sin el servicio en primer plano

Anshul1507
fuente
2

Si está ejecutando su código en 8.0, la aplicación se bloqueará. Entonces comience el servicio en primer plano. Si debajo de 8.0 usa esto:

Intent serviceIntent = new Intent(context, RingtonePlayingService.class);
context.startService(serviceIntent);

Si es anterior o 8.0, use esto:

Intent serviceIntent = new Intent(context, RingtonePlayingService.class);
ContextCompat.startForegroundService(context, serviceIntent );
iamfaizuddin
fuente
Se recomienda usar solo los servicios de primer plano para los casos en que el usuario necesita saber que se está ejecutando un servicio. El ejemplo típico es para reproducir música de fondo. Hay otros casos que tienen sentido, pero no solo debe convertir todos sus servicios a servicios de primer plano. Considere convertir sus servicios para usar WorkManager de los componentes arquitectónicos de Google cuando solo necesite hacer un trabajo en segundo plano y tenga la garantía de que se ejecutará.
CUENTO
startForegroundService requiere permiso, de lo contrario java.lang.SecurityException: Permission Denial: startForeground from pid=13708, uid=10087 requires android.permission.FOREGROUND_SERVICE. Corrección en stackoverflow.com/a/52382711/550471
Alguien en algún lugar
1

si ha integrado la notificación push de mensajería firebase entonces,

Agregue nuevas dependencias de mensajería firebase para Android O (Android 8.0), debido a los Límites de ejecución en segundo plano .

compile 'com.google.firebase:firebase-messaging:11.4.0'

Actualice los servicios de Google Play y los repositorios de Google si es necesario.

Actualizar:

 compile 'com.google.firebase:firebase-messaging:11.4.2'
Samir Mangroliya
fuente
0

Use en startForegroundService()lugar de startService() y no olvide crear startForeground(1,new Notification());en su servicio dentro de los 5 segundos posteriores al inicio del servicio.

Arpit
fuente
2
Parece que la nueva Notificación () no funciona desde Android 8.1; Debe crear el canal para la notificación: stackoverflow.com/a/47533338/1048087
Prizoff
0

Debido a los controvertidos votos sobre esta respuesta (+ 4 / -4 a partir de esta edición), MIRE PRIMERO LAS OTRAS RESPUESTAS Y UTILICE ESTO SOLO COMO ÚLTIMO RECURSO . Solo usé esto una vez para una aplicación de red que se ejecuta como root y estoy de acuerdo con la opinión general de que esta solución no debe usarse en circunstancias normales.

Respuesta original a continuación:

Las otras respuestas son correctas, pero me gustaría señalar que otra forma de evitar esto es pedirle al usuario que desactive las optimizaciones de batería para su aplicación (esto generalmente no es una buena idea a menos que su aplicación esté relacionada con el sistema). Consulte esta respuesta para saber cómo solicitar la exclusión de las optimizaciones de batería sin que su aplicación sea bloqueada en Google Play.

También debe verificar si las optimizaciones de la batería están desactivadas en su receptor para evitar fallas a través de:

if (Build.VERSION.SDK_INT < 26 || getSystemService<PowerManager>()
        ?.isIgnoringBatteryOptimizations(packageName) != false) {
    startService(Intent(context, MyService::class.java))
} // else calling startService will result in crash
Dios mío
fuente
1
Pedirle a sus usuarios que le permitan tener un pase gratis usando la mayor cantidad de batería posible no es una buena solución. Considere convertir su código a una solución más amigable con la batería. Tus usuarios te lo agradecerán.
CUENTO
55
@TALE No todos los servicios en segundo plano pueden utilizarse con baterías JobSchedulery otras cosas. Algunas de las aplicaciones necesitan funcionar a un nivel más bajo que las aplicaciones de sincronización típicas. Esta es una solución alternativa cuando eso no funciona.
Mygod
-17

no use en onStartCommand:

return START_NOT_STICKY

solo cámbielo a:

return START_STICKY

y va a funcionar

Omar Othman
fuente