web-dev-qa-db-fra.com

Le service ne démarre pas sur Oreo dans le widget d'application à l'aide de PendingIntent

J'utilise Android. Je crée un objet PendingIntent et l'utilise dans la méthode RemoteViews#setOnClickPendingIntent(). C'est l'intention en attente:

// 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);

Le code ci-dessus fonctionne comme prévu avant Android Oreo. Cependant, dans Android Oreo, il ne démarre plus le service si l'application est balayée des recents. (No PendingIntents ne sont-ils pas exclus des limites d'exécution en arrière-plan d'Oreo?

À des fins de test, j'ai remplacé getService par getForegroundService mais le Service n'est toujours pas démarré. Les deux méthodes affichent le message ci-dessous dans le journal:

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

Pourquoi le Service n'est-il pas démarré, même lorsque vous utilisez getForegroundService? J'ai testé cela sur un Nexus 6P exécutant Android 8.1.0.

16
Thomas Vos

Comme vous le constatez, un widget d'application ne compte pas pour la liste blanche d'arrière-plan PendingIntent. Je ne sais pas pourquoi - il semblerait être à peu près au même niveau qu'un PendingIntent démarré par un Notification. C'est peut-être un problème que le Notification est une chose système, tandis que le widget de l'application est une chose de l'écran d'accueil. Quoi qu'il en soit, vous pourriez:

  • Utilisez getForegroundService(), comme vous l'avez fait, ou

  • Essayez getBroadcast() avec un Intent explicite, et avec le BroadcastReceiver commençant un JobIntentService, si vous ne voulez pas soulever un Notification (comme un service de premier plan l'exige)

Sur la base de vos symptômes, vous semblez avoir trébuché sur ce que je considérerais comme un bug: il devrait y avoir un moyen de passer de getService() à getForegroundService() sans avoir à redémarrer. :-) J'essaierai d'exécuter quelques expériences et déposerai un problème si je peux reproduire le problème.

7
CommonsWare

Vous ne pouvez plus démarrer un service en arrière-plan dans 8.0, mais vous pouvez utiliser JobScheduler pour obtenir des résultats similaires. Il existe également une classe d'assistance JobIntentService qui vous permet de basculer vers JobScheduler à partir du service sans trop de refactorisation. Et vous ne pouvez pas utiliser PendingIntent pointant vers un service, mais vous pouvez en utiliser un pointant vers Activity ou BroadcastReceiver.

Si vous aviez un widget fonctionnel avant 8.0, et maintenant vous devez le faire fonctionner sur Android 8.0, effectuez simplement ces étapes simples:

  1. Changez votre classe IntentService en JobIntentService
  2. Renommer la méthode du service onHandleIntent en onHandleWork (mêmes paramètres)
  3. Ajouter BIND_JOB_SERVICE autorisation de votre service dans le manifeste:
    <service Android:name=".widget.MyWidgetService"
           Android:permission="Android.permission.BIND_JOB_SERVICE">
    </service>
  1. Pour démarrer ce service, vous ne devez plus utiliser context.startService. Utilisez plutôt la méthode statique enqueueWork (où JOB_ID est juste une constante entière unique, doit être la même valeur pour tous les travaux mis en file d'attente pour la même classe):
    enqueueWork(context, MyWidgetService.class, JOB_ID, intent);
  1. Pour les clics, remplacez votre instance en attente qui pointait vers le service par une instance en attente qui pointe vers un BroadcastReceiver. Puisque votre sous-classe d'AppWidgetProvider est un BroadcastReceiver lui-même, vous pourriez aussi bien l'utiliser:
    Intent myIntent = new Intent(context, MyAppWidgetProvider.class);
    myIntent .setAction("SOME_UNIQUE_ACTION");
    pendingIntent = PendingIntent.getBroadcast(context, 0, myIntent, PendingIntent.FLAG_UPDATE_CURRENT);
  1. Dans onReceive, démarrez le service en utilisant enqueueWork (Si votre PendingIntent démarrait le activity, laissez-le tranquille - cela fonctionnera très bien sur 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. Pour vous assurer que widget fonctionnera sur les anciens appareils, assurez-vous que vous disposez de l'autorisation WAKE_LOCK dans votre manifeste (utilisée par JobIntentService sur les anciens appareils).

C'est ça. Ce widget fonctionnera désormais correctement sur les nouveaux et les anciens appareils. La seule vraie différence sera que si votre appareil 8.0 est en mode somnolence, il ne peut pas mettre à jour le widget très souvent, mais cela ne devrait pas être un problème car s'il somnole, cela signifie que l'utilisateur ne peut pas voir votre widget pour le moment de toute façon.

14
Alexey

Est-ce que goasyc pourrait être une option? Vous pouvez modifier votre PendingIntent pour déclencher un BroadcastReceiver, puis dans la méthode OnRecieve, vous pouvez appeler goasync () Ensuite, vous devriez pouvoir utiliser le PendingResult qu'il génère pour créer un appel asynchrone. Ne pensez toujours pas que vous pouvez démarrer un service.

Comment utiliser "goAsync" pour broadcastReceiver?

2
Yeshia