How do I run code in the background, even with the

2019-01-13 02:21发布

问题:

I have a simple timer app in Flutter, which shows a countdown with the number of seconds remaining. I have:

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

It seems to work fine until my phone's display switches off (even if I switch to another app) and goes to sleep. Then, the timer pauses. Is there a recommended way to create a service that runs in the background even when the screen is off?

回答1:

Answering the question of how to implement your specific timer case doesn't actually have to do with background code. Overall running code in the background is something discouraged on mobile operating systems.

For example, iOS Documentation discusses background code in greater detail here: https://developer.apple.com/library/content/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/BackgroundExecution/BackgroundExecution.html

Instead mobile operating systems provide apis (like a timer/alarm/notification apis) to call back to your application after a specific time. For example on iOS you can request that your application be notified/woken at a specific point in the future via UINotificationRequest: https://developer.apple.com/reference/usernotifications/unnotificationrequest This allows them to kill/suspend your app to achieve better power savings and instead have a single highly-efficent shared system service for tracking these notifications/alarms/geofencing, etc.

Flutter does not currently provide any wrappers around these OS services out-of-the-box, however it is straighforward to write your own using our platform-services model: flutter.io/platform-services

We're working on a system for publishing/sharing service integrations like this so that once one person writes this integration (for say scheduling some future execution of your app) everyone can benefit.

Separately, the more general question of "is it possible to run background Dart code" (without having a FlutterView active on screen), is "not yet". We have a bug on file: https://github.com/flutter/flutter/issues/3671

The use-case driving that kind of back-ground code execution is when your app receives a notification, wants to process it using some Dart code without bringing your app to the front. If you have other use cases for background code you'd like us to know about, comments are most welcome on that bug!



回答2:

Short answer: no, it's not possible, although I have observed a different behavior for the display going to sleep. The following code will help you understand the different states of a Flutter app on Android, tested with the these Flutter and Flutter Engine versions:

  • Framework revision b339c71523 (6 hours ago), 2017-02-04 00:51:32
  • Engine revision cd34b0ef39

Create a new Flutter app, and replace the content of lib/main.dart with this 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(),
        ],
      ),
    );
  }
}

When launching the app, the value of _timerCounter is incremented every 3s. A text field below the counter will show any AppLifecycleState changes for the Flutter app, you will see corresponding output in the Flutter debug log, e.g.:

[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