web-dev-qa-db-fra.com

Comment puis-je exécuter du code en arrière-plan, même avec l'écran éteint?

J'ai une application simple minuterie dans Flutter, qui affiche un compte à rebours avec le nombre de secondes restantes. J'ai:

new Timer.periodic(new Duration(seconds: 1), _decrementCounter);

Cela semble fonctionner correctement jusqu'à ce que l'écran de mon téléphone s'éteigne (même si je change d'application) et s'endorme. Ensuite, le chronomètre se met en pause. Existe-t-il un moyen recommandé de créer un service qui s'exécute en arrière-plan même lorsque l'écran est éteint?

24
Lokman

Répondre à la question de savoir comment mettre en œuvre votre cas spécifique de minuterie n'a pas vraiment à voir avec le code d'arrière-plan. Le code d'exécution global en arrière-plan est quelque chose de découragé sur les systèmes d'exploitation mobiles.

Par exemple, la documentation iOS aborde le code d’arrière-plan plus en détail ici: https://developer.Apple.com/library/content/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/BackgroundExecution/BackgroundExecution.html

À la place, les systèmes d'exploitation mobiles fournissent un apis (comme un apis de minuterie/alarme/notification) à rappeler à votre application après une heure spécifique. Par exemple, sur iOS, vous pouvez demander à ce que votre application soit notifiée/réactivée à un moment ultérieur via UINotificationRequest: https://developer.Apple.com/reference/usernotifications/unnotificationrequest leur permet de supprimer/suspendre votre application afin de réaliser de meilleures économies d'énergie et de disposer d'un seul et même service système hautement efficace pour le suivi de ces notifications/alarmes/geofencing, etc.

Flutter ne fournit actuellement aucun emballage sur ces services de système d'exploitation prêt à l'emploi, mais il est facile d'écrire vos propres en utilisant notre modèle de services de plate-forme: Flutter.io/platform-services

Nous travaillons sur un système de publication/partage d'intégrations de services comme celui-ci afin qu'une fois qu'une personne ait écrit cette intégration (par exemple en planifiant une exécution future de votre application), tout le monde puisse en bénéficier.

Séparément, la question plus générale de "est-il possible d'exécuter du code Dart en arrière-plan" (sans avoir un FlutterView actif à l'écran), est "pas encore". Nous avons un bug dans le fichier: https://github.com/flutter/flutter/issues/3671

Le cas d'utilisation de ce type d'exécution de code d'arrière-plan est lorsque votre application reçoit une notification, veut la traiter à l'aide d'un code Dart sans amener votre application au premier plan. Si vous avez d'autres cas d'utilisation de code d'arrière-plan que vous souhaiteriez connaître, les commentaires sur ce bogue sont les bienvenus!

20
Eric Seidel

Réponse courte: non, ce n'est pas possible, bien que j'aie observé un comportement différent pour l'affichage en veille. Le code suivant vous aidera à comprendre les différents états d'une application Flutter sur Android, testée avec les versions Flutter et Flutter Engine suivantes: 

  • Révision du cadre b339c71523 (il y a 6 heures), 2017-02-04 00:51:32
  • Révision du moteur cd34b0ef39

Créez une nouvelle application Flutter et remplacez le contenu de lib/main.Dart par ce code:

import 'Dart:async';

import 'package:flutter/material.Dart';

void main() {
  runApp(new MyApp());
}

class LifecycleWatcher extends StatefulWidget {
  @override
  _LifecycleWatcherState createState() => new _LifecycleWatcherState();
}

class _LifecycleWatcherState extends State<LifecycleWatcher>
    with WidgetsBindingObserver {
  AppLifecycleState _lastLifecyleState;

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
  }

  @override
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    super.dispose();
  }

  @override
  void onDeactivate() {
    super.deactivate();
  }

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    print("LifecycleWatcherState#didChangeAppLifecycleState state=${state.toString()}");
    setState(() {
      _lastLifecyleState = state;
    });
  }

  @override
  Widget build(BuildContext context) {
    if (_lastLifecyleState == null)
      return new Text('This widget has not observed any lifecycle changes.');
    return new Text(
        'The most recent lifecycle state this widget observed was: $_lastLifecyleState.');
  }
}

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Flutter Demo',
      theme: new ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: new MyHomePage(title: 'Flutter App Lifecycle'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => new _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _timerCounter = 0;
  // ignore: unused_field only created once
  Timer _timer;

  _MyHomePageState() {
    print("_MyHomePageState#constructor, creating new Timer.periodic");
    _timer = new Timer.periodic(
        new Duration(milliseconds: 3000), _incrementTimerCounter);
  }

  void _incrementTimerCounter(Timer t) {
    print("_timerCounter is $_timerCounter");
    setState(() {
      _timerCounter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text(config.title),
      ),
      body: new Block(
        children: [
          new Text(
            'Timer called $_timerCounter time${ _timerCounter == 1 ? '' : 's' }.',
          ),
          new LifecycleWatcher(),
        ],
      ),
    );
  }
}

Lors du lancement de l'application, la valeur de _timerCounter est incrémentée toutes les 3 secondes. Un champ de texte situé sous le compteur indiquera toutes les modifications AppLifecycleState pour l'application Flutter, vous verrez la sortie correspondante dans le journal de débogage de Flutter, par exemple:

[raju@eagle:~/flutter/helloworld]$ flutter run
Launching lib/main.Dart on SM N920S in debug mode...
Building APK in debug mode (Android-arm)...         6440ms
Installing build/app.apk...                         6496ms
I/flutter (28196): _MyHomePageState#constructor, creating new Timer.periodic
Syncing files to device...
I/flutter (28196): _timerCounter is 0

????  To hot reload your app on the fly, press "r" or F5. To restart the app entirely, press "R".
The Observatory debugger and profiler is available at: http://127.0.0.1:8108/
For a more detailed help message, press "h" or F1. To quit, press "q", F10, or Ctrl-C.
I/flutter (28196): _timerCounter is 1
I/flutter (28196): LifecycleWatcherState#didChangeAppLifecycleState state=AppLifecycleState.paused
I/flutter (28196): _timerCounter is 2
I/flutter (28196): _timerCounter is 3
I/flutter (28196): LifecycleWatcherState#didChangeAppLifecycleState state=AppLifecycleState.resumed
I/flutter (28196): _timerCounter is 4
I/flutter (28196): LifecycleWatcherState#didChangeAppLifecycleState state=AppLifecycleState.paused
I/flutter (28196): _timerCounter is 5
I/flutter (28196): _timerCounter is 6
I/flutter (28196): _timerCounter is 7
I/flutter (28196): LifecycleWatcherState#didChangeAppLifecycleState state=AppLifecycleState.resumed
I/flutter (28196): LifecycleWatcherState#didChangeAppLifecycleState state=AppLifecycleState.paused
I/flutter (28196): _timerCounter is 8
I/flutter (28196): _MyHomePageState#constructor, creating new Timer.periodic
I/flutter (28196): _timerCounter is 0
I/flutter (28196): _timerCounter is 1

Pour la sortie de journal ci-dessus, voici les étapes que j'ai effectuées:

  1. Lancer l'application avec flutter run
  2. Basculer vers une autre application (valeur _timerCounter 1)
  3. Retourner à l'application Flutter (valeur _timerCounter 3)
  4. Bouton d'alimentation enfoncé, affichage éteint (valeur _timerCounter 4)
  5. Téléphone déverrouillé, l'application Flutter a repris (valeur _timerCounter 7)
  6. Bouton de retour appuyé sur le téléphone (la valeur de _timerCounter n’a pas changé). C'est le moment où le FlutterActivity est détruit et le Dart VM également isoler.
  7. L'application Flutter a repris (la valeur de _timerCounter est à nouveau égale à 0)

Basculer entre les applications, appuyer sur le bouton d'alimentation ou de retour
Lorsque vous passez à une autre application ou que vous appuyez sur le bouton d'alimentation pour éteindre l'écran, la minuterie continue de fonctionner. Mais lorsque vous appuyez sur le bouton Précédent pendant que l'application Flutter est active, l'activité est détruite et le fléchette est isolée. Vous pouvez tester cela en vous connectant à Dart Observatory lors du passage d'une application à une autre ou en tournant l'écran. L'Observatoire affichera une application active Flutter en cours d'exécution. Mais lorsque vous appuyez sur le bouton Précédent, l’Observatoire ne montre aucun isolement en cours d’exécution. Le comportement a été confirmé sur un Galaxy Note 5 sous Android 6.x et un Nexus 4 sous Android 4.4.x.

Cycle de vie des applications Flutter et du cycle de vie Android Pour le calque de widget Flutter, seuls les états en pause et repris sont exposés. Destroy est géré par Android Activity pour une application Android Flutter:

/**
 * @see Android.app.Activity#onDestroy()
 */
@Override
protected void onDestroy() {
    if (flutterView != null) {
        flutterView.destroy();
    }
    super.onDestroy();
}

Étant donné que la fléchette VM d'une application Flutter est en cours d'exécution dans l'activité, le VM sera arrêté chaque fois que l'activité sera détruite.

Flutter Code logique moteur
Cela ne répond pas directement à votre question, mais vous donnera des informations générales plus détaillées sur la manière dont le moteur Flutter gère les changements d'état pour Android.
En regardant à travers le code du moteur Flutter, il devient évident que la boucle d'animation est suspendue lorsque l'événement FlutterActivity reçoit l'événement Android Activité # onPause . Lorsque l'application passe à l'état en pause, selon le commentaire source ici , il se passe ce qui suit:

"L'application n'est actuellement pas visible pour l'utilisateur. Lorsque l'application est dans cet état, le moteur n'appelle pas le rappel [onBeginFrame]."

D'après mes tests, le minuteur continue de fonctionner même lorsque le rendu de l'interface utilisateur est en pause, ce qui est logique. Il serait bon d'envoyer un événement dans la couche de widget à l'aide de WidgetsBindingObserver lorsque l'activité est détruite, afin que les développeurs puissent s'assurer de stocker l'état de l'application Flutter jusqu'à la reprise de l'activité.

16
raju-bitter

Vous pouvez utiliser le plug-in Android_alarm_manager flutter qui vous permet d’exécuter du code Dart en arrière-plan lorsqu’une alarme se déclenche.

Une autre façon, avec davantage de contrôle, serait d'écrire un service natif Android (avec Java ou Kotlin) pour votre application qui communique avec l'interface frontale flottante via le stockage de l'appareil ou des préférences partagées.

0
dodgy_coder