Android 8.0: java.lang.IllegalStateException: não é permitido iniciar o serviço Intent

360

No lançamento do aplicativo, o aplicativo inicia o serviço que deve executar algumas tarefas de rede. Depois de segmentar o nível 26 da API, meu aplicativo falha ao iniciar o serviço no Android 8.0 em segundo plano.

Causado por: java.lang.IllegalStateException: Não é permitido iniciar o serviço Intenção {cmp = my.app.tt / com.my.service}: o aplicativo está em segundo plano uid UidRecord {90372b1 u0a136 CEM ids procs: 1 seq (0,0 , 0)}

como eu o entendo relacionado a: Limites de execução em segundo plano

O método startService () agora lança uma IllegalStateException se um aplicativo direcionado ao Android 8.0 tentar usar esse método em uma situação em que não é permitido criar serviços em segundo plano.

" em uma situação em que não é permitido " - o que realmente significa? E como consertar isso. Não quero definir meu serviço como "primeiro plano"

phnmnn
fonte
4
Isso significa que você não pode iniciar um serviço quando seu aplicativo está em segundo plano
Tim
22
isso não tem nada a ver com permissões de tempo de execução
Tim
10
Use em startForegroundService()vez de startService().
Frogatto 11/10
2
Você pode tentar usar o targetSdkVersion 25, mas compilar com o compileSdkVersion 26. Dessa maneira, você pode usar novas classes do Android 8 e a mais nova biblioteca de suporte, mas seu aplicativo não será limitado pelos Limites de Execução em Segundo Plano.
Kacper Dziubek
2
@KacperDziubek Isso deve funcionar, mas é uma solução temporária, uma vez que será necessário para SDK26 alvo no outono de 2018.
RightHandedMonkey

Respostas:

194

As situações permitidas são uma lista branca temporária em que o serviço em segundo plano se comporta da mesma maneira que antes do Android O.

Sob certas circunstâncias, um aplicativo em segundo plano é colocado em uma lista branca temporária por vários minutos. Enquanto um aplicativo estiver na lista de desbloqueio, ele poderá iniciar serviços sem limitação, e seus serviços em segundo plano poderão ser executados. Um aplicativo é colocado na lista de permissões quando lida com uma tarefa que é visível para o usuário, como:

  • Manipulando uma mensagem de alta prioridade do Firebase Cloud Messaging (FCM).
  • Recebendo uma transmissão, como uma mensagem SMS / MMS.
  • Executando um PendingIntent a partir de uma notificação.
  • Iniciar um VpnService antes do aplicativo VPN se promover em primeiro plano.

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

Portanto, em outras palavras, se o serviço em segundo plano não atender aos requisitos da lista de desbloqueio, você precisará usar o novo JobScheduler . É basicamente o mesmo que um serviço em segundo plano, mas é chamado periodicamente em vez de ser executado em segundo plano continuamente.

Se você estiver usando um IntentService, poderá mudar para um JobIntentService. Veja a resposta de @ kosev abaixo .

Murat Karagöz
fonte
Estou tendo um erro fatal após iniciar o serviço logo após receber a mensagem do GCM de prêmio "alto". Ainda uso o GCM: "com.google.android.gms: play-services-gcm: 11.4.2", não 'com.google.firebase: firebase-messaging: 11.4.2'. Não tenho certeza se isso importa .. #
4555 Alex Radzishevsky 30/10
"É basicamente o mesmo que um serviço em segundo plano, mas é chamado periodicamente em vez de ser executado em segundo plano continuamente". - não sei o que você quer dizer com isso, pois os serviços Android nunca são executados continuamente. Eles começam, correm e depois desligam.
Melllvar #
2
O método FirebaseInstanceIdService e é onTokenRefreshuma mensagem FCM de alta prioridade?
Cord Rehn
@phnmnn não, o GCMTaskService realmente não segue o FCM, portanto, eles não funcionam.
Abhinav Upadhyay
4
Você não deve usar o WorkManager (aqui: developer.android.com/topic/libraries/architecture/workmanager ) em vez do JobScheduler ou outros? Quero dizer o seguinte: youtu.be/IrKoBFLwTN0
desenvolvedor android
256

Eu tenho solução. Para dispositivos anteriores à 8.0, você precisa apenas usar startService(), mas para dispositivos posteriores à 7.0, você deve usar startForgroundService(). Aqui está um exemplo de código para iniciar o serviço.

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

E na classe de serviço, adicione o código abaixo para notificação:

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

Onde O é a versão 26 do Android.

Sagar Kacha
fonte
9
Um serviço em primeiro plano é algo que o usuário estará ciente e que precisa de uma notificação. Também ANR se demorar muito. Portanto, não é realmente uma resposta adequada se o aplicativo já estiver sendo executado em segundo plano.
SimonH
80
Existe uma ContextCompat.startForegroundService(...)biblioteca de suporte que pode ser usada em seu lugar.
jayeffkay
37
Isso não é uma solução.
precisa saber é o seguinte
17
Também concordo que isso não é uma solução. É uma solução alternativa e ajuda, mas os limites de segundo plano no Oreo foram introduzidos por um motivo. Ignorar esses limites dessa maneira definitivamente não é a abordagem correta (mesmo que funcione). A melhor maneira é usar o JobScheduler (consulte a resposta aceita).
Vratislav Jindra
6
Não acho que seja uma boa experiência para o usuário se você precisar mostrar uma notificação em primeiro plano vazia. Considerando o fato de que você deve. - O Android 8.0 introduz o novo método startForegroundService () para iniciar um novo serviço em primeiro plano. Depois que o sistema criou o serviço, o aplicativo tem cinco segundos para chamar o método startForeground () do serviço para mostrar a notificação visível ao usuário do novo serviço. Se o aplicativo não chamar startForeground () dentro do prazo, o sistema interromperá o serviço e declarará que o aplicativo é ANR.
Heeleeaz 01/05/19
85

A melhor maneira é usar JobIntentService, que usa o novo JobScheduler for Oreo ou os serviços antigos, se não disponíveis.

Declare no seu manifesto:

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

E em seu serviço, você deve substituir 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
    }

}

Então você inicia seu serviço com:

YourService.enqueueWork(context, new Intent());
Kosev
fonte
Como você é capaz de chamar um método não estático dentro de um método estático? Você pode explicar, por favor?
Maddy
@ Maddy também enqueueWork(...)é um método estático.
hgoebl
2
Onde você chamaria YourService.enqueueWork (context, new Intent ()); ? Do receptor de transmissão?
TheLearner
Não acredito que esta seja a solução mais fácil. Veja meu comentário abaixo sobre o WorkManager. Ele usa o JobIntentService quando apropriado, mas possui muito menos placa de aquecimento.
TAL
36

Se o serviço estiver sendo executado em um encadeamento em segundo plano, estendendo IntentService, você poderá substituir IntentServiceporJobIntentService que é fornecido como parte da Biblioteca de suporte Android

A vantagem de usar JobIntentServiceé que ele se comporta como umIntentService em dispositivos pré-O e em O e superior, ele o envia como um trabalho

JobSchedulertambém pode ser usado para trabalhos periódicos / sob demanda. Mas assegure-se de lidar com a compatibilidade com versões anteriores, pois a JobSchedulerAPI está disponível apenas na API 21

Harini S
fonte
11
O problema com o JobIntentService é que o Android pode agendar seu trabalho de maneira arbitrária e não pode ser iniciado implicitamente sem alguns ajustes, ao contrário de um IntentService. Veja stackoverflow.com/questions/52479262/…
kilokahn
15

No Oreo, o Android definiu limites para os serviços em segundo plano .

Para melhorar a experiência do usuário, o Android 8.0 (nível 26 da API) impõe limitações sobre o que os aplicativos podem fazer durante a execução em segundo plano.

Ainda assim, se você precisar sempre executar o serviço, poderá usar o serviço em primeiro plano.

Limitações do serviço em segundo plano: enquanto um aplicativo está ocioso, há limites para o uso de serviços em segundo plano. Isso não se aplica aos serviços em primeiro plano, que são mais perceptíveis para o usuário.

Então você pode fazer um serviço em primeiro plano . Você precisará mostrar uma notificação ao usuário quando seu serviço estiver sendo executado. Veja esta resposta (existem muitas outras)

Uma solução se -

você não quer uma notificação para o seu serviço?

Você pode executar tarefas periódicas, 1. ele inicia seu serviço, 2. o serviço faz seu trabalho e 3. se interrompe. Por isso, seu aplicativo não será considerado o esgotamento da bateria.

Você pode usar tarefas periódicas com o Alarm Manager , o Job Scheduler , o Evernote-Jobs ou o Work Manager .

Eu testei o serviço para sempre em execução com o Work-Manager.

Khemraj
fonte
O WorkManager parece ser o melhor caminho a percorrer, supondo que o trabalho não precise ser executado imediatamente. É compatível com a API 14, usando o JobScheduler em dispositivos com API 23+ e uma combinação de BroadcastReceiver + AlarmManager em dispositivos com API 14-22
James Allen
o principal do WorkManager é WorkManager destina-se a tarefas que são deferrable, isto é, não são obrigados a executar imediatamente
touhid udoy
13

Sim, é porque você não pode mais iniciar serviços em segundo plano na API 26. Portanto, você pode iniciar o ForegroundService acima da API 26.

Você terá que usar

ContextCompat.startForegroundService(...)

e publique uma notificação ao processar o vazamento.

PK
fonte
11
O OP disse especificamente que ele não quer como primeiro plano. Isso deve ser colocado como um comentário ou como parte de uma resposta mais completa.
Ricardo A.
7

Como @kosev disse em sua resposta, você pode usar o JobIntentService. Mas eu uso uma solução alternativa - eu pego o IllegalStateException e inicio o serviço como primeiro plano. Por exemplo, esta função inicia meu serviço:

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

e quando processo Intent, faço o seguinte:

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
fonte
Eu gosto da sua solução try catch. Para mim é uma solução, porque às vezes context.startServicetrabalha em fundo - às vezes não - esta parece ser a única melhor maneira de outra forma você tem que implementar mais código em sua classe principal extending Applicatione implementing ActivityLifecycleCallbackse acompanhar se o aplicativo está em primeiro ou segundo plano e começar a sua intenção adequadamente.
315 Pierre
Esta exceção pode ser detectada?
thecr0w 10/01
5

Nas notas de versão do firebase , eles afirmam que o suporte ao Android O foi lançado pela primeira vez na versão 10.2.1 (embora eu recomende o uso da versão mais recente).

adicione novas dependências de mensagens do firebase para o Android O

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

atualize os serviços do Google Play e os repositórios do Google, se necessário.

Dhaval Jivani
fonte
Isso não responde à pergunta, nem tem nada a ver com firebase. Deve ser colocado como um comentário.
Ricardo A.
5

Se alguma intenção estava funcionando bem quando o aplicativo está em segundo plano, não será mais o caso do Android 8 e posterior. Referindo-se apenas à intenção que precisa fazer algum processamento quando o aplicativo estiver em segundo plano.

Os passos abaixo devem ser seguidos:

  1. A intenção acima mencionada deve estar usando em JobIntentServicevez de IntentService.
  2. A classe que se estende JobIntentServicedeve implementar o onHandleWork(@NonNull Intent intent)método - e deve ter abaixo do método, o que invocará o onHandleWorkmétodo:

    public static void enqueueWork(Context context, Intent work) {
        enqueueWork(context, xyz.class, 123, work);
    }
  3. Ligue enqueueWork(Context, intent)da classe onde sua intenção está definida.

    Código de amostra:

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

A classe abaixo anteriormente estava estendendo a classe Serviço

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-compaté necessário para JobIntentService- eu uso 26.1.0 V.

  2. O mais importante é garantir que a versão das bibliotecas do Firebase esteja no mínimo 10.2.1, tive problemas com 10.2.0- se você tiver algum!

  3. Seu manifesto deve ter a permissão abaixo para a classe Service:

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

Espero que isto ajude.

chitraju chaithanya
fonte
4

Vejo muitas respostas que recomendam apenas o uso de um ForegroundService. Para usar um ForegroundService, deve haver uma notificação associada a ele. Os usuários verão esta notificação. Dependendo da situação, eles podem ficar irritados com o aplicativo e desinstalá-lo.

A solução mais fácil é usar o novo componente de arquitetura chamado WorkManager. Você pode conferir a documentação aqui: https://developer.android.com/topic/libraries/architecture/workmanager/

Você acabou de definir sua classe de trabalhador que estende o Trabalhador.

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

Em seguida, você agende quando deseja executá-lo.

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

Fácil! Existem várias maneiras de configurar trabalhadores. Ele suporta trabalhos recorrentes e você pode até fazer coisas complexas, como encadeamento, se precisar. Espero que isto ajude.

CONTO
fonte
3
Atualmente, o WorkManager ainda é alfa.
pzulw
3
5 de março de 2019 - Versão estável do WorkManager 1.0.0.
Phnmnn 07/03/19
deve usar WorkManager em vez de usar inter ou JobIntentService
sivaBE35
11
WorkManager is intended for tasks that are deferrable—that is, not required to run immediately... pode ser mais fácil, no entanto, meu aplicativo precisa de um serviço em segundo plano que execute as solicitações dos usuários imediatamente!
Alguém em algum lugar
Se você precisar que a tarefa seja concluída imediatamente, use um serviço em primeiro plano. O usuário verá uma notificação e saberá que você está fazendo um trabalho. Confira os documentos se precisar de ajuda para decidir o que usar. Eles têm um bom guia para o processamento em segundo plano. developer.android.com/guide/background
TALE
4

Solução alternativa usando o JobScheduler, ele pode iniciar o serviço em segundo plano em um intervalo regular de tempo.

Em primeiro lugar, faça a classe denominada 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());
   }
  }

Em seguida, torne a classe JobService denominada 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;
  }
 }

Depois dessa classe BroadCast Receiver chamada 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);
 }
}

Atualizar arquivo de manifesto com código de classe de serviço e 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>

O ativador main_intent esquerdo para o arquivo mainActivity.java, criado por padrão, e as alterações no arquivo MainActivity.java são

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 !! O serviço em segundo plano é iniciado sem o serviço de primeiro plano

Anshul1507
fonte
2

Se você estiver executando seu código na 8.0, o aplicativo falhará. Portanto, inicie o serviço em primeiro plano. Se abaixo de 8.0, use isto:

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

Se acima ou 8.0, use o seguinte:

Intent serviceIntent = new Intent(context, RingtonePlayingService.class);
ContextCompat.startForegroundService(context, serviceIntent );
iamfaizuddin
fonte
Recomenda-se usar apenas os serviços de primeiro plano nos casos em que o usuário precisa estar ciente de que um serviço está sendo executado. O exemplo típico é para tocar música em segundo plano. Existem outros casos que fazem sentido, mas você não deve apenas converter todos os seus serviços em serviços de primeiro plano. Considere converter seus serviços para usar o WorkManager a partir dos componentes de arquitetura do Google quando você precisar fazer algum trabalho em segundo plano e ter a garantia de que ele será executado.
TAL
startForegroundService requer permissão, caso contrário java.lang.SecurityException: Permission Denial: startForeground from pid=13708, uid=10087 requires android.permission.FOREGROUND_SERVICE. Corrigir stackoverflow.com/a/52382711/550471
Alguém em algum lugar
1

se você tiver integrado a notificação por push do sistema de mensagens do firebase,

Adicione novas / atualize dependências de mensagens do firebase para o Android O (Android 8.0), devido a limites de execução em segundo plano .

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

atualize os serviços do Google Play e os repositórios do Google, se necessário.

Atualizar:

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

Use em startForegroundService()vez de startService() e não esqueça de criar startForeground(1,new Notification());no seu serviço dentro de 5 segundos após iniciar o serviço.

Arpit
fonte
2
Parece que o novo Notificaction () não funciona no Android 8.1; Você deve criar o canal de notificação: stackoverflow.com/a/47533338/1048087
Prizoff
0

Devido a votos controversos nesta resposta (+ 4 / -4 a partir desta edição), VEJA AS OUTRAS RESPOSTAS PRIMEIRAMENTE E USE ISSO SOMENTE COMO ÚLTIMO RECURSO . Eu usei isso apenas uma vez para um aplicativo de rede que roda como root e concordo com a opinião geral de que esta solução não deve ser usada em circunstâncias normais.

Resposta original abaixo:

As outras respostas estão todas corretas, mas gostaria de salientar que outra maneira de contornar isso é pedir ao usuário que desative as otimizações de bateria do seu aplicativo (isso geralmente não é uma boa ideia, a menos que o aplicativo esteja relacionado ao sistema). Veja esta resposta para saber como solicitar a desativação das otimizações da bateria sem que seu aplicativo seja banido no Google Play.

Você também deve verificar se as otimizações da bateria estão desativadas no seu receptor para evitar falhas atravé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
Meu Deus
fonte
11
Pedir aos usuários que lhe permitam um passe livre usando o máximo de bateria possível não é uma boa solução. Considere converter seu código em uma solução mais amigável para bateria. Seus usuários agradecerão.
CONTEÚDO 14/11
5
@TALE Nem todos os serviços em segundo plano podem ser compatíveis com a bateria JobSchedulere outras coisas. Alguns aplicativos precisam funcionar em um nível mais baixo do que os aplicativos de sincronização típicos. Esta é uma solução alternativa quando isso não funciona.
Mygod 15/11/19
-17

não use no onStartCommand:

return START_NOT_STICKY

basta alterá-lo para:

return START_STICKY

e vai funcionar

Omar Othman
fonte