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
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
For the above log output, here are the steps I did:
- Launch app with
flutter run
- Switch to other app (_timerCounter value 1)
- Return to Flutter app (_timerCounter value 3)
- Pressed power button, display turned off (_timerCounter value 4)
- Unlocked phone, Flutter app resumed (_timerCounter value 7)
- Pressed back button on phone (_timerCounter value not changed). This is the moment where the FlutterActivity gets destroyed and the Dart VM Isolate as well.
- Flutter app resumed (_timerCounter value is 0 again)
Switching between apps, pressing power or back button
When switching to another app, or when pressing the power button to turn of the screen the timer continues to run. But when pressing the back button while the Flutter app has the focus, the Activity gets destroyed, and with it the Dart isolate. You can test that by connecting to the Dart Observatory when switching between apps, or turning of the screen. The Observatory will show an active Flutter app Isolate running. But when pressing the back button, the Observatory shows no running Isolate. The behavior was confirmed on a Galaxy Note 5 running Android 6.x, and a Nexus 4 running Android 4.4.x.
Flutter app lifecycle and Android lifecycle
For the Flutter widget layer, only the paused and resumed states are exposed. Destroy is handled by Android Activity for an Android Flutter app:
/**
* @see android.app.Activity#onDestroy()
*/
@Override
protected void onDestroy() {
if (flutterView != null) {
flutterView.destroy();
}
super.onDestroy();
}
Since the Dart VM for a Flutter app is running inside the Activity, the VM will be stopped every time the Activity gets destroyed.
Flutter Engine code logic
This doesn't directly answer your question, but will give you some more detailed background info on how the Flutter engine handles state changes for Android.
Looking through the Flutter engine code it becomes obvious that the animation loop is paused when the FlutterActivity receives the Android Activity#onPause event. When the application goes into paused state, according to the source comment here the following happens:
"The application is not currently visible to the user. When the application is in this state, the engine will not call the [onBeginFrame] callback."
Based on my testing the timer continues to work even with the UI rendering being paused, which makes sense. It would be good to send an event into the widget layer using the WidgetsBindingObserver when the Activity gets destroyed, so developers can make sure to store the state of the Flutter app until the Activity is resumed.