it-swarm.dev

Serviço não iniciado no Oreo no widget de aplicativo usando PendingIntent

Estou usando Android. Estou criando um objeto PendingIntent e o uso no método RemoteViews#setOnClickPendingIntent(). Esta é a intenção pendente:

// The below code is called in the onUpdate(Context, AppWidgetManager, int[]) method of the AppWidgetProvider class.

// Explicit intent
Intent intent = new Intent(context, MyService.class);
intent.setAction(MY_ACTION);
intent.putExtra(EXTRA_KEY, VALUE);

// Create the pending intent
PendingIntent pendingIntent = PendingIntent.getService(context, appWidgetId, intent, 0);

// 'views' is a RemoteViews object provided by onUpdate in the AppWidgetProvider class.
views.setOnClickPendingIntent(R.id.root_layout, pendingIntent);

O código acima funciona conforme o esperado antes Android Oreo. No entanto, em Android Oreo, ele não inicia mais o serviço se o aplicativo for retirado de recentes). (Não (PendingIntent s não são excluídos nos limites de execução em segundo plano do Oreo?

Para fins de teste, substitui getService por getForegroundService, mas o Service ainda não foi iniciado. Ambos os métodos mostram a mensagem abaixo no log:

W/ActivityManager: Background start not allowed: service Intent { act=com.example.myapp.MY_ACTION flg=0x10000000 cmp=com.example.myapp/com.example.myapp.MyService bnds=[607,716][833,942] (has extras) } to com.example.myapp/com.example.myapp.MyService from pid=-1 uid=10105 pkg=com.example.myapp

Por que o Service não foi iniciado, mesmo quando se usa getForegroundService? Eu testei isso em um Nexus 6P executando Android 8.1.0.

16
Thomas Vos

Como você observa, um widget de aplicativo não conta para a lista de permissões PendingIntent em segundo plano. Eu não sei por que - parece estar em pé de igualdade com um PendingIntent iniciado por um Notification. Talvez seja um problema que o Notification seja uma coisa do sistema, enquanto o widget do aplicativo é uma coisa da tela inicial. Independentemente, você pode:

  • Use getForegroundService(), como você possui, ou

  • Tente getBroadcast() com um explícito Intent e com o BroadcastReceiver iniciando um JobIntentService, se você não desejar gerar um Notification (como um serviço em primeiro plano exige)

Com base nos seus sintomas, você parece ter tropeçado no que eu consideraria um bug: deve haver uma maneira de alternar de getService() para getForegroundService() sem ter que reiniciar. :-) Vou tentar executar algumas experiências e arquivar um problema se eu puder reproduzir o problema.

7
CommonsWare

Você não pode mais iniciar um serviço em segundo plano na 8.0, mas pode usar JobScheduler para obter resultados semelhantes. Há também uma classe auxiliar JobIntentService que permite alternar para JobScheduler do serviço sem muita refatoração. E você não pode usar PendingIntent apontando para um serviço, mas pode usar um apontando para Activity ou BroadcastReceiver.

Se você tinha um widget em funcionamento anterior à 8.0 e agora precisa fazê-lo funcionar em Android 8.0, basta executar estas etapas simples:

  1. Altere sua classe IntentService para JobIntentService
  2. Renomeie o serviço onHandleIntent método para onHandleWork (mesmos parâmetros)
  3. Adicionar BIND_JOB_SERVICE permissão para seu serviço no manifesto:
    <service Android:name=".widget.MyWidgetService"
           Android:permission="Android.permission.BIND_JOB_SERVICE">
    </service>
  1. Para iniciar este serviço, você não deve mais usar context.startService. Em vez disso, use o método estático enqueueWork (em que JOB_ID é apenas uma constante inteira exclusiva, deve ser o mesmo valor para todo o trabalho enfileirado na mesma classe):
    enqueueWork(context, MyWidgetService.class, JOB_ID, intent);
  1. Para cliques, substitua seu pendingIntent que estava apontando para o serviço por um pendingIntent que aponta para um BroadcastReceiver. Como sua subclasse de AppWidgetProvider é um BroadcastReceiver, você também pode usá-lo:
    Intent myIntent = new Intent(context, MyAppWidgetProvider.class);
    myIntent .setAction("SOME_UNIQUE_ACTION");
    pendingIntent = PendingIntent.getBroadcast(context, 0, myIntent, PendingIntent.FLAG_UPDATE_CURRENT);
  1. No onReceive, inicie o serviço usando enqueueWork (se seu PendingIntent estava iniciando o activity, deixe como está - ele funcionará perfeitamente em Android 8.0+ ):
    @Override
    public void onReceive(Context context, Intent intent) {
        super.onReceive(context, intent);
        if (intent.getAction().equals("SOME_UNIQUE_ACTION")) {
            MyWidgetService.enqueueWork(.....);
        }
    }
  1. Para garantir que o widget funcione em dispositivos antigos, verifique se você tem a permissão WAKE_LOCK em seu manifesto (usado por JobIntentService em dispositivos antigos).

É isso aí. Este widget agora funcionará corretamente em dispositivos novos e antigos. A única diferença real será que, se o seu dispositivo 8.0 estiver no modo cochilo, ele pode não atualizar o widget com tanta frequência, mas isso não deve ser um problema porque, se estiver cochilando, significa que o usuário não pode ver seu widget agora em todo o caso.

14
Alexey

Poderia o goasyc ser uma opção? Você pode alterar seu PendingIntent para disparar um BroadcastReceiver e, no método OnRecieve, pode chamar goasync (). Então, você poderá usar o PendingResult que ele gera para criar uma chamada assíncrona. Ainda não acho que você pode iniciar um serviço.

Como usar o "goAsync" para broadcastReceiver?

2
Yeshia