Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
F
Front-End
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
abdullh.alsoleman
Front-End
Commits
186d1e9b
Unverified
Commit
186d1e9b
authored
Dec 09, 2017
by
Ian Hickson
Committed by
GitHub
Dec 09, 2017
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Have the framework in charge of scheduling frames. (#13344)
...instead of the engine.
parent
425bd5a8
Changes
14
Show whitespace changes
Inline
Side-by-side
Showing
14 changed files
with
418 additions
and
100 deletions
+418
-100
ticker_expectation.txt
dev/automated_tests/flutter_test/ticker_expectation.txt
+16
-0
ticker_test.dart
dev/automated_tests/flutter_test/ticker_test.dart
+16
-0
binding.dart
packages/flutter/lib/src/rendering/binding.dart
+5
-22
binding.dart
packages/flutter/lib/src/scheduler/binding.dart
+236
-33
ticker.dart
packages/flutter/lib/src/scheduler/ticker.dart
+15
-3
message_codec.dart
packages/flutter/lib/src/services/message_codec.dart
+2
-0
binding.dart
packages/flutter/lib/src/widgets/binding.dart
+10
-26
service_extensions_test.dart
...ages/flutter/test/foundation/service_extensions_test.dart
+13
-0
animation_test.dart
packages/flutter/test/scheduler/animation_test.dart
+2
-1
scheduler_test.dart
packages/flutter/test/scheduler/scheduler_test.dart
+15
-14
ticker_test.dart
packages/flutter/test/scheduler/ticker_test.dart
+23
-1
binding_test.dart
packages/flutter/test/widgets/binding_test.dart
+52
-0
binding.dart
packages/flutter_test/lib/src/binding.dart
+8
-0
test_test.dart
packages/flutter_tools/test/commands/test_test.dart
+5
-0
No files found.
dev/automated_tests/flutter_test/ticker_expectation.txt
0 → 100644
View file @
186d1e9b
[^═]*(this line contains the test framework's output with the clock and so forth)?
══╡ EXCEPTION CAUGHT BY SCHEDULER LIBRARY ╞═════════════════════════════════════════════════════════
The following message was thrown:
An animation is still running even after the widget tree was disposed.
There was one transient callback left. The stack trace for when it was registered is as follows:
── callback 2 ──
<<skip until matching line>>
#[0-9]+ main.+ \(.+/flutter/dev/automated_tests/flutter_test/ticker_test\.dart:[0-9]+:[0-9]+\)
<<skip until matching line>>
════════════════════════════════════════════════════════════════════════════════════════════════════
.*..:.. \+0 -1: - Does flutter_test catch leaking tickers\? \[E\]
Test failed\. See exception logs above\.
The test description was: Does flutter_test catch leaking tickers\?
*
.*..:.. \+0 -1: Some tests failed\. *
dev/automated_tests/flutter_test/ticker_test.dart
0 → 100644
View file @
186d1e9b
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import
'package:flutter/scheduler.dart'
;
import
'package:flutter/services.dart'
;
import
'package:flutter_test/flutter_test.dart'
;
void
main
(
)
{
testWidgets
(
'Does flutter_test catch leaking tickers?'
,
(
WidgetTester
tester
)
async
{
new
Ticker
((
Duration
duration
)
{
})..
start
();
final
ByteData
message
=
const
StringCodec
().
encodeMessage
(
'AppLifecycleState.paused'
);
await
BinaryMessages
.
handlePlatformMessage
(
'flutter/lifecycle'
,
message
,
(
_
)
{});
});
}
packages/flutter/lib/src/rendering/binding.dart
View file @
186d1e9b
...
...
@@ -139,14 +139,17 @@ abstract class RendererBinding extends BindingBase with ServicesBinding, Schedul
/// Called when the system metrics change.
///
/// See [Window.onMetricsChanged].
@protected
void
handleMetricsChanged
()
{
assert
(
renderView
!=
null
);
renderView
.
configuration
=
createViewConfiguration
();
scheduleForcedFrame
();
}
/// Called when the platform text scale factor changes.
///
/// See [Window.onTextScaleFactorChanged].
@protected
void
handleTextScaleFactorChanged
()
{
}
/// Returns a [ViewConfiguration] configured for the [RenderView] based on the
...
...
@@ -266,26 +269,6 @@ abstract class RendererBinding extends BindingBase with ServicesBinding, Schedul
pipelineOwner
.
flushSemantics
();
// this also sends the semantics to the OS.
}
/// Schedule a frame to run as soon as possible, rather than waiting for
/// the engine to request a frame.
///
/// This is used during application startup so that the first frame (which is
/// likely to be quite expensive) gets a few extra milliseconds to run.
void
scheduleWarmUpFrame
()
{
// We use timers here to ensure that microtasks flush in between.
//
// We call resetEpoch after this frame so that, in the hot reload case, the
// very next frame pretends to have occurred immediately after this warm-up
// frame. The warm-up frame's timestamp will typically be far in the past
// (the time of the last real frame), so if we didn't reset the epoch we
// would see a sudden jump from the old time in the warm-up frame to the new
// time in the "real" frame. The biggest problem with this is that implicit
// animations end up being triggered at the old time and then skipping every
// frame and finishing in the new time.
Timer
.
run
(()
{
handleBeginFrame
(
null
);
});
Timer
.
run
(()
{
handleDrawFrame
();
resetEpoch
();
});
}
@override
Future
<
Null
>
performReassemble
()
async
{
await
super
.
performReassemble
();
...
...
packages/flutter/lib/src/scheduler/binding.dart
View file @
186d1e9b
...
...
@@ -6,15 +6,16 @@ import 'dart:async';
import
'dart:collection'
;
import
'dart:developer'
;
import
'dart:ui'
as
ui
show
window
;
import
'dart:ui'
show
VoidCallback
;
import
'dart:ui'
show
AppLifecycleState
,
VoidCallback
;
import
'package:collection/collection.dart'
show
PriorityQueue
,
HeapPriorityQueue
;
import
'package:flutter/foundation.dart'
;
import
'package:flutter/services.dart'
;
import
'debug.dart'
;
import
'priority.dart'
;
export
'dart:ui'
show
VoidCallback
;
export
'dart:ui'
show
AppLifecycleState
,
VoidCallback
;
/// Slows down animations by this factor to help in development.
double
get
timeDilation
=>
_timeDilation
;
...
...
@@ -51,9 +52,16 @@ typedef void FrameCallback(Duration timeStamp);
typedef
bool
SchedulingStrategy
(
{
int
priority
,
SchedulerBinding
scheduler
});
class
_TaskEntry
{
const
_TaskEntry
(
this
.
task
,
this
.
priority
);
_TaskEntry
(
this
.
task
,
this
.
priority
)
{
assert
(()
{
debugStack
=
StackTrace
.
current
;
return
true
;
}());
}
final
VoidCallback
task
;
final
int
priority
;
StackTrace
debugStack
;
}
class
_FrameCallbackEntry
{
...
...
@@ -85,7 +93,6 @@ class _FrameCallbackEntry {
final
FrameCallback
callback
;
// debug-mode fields
static
StackTrace
debugCurrentCallbackStack
;
StackTrace
debugStack
;
}
...
...
@@ -158,7 +165,7 @@ enum SchedulerPhase {
/// * Non-rendering tasks, to be run between frames. These are given a
/// priority and are executed in priority order according to a
/// [schedulingStrategy].
abstract
class
SchedulerBinding
extends
BindingBase
{
abstract
class
SchedulerBinding
extends
BindingBase
with
ServicesBinding
{
// This class is intended to be used as a mixin, and should not be
// extended directly.
factory
SchedulerBinding
.
_
()
=>
null
;
...
...
@@ -167,8 +174,9 @@ abstract class SchedulerBinding extends BindingBase {
void
initInstances
()
{
super
.
initInstances
();
_instance
=
this
;
ui
.
window
.
onBeginFrame
=
handleBeginFrame
;
ui
.
window
.
onDrawFrame
=
handleDrawFrame
;
ui
.
window
.
onBeginFrame
=
_handleBeginFrame
;
ui
.
window
.
onDrawFrame
=
_handleDrawFrame
;
SystemChannels
.
lifecycle
.
setMessageHandler
(
_handleLifecycleMessage
);
}
/// The current [SchedulerBinding], if one has been created.
...
...
@@ -187,6 +195,59 @@ abstract class SchedulerBinding extends BindingBase {
);
}
/// Whether the application is visible, and if so, whether it is currently
/// interactive.
///
/// This is set by [handleAppLifecycleStateChanged] when the
/// [SystemChannels.lifecycle] notification is dispatched.
///
/// The preferred way to watch for changes to this value is using
/// [WidgetsBindingObserver.didChangeAppLifecycleState].
AppLifecycleState
get
lifecycleState
=>
_lifecycleState
;
AppLifecycleState
_lifecycleState
;
/// Called when the application lifecycle state changes.
///
/// Notifies all the observers using
/// [WidgetsBindingObserver.didChangeAppLifecycleState].
///
/// This method exposes notifications from [SystemChannels.lifecycle].
@protected
@mustCallSuper
void
handleAppLifecycleStateChanged
(
AppLifecycleState
state
)
{
assert
(
state
!=
null
);
_lifecycleState
=
state
;
switch
(
state
)
{
case
AppLifecycleState
.
resumed
:
case
AppLifecycleState
.
inactive
:
_setFramesEnabledState
(
true
);
break
;
case
AppLifecycleState
.
paused
:
case
AppLifecycleState
.
suspending
:
_setFramesEnabledState
(
false
);
break
;
}
}
Future
<
String
>
_handleLifecycleMessage
(
String
message
)
{
handleAppLifecycleStateChanged
(
_parseAppLifecycleMessage
(
message
));
return
null
;
}
static
AppLifecycleState
_parseAppLifecycleMessage
(
String
message
)
{
switch
(
message
)
{
case
'AppLifecycleState.paused'
:
return
AppLifecycleState
.
paused
;
case
'AppLifecycleState.resumed'
:
return
AppLifecycleState
.
resumed
;
case
'AppLifecycleState.inactive'
:
return
AppLifecycleState
.
inactive
;
case
'AppLifecycleState.suspending'
:
return
AppLifecycleState
.
suspending
;
}
return
null
;
}
/// The strategy to use when deciding whether to run a task or not.
///
/// Defaults to [defaultSchedulingStrategy].
...
...
@@ -221,41 +282,65 @@ abstract class SchedulerBinding extends BindingBase {
// Whether this scheduler already requested to be called from the event loop.
bool
_hasRequestedAnEventLoopCallback
=
false
;
// Ensures that the scheduler
is awakened by the event loop
.
// Ensures that the scheduler
services a task scheduled by [scheduleTask]
.
void
_ensureEventLoopCallback
()
{
assert
(!
locked
);
assert
(
_taskQueue
.
isNotEmpty
);
if
(
_hasRequestedAnEventLoopCallback
)
return
;
Timer
.
run
(
handleEventLoopCallback
);
_hasRequestedAnEventLoopCallback
=
true
;
Timer
.
run
(
_runTasks
);
}
//
/ Called by the system when there is time to run tasks
.
void
handleEventLoopCallback
()
{
//
Scheduled by _ensureEventLoopCallback
.
void
_runTasks
()
{
_hasRequestedAnEventLoopCallback
=
false
;
_runTasks
();
if
(
handleEventLoopCallback
())
_ensureEventLoopCallback
();
// runs next task when there's time
}
// Called when the system wakes up and at the end of each frame.
void
_runTasks
()
{
/// Execute the highest-priority task, if it is of a high enough priority.
///
/// Returns true if a task was executed and there are other tasks remaining
/// (even if they are not high-enough priority).
///
/// Returns false if no task was executed, which can occur if there are no
/// tasks scheduled, if the scheduler is [locked], or if the highest-priority
/// task is of too low a priority given the current [schedulingStrategy].
///
/// Also returns false if there are no tasks remaining.
@visibleForTesting
bool
handleEventLoopCallback
()
{
if
(
_taskQueue
.
isEmpty
||
locked
)
return
;
return
false
;
final
_TaskEntry
entry
=
_taskQueue
.
first
;
// TODO(floitsch): for now we only expose the priority. It might
// be interesting to provide more info (like, how long the task
// ran the last time, or how long is left in this frame).
if
(
schedulingStrategy
(
priority:
entry
.
priority
,
scheduler:
this
))
{
try
{
(
_taskQueue
.
removeFirst
().
task
)();
}
finally
{
if
(
_taskQueue
.
isNotEmpty
)
_ensureEventLoopCallback
();
}
catch
(
exception
,
exceptionStack
)
{
StackTrace
callbackStack
;
assert
(()
{
callbackStack
=
entry
.
debugStack
;
return
true
;
}());
FlutterError
.
reportError
(
new
FlutterErrorDetails
(
exception:
exception
,
stack:
exceptionStack
,
library
:
'scheduler library'
,
context:
'during a task callback'
,
informationCollector:
(
callbackStack
==
null
)
?
null
:
(
StringBuffer
information
)
{
information
.
writeln
(
'
\n
This exception was thrown in the context of a task callback. '
'When the task callback was _registered_ (as opposed to when the '
'exception was thrown), this was the stack:'
);
FlutterError
.
defaultStackFilter
(
callbackStack
.
toString
().
trimRight
().
split
(
'
\n
'
)).
forEach
(
information
.
writeln
);
}
}
else
{
// TODO(floitsch): we shouldn't need to request a frame. Just schedule
// an event-loop callback.
scheduleFrame
();
));
}
return
_taskQueue
.
isNotEmpty
;
}
return
false
;
}
int
_nextFrameCallbackId
=
0
;
// positive
...
...
@@ -437,6 +522,11 @@ abstract class SchedulerBinding extends BindingBase {
/// added.
///
/// Post-frame callbacks cannot be unregistered. They are called exactly once.
///
/// See also:
///
/// * [scheduleFrameCallback], which registers a callback for the start of
/// the next frame.
void
addPostFrameCallback
(
FrameCallback
callback
)
{
_postFrameCallbacks
.
add
(
callback
);
}
...
...
@@ -473,6 +563,20 @@ abstract class SchedulerBinding extends BindingBase {
SchedulerPhase
get
schedulerPhase
=>
_schedulerPhase
;
SchedulerPhase
_schedulerPhase
=
SchedulerPhase
.
idle
;
/// Whether frames are currently being scheduled when [scheduleFrame] is called.
///
/// This value depends on the value of the [lifecycleState].
bool
get
framesEnabled
=>
_framesEnabled
;
bool
_framesEnabled
=
true
;
void
_setFramesEnabledState
(
bool
enabled
)
{
if
(
_framesEnabled
==
enabled
)
return
;
_framesEnabled
=
enabled
;
if
(
enabled
)
scheduleFrame
();
}
/// Schedules a new frame using [scheduleFrame] if this object is not
/// currently producing a frame.
///
...
...
@@ -494,10 +598,25 @@ abstract class SchedulerBinding extends BindingBase {
/// another frame to be scheduled, even if the current frame has not yet
/// completed.
///
/// Scheduled frames are serviced when triggered by a "Vsync" signal provided
/// by the operating system. The "Vsync" signal, or vertical synchronization
/// signal, was historically related to the display refresh, at a time when
/// hardware physically moved a beam of electrons vertically between updates
/// of the display. The operation of contemporary hardware is somewhat more
/// subtle and complicated, but the conceptual "Vsync" refresh signal continue
/// to be used to indicate when applications should update their rendering.
///
/// To have a stack trace printed to the console any time this function
/// schedules a frame, set [debugPrintScheduleFrameStacks] to true.
///
/// See also:
///
/// * [scheduleForcedFrame], which ignores the [lifecycleState] when
/// scheduling a frame.
/// * [scheduleWarmUpFrame], which ignores the "Vsync" signal entirely and
/// triggers a frame immediately.
void
scheduleFrame
()
{
if
(
_hasScheduledFrame
)
if
(
_hasScheduledFrame
||
!
_framesEnabled
)
return
;
assert
(()
{
if
(
debugPrintScheduleFrameStacks
)
...
...
@@ -508,6 +627,76 @@ abstract class SchedulerBinding extends BindingBase {
_hasScheduledFrame
=
true
;
}
/// Schedules a new frame by calling [Window.scheduleFrame].
///
/// After this is called, the engine will call [handleBeginFrame], even if
/// frames would normally not be scheduled by [scheduleFrame] (e.g. even if
/// the device's screen is turned off).
///
/// The framework uses this to force a frame to be rendered at the correct
/// size when the phone is rotated, so that a correctly-sized rendering is
/// available when the screen is turned back on.
///
/// To have a stack trace printed to the console any time this function
/// schedules a frame, set [debugPrintScheduleFrameStacks] to true.
///
/// Prefer using [scheduleFrame] unless it is imperative that a frame be
/// scheduled immediately, since using [scheduleForceFrame] will cause
/// significantly higher battery usage when the device should be idle.
///
/// Consider using [scheduleWarmUpFrame] instead if the goal is to update the
/// rendering as soon as possible (e.g. at application startup).
void
scheduleForcedFrame
()
{
if
(
_hasScheduledFrame
)
return
;
assert
(()
{
if
(
debugPrintScheduleFrameStacks
)
debugPrintStack
(
label:
'scheduleForcedFrame() called. Current phase is
$schedulerPhase
.'
);
return
true
;
}());
ui
.
window
.
scheduleFrame
();
_hasScheduledFrame
=
true
;
}
bool
_warmUpFrame
=
false
;
/// Schedule a frame to run as soon as possible, rather than waiting for
/// the engine to request a frame in response to a system "Vsync" signal.
///
/// This is used during application startup so that the first frame (which is
/// likely to be quite expensive) gets a few extra milliseconds to run.
///
/// If a frame has already been scheduled with [scheduleFrame] or
/// [scheduleForcedFrame], this call may delay that frame.
///
/// Prefer [scheduleFrame] to update the display in normal operation.
void
scheduleWarmUpFrame
()
{
assert
(!
_warmUpFrame
);
final
bool
hadScheduledFrame
=
_hasScheduledFrame
;
_warmUpFrame
=
true
;
// We use timers here to ensure that microtasks flush in between.
Timer
.
run
(()
{
assert
(
_warmUpFrame
);
handleBeginFrame
(
null
);
});
Timer
.
run
(()
{
assert
(
_warmUpFrame
);
handleDrawFrame
();
// We call resetEpoch after this frame so that, in the hot reload case,
// the very next frame pretends to have occurred immediately after this
// warm-up frame. The warm-up frame's timestamp will typically be far in
// the past (the time of the last real frame), so if we didn't reset the
// epoch we would see a sudden jump from the old time in the warm-up frame
// to the new time in the "real" frame. The biggest problem with this is
// that implicit animations end up being triggered at the old time and
// then skipping every frame and finishing in the new time.
resetEpoch
();
_warmUpFrame
=
false
;
if
(
hadScheduledFrame
)
scheduleFrame
();
});
}
Duration
_firstRawTimeStampInEpoch
;
Duration
_epochStart
=
Duration
.
ZERO
;
Duration
_lastRawTimeStamp
=
Duration
.
ZERO
;
...
...
@@ -560,6 +749,24 @@ abstract class SchedulerBinding extends BindingBase {
int
_profileFrameNumber
=
0
;
final
Stopwatch
_profileFrameStopwatch
=
new
Stopwatch
();
String
_debugBanner
;
bool
_ignoreNextEngineDrawFrame
=
false
;
void
_handleBeginFrame
(
Duration
rawTimeStamp
)
{
if
(
_warmUpFrame
)
{
assert
(!
_ignoreNextEngineDrawFrame
);
_ignoreNextEngineDrawFrame
=
true
;
return
;
}
handleBeginFrame
(
rawTimeStamp
);
}
void
_handleDrawFrame
()
{
if
(
_ignoreNextEngineDrawFrame
)
{
_ignoreNextEngineDrawFrame
=
false
;
return
;
}
handleDrawFrame
();
}
/// Called by the engine to prepare the framework to produce a new frame.
///
...
...
@@ -576,9 +783,9 @@ abstract class SchedulerBinding extends BindingBase {
/// console using [debugPrint] and will contain the frame number (which
/// increments by one for each frame), and the time stamp of the frame. If the
/// given time stamp was null, then the string "warm-up frame" is shown
/// instead of the time stamp. This allows
you to distinguish frames eagerly
///
pushed by the framework from those requested by the engine in response to
///
the vsync
signal from the operating system.
/// instead of the time stamp. This allows
frames eagerly pushed by the
///
framework to be distinguished from those requested by the engine in
///
response to the "Vsync"
signal from the operating system.
///
/// You can also show a banner at the end of every frame by setting
/// [debugPrintEndFrameBanner] to true. This allows you to distinguish log
...
...
@@ -670,9 +877,6 @@ abstract class SchedulerBinding extends BindingBase {
}());
_currentFrameTimeStamp
=
null
;
}
// All frame-related callbacks have been executed. Run lower-priority tasks.
_runTasks
();
}
void
_profileFramePostEvent
()
{
...
...
@@ -707,7 +911,6 @@ abstract class SchedulerBinding extends BindingBase {
void
_invokeFrameCallback
(
FrameCallback
callback
,
Duration
timeStamp
,
[
StackTrace
callbackStack
])
{
assert
(
callback
!=
null
);
assert
(
_FrameCallbackEntry
.
debugCurrentCallbackStack
==
null
);
// TODO(ianh): Consider using a Zone instead to track the current callback registration stack
assert
(()
{
_FrameCallbackEntry
.
debugCurrentCallbackStack
=
callbackStack
;
return
true
;
}());
try
{
callback
(
timeStamp
);
...
...
packages/flutter/lib/src/scheduler/ticker.dart
View file @
186d1e9b
...
...
@@ -101,9 +101,21 @@ class Ticker {
/// A ticker that is [muted] can be active (see [isActive]) yet not be
/// ticking. In that case, the ticker will not call its callback, and
/// [isTicking] will be false, but time will still be progressing.
// TODO(ianh): we should teach the scheduler binding about the lifecycle events
// and then this could return an accurate view of the actual scheduler.
bool
get
isTicking
=>
_future
!=
null
&&
!
muted
;
///
/// This will return false if the [Scheduler.lifecycleState] is one that
/// indicates the application is not currently visible (e.g. if the device's
/// screen is turned off).
bool
get
isTicking
{
if
(
_future
==
null
)
return
false
;
if
(
muted
)
return
false
;
if
(
SchedulerBinding
.
instance
.
framesEnabled
)
return
true
;
if
(
SchedulerBinding
.
instance
.
schedulerPhase
!=
SchedulerPhase
.
idle
)
return
true
;
// for example, we might be in a warm-up frame or forced frame
return
false
;
}
/// Whether time is elapsing for this [Ticker]. Becomes true when [start] is
/// called and false when [stop] is called.
...
...
packages/flutter/lib/src/services/message_codec.dart
View file @
186d1e9b
...
...
@@ -8,6 +8,8 @@ import 'package:flutter/foundation.dart';
import
'platform_channel.dart'
;
export
'dart:typed_data'
show
ByteData
;
/// A message encoding/decoding mechanism.
///
/// Both operations throw an exception, if conversion fails. Such situations
...
...
packages/flutter/lib/src/widgets/binding.dart
View file @
186d1e9b
...
...
@@ -231,7 +231,7 @@ abstract class WidgetsBindingObserver {
}
/// The glue between the widgets layer and the Flutter engine.
abstract
class
WidgetsBinding
extends
BindingBase
with
GestureBinding
,
RendererBinding
{
abstract
class
WidgetsBinding
extends
BindingBase
with
SchedulerBinding
,
GestureBinding
,
RendererBinding
{
// This class is intended to be used as a mixin, and should not be
// extended directly.
factory
WidgetsBinding
.
_
()
=>
null
;
...
...
@@ -243,7 +243,6 @@ abstract class WidgetsBinding extends BindingBase with GestureBinding, RendererB
buildOwner
.
onBuildScheduled
=
_handleBuildScheduled
;
ui
.
window
.
onLocaleChanged
=
handleLocaleChanged
;
SystemChannels
.
navigation
.
setMethodCallHandler
(
_handleNavigationInvocation
);
SystemChannels
.
lifecycle
.
setMessageHandler
(
_handleLifecycleMessage
);
SystemChannels
.
system
.
setMessageHandler
(
_handleSystemMessage
);
}
...
...
@@ -369,6 +368,8 @@ abstract class WidgetsBinding extends BindingBase with GestureBinding, RendererB
/// Calls [dispatchLocaleChanged] to notify the binding observers.
///
/// See [Window.onLocaleChanged].
@protected
@mustCallSuper
void
handleLocaleChanged
()
{
dispatchLocaleChanged
(
ui
.
window
.
locale
);
}
...
...
@@ -379,6 +380,8 @@ abstract class WidgetsBinding extends BindingBase with GestureBinding, RendererB
///
/// This is called by [handleLocaleChanged] when the [Window.onLocaleChanged]
/// notification is received.
@protected
@mustCallSuper
void
dispatchLocaleChanged
(
Locale
locale
)
{
for
(
WidgetsBindingObserver
observer
in
_observers
)
observer
.
didChangeLocale
(
locale
);
...
...
@@ -398,6 +401,7 @@ abstract class WidgetsBinding extends BindingBase with GestureBinding, RendererB
///
/// This method exposes the `popRoute` notification from
/// [SystemChannels.navigation].
@protected
Future
<
Null
>
handlePopRoute
()
async
{
for
(
WidgetsBindingObserver
observer
in
new
List
<
WidgetsBindingObserver
>.
from
(
_observers
))
{
if
(
await
observer
.
didPopRoute
())
...
...
@@ -416,6 +420,8 @@ abstract class WidgetsBinding extends BindingBase with GestureBinding, RendererB
///
/// This method exposes the `pushRoute` notification from
/// [SystemChannels.navigation].
@protected
@mustCallSuper
Future
<
Null
>
handlePushRoute
(
String
route
)
async
{
for
(
WidgetsBindingObserver
observer
in
new
List
<
WidgetsBindingObserver
>.
from
(
_observers
))
{
if
(
await
observer
.
didPushRoute
(
route
))
...
...
@@ -433,35 +439,13 @@ abstract class WidgetsBinding extends BindingBase with GestureBinding, RendererB
return
new
Future
<
Null
>.
value
();
}
/// Called when the application lifecycle state changes.
///
/// Notifies all the observers using
/// [WidgetsBindingObserver.didChangeAppLifecycleState].
///
/// This method exposes notifications from [SystemChannels.lifecycle].
@override
void
handleAppLifecycleStateChanged
(
AppLifecycleState
state
)
{
super
.
handleAppLifecycleStateChanged
(
state
);
for
(
WidgetsBindingObserver
observer
in
_observers
)
observer
.
didChangeAppLifecycleState
(
state
);
}
Future
<
String
>
_handleLifecycleMessage
(
String
message
)
async
{
switch
(
message
)
{
case
'AppLifecycleState.paused'
:
handleAppLifecycleStateChanged
(
AppLifecycleState
.
paused
);
break
;
case
'AppLifecycleState.resumed'
:
handleAppLifecycleStateChanged
(
AppLifecycleState
.
resumed
);
break
;
case
'AppLifecycleState.inactive'
:
handleAppLifecycleStateChanged
(
AppLifecycleState
.
inactive
);
break
;
case
'AppLifecycleState.suspending'
:
handleAppLifecycleStateChanged
(
AppLifecycleState
.
suspending
);
break
;
}
return
null
;
}
/// Called when the operating system notifies the application of a memory
/// pressure situation.
///
...
...
packages/flutter/test/foundation/service_extensions_test.dart
View file @
186d1e9b
...
...
@@ -40,9 +40,11 @@ class TestServiceExtensionsBinding extends BindingBase
}
int
reassembled
=
0
;
bool
pendingReassemble
=
false
;
@override
Future
<
Null
>
performReassemble
()
{
reassembled
+=
1
;
pendingReassemble
=
true
;
return
super
.
performReassemble
();
}
...
...
@@ -60,6 +62,17 @@ class TestServiceExtensionsBinding extends BindingBase
ui
.
window
.
onDrawFrame
();
}
@override
void
scheduleForcedFrame
()
{
expect
(
true
,
isFalse
);
}
@override
void
scheduleWarmUpFrame
()
{
expect
(
pendingReassemble
,
isTrue
);
pendingReassemble
=
false
;
}
Future
<
Null
>
flushMicrotasks
()
{
final
Completer
<
Null
>
completer
=
new
Completer
<
Null
>();
Timer
.
run
(
completer
.
complete
);
...
...
packages/flutter/test/scheduler/animation_test.dart
View file @
186d1e9b
...
...
@@ -4,11 +4,12 @@
import
'package:flutter/foundation.dart'
;
import
'package:flutter/scheduler.dart'
;
import
'package:flutter/services.dart'
;
import
'package:test/test.dart'
;
import
'scheduler_tester.dart'
;
class
TestSchedulerBinding
extends
BindingBase
with
SchedulerBinding
{
}
class
TestSchedulerBinding
extends
BindingBase
with
S
ervicesBinding
,
S
chedulerBinding
{
}
void
main
(
)
{
final
SchedulerBinding
scheduler
=
new
TestSchedulerBinding
();
...
...
packages/flutter/test/scheduler/scheduler_test.dart
View file @
186d1e9b
...
...
@@ -4,9 +4,10 @@
import
'package:flutter/foundation.dart'
;
import
'package:flutter/scheduler.dart'
;
import
'package:flutter/services.dart'
;
import
'package:test/test.dart'
;
class
TestSchedulerBinding
extends
BindingBase
with
SchedulerBinding
{
}
class
TestSchedulerBinding
extends
BindingBase
with
S
ervicesBinding
,
S
chedulerBinding
{
}
class
TestStrategy
{
int
allowedPriority
=
10000
;
...
...
@@ -32,20 +33,20 @@ void main() {
strategy
.
allowedPriority
=
100
;
for
(
int
i
=
0
;
i
<
3
;
i
+=
1
)
scheduler
.
handleEventLoopCallback
(
);
expect
(
scheduler
.
handleEventLoopCallback
(),
isFalse
);
expect
(
executedTasks
.
isEmpty
,
isTrue
);
strategy
.
allowedPriority
=
50
;
for
(
int
i
=
0
;
i
<
3
;
i
+=
1
)
scheduler
.
handleEventLoopCallback
(
);
expect
(
executedTasks
.
length
,
equals
(
1
));
expect
(
scheduler
.
handleEventLoopCallback
(),
i
==
0
?
isTrue
:
isFalse
);
expect
(
executedTasks
,
hasLength
(
1
));
expect
(
executedTasks
.
single
,
equals
(
80
));
executedTasks
.
clear
();
strategy
.
allowedPriority
=
20
;
for
(
int
i
=
0
;
i
<
3
;
i
+=
1
)
scheduler
.
handleEventLoopCallback
(
);
expect
(
executedTasks
.
length
,
equals
(
2
));
expect
(
scheduler
.
handleEventLoopCallback
(),
i
<
2
?
isTrue
:
isFalse
);
expect
(
executedTasks
,
hasLength
(
2
));
expect
(
executedTasks
[
0
],
equals
(
23
));
expect
(
executedTasks
[
1
],
equals
(
23
));
executedTasks
.
clear
();
...
...
@@ -55,32 +56,32 @@ void main() {
scheduleAddingTask
(
5
);
scheduleAddingTask
(
97
);
for
(
int
i
=
0
;
i
<
3
;
i
+=
1
)
scheduler
.
handleEventLoopCallback
(
);
expect
(
executedTasks
.
length
,
equals
(
2
));
expect
(
scheduler
.
handleEventLoopCallback
(),
i
<
2
?
isTrue
:
isFalse
);
expect
(
executedTasks
,
hasLength
(
2
));
expect
(
executedTasks
[
0
],
equals
(
99
));
expect
(
executedTasks
[
1
],
equals
(
97
));
executedTasks
.
clear
();
strategy
.
allowedPriority
=
10
;
for
(
int
i
=
0
;
i
<
3
;
i
+=
1
)
scheduler
.
handleEventLoopCallback
(
);
expect
(
executedTasks
.
length
,
equals
(
2
));
expect
(
scheduler
.
handleEventLoopCallback
(),
i
<
2
?
isTrue
:
isFalse
);
expect
(
executedTasks
,
hasLength
(
2
));
expect
(
executedTasks
[
0
],
equals
(
19
));
expect
(
executedTasks
[
1
],
equals
(
11
));
executedTasks
.
clear
();
strategy
.
allowedPriority
=
1
;
for
(
int
i
=
0
;
i
<
4
;
i
+=
1
)
scheduler
.
handleEventLoopCallback
(
);
expect
(
executedTasks
.
length
,
equals
(
3
));
expect
(
scheduler
.
handleEventLoopCallback
(),
i
<
3
?
isTrue
:
isFalse
);
expect
(
executedTasks
,
hasLength
(
3
));
expect
(
executedTasks
[
0
],
equals
(
5
));
expect
(
executedTasks
[
1
],
equals
(
3
));
expect
(
executedTasks
[
2
],
equals
(
2
));
executedTasks
.
clear
();
strategy
.
allowedPriority
=
0
;
scheduler
.
handleEventLoopCallback
(
);
expect
(
executedTasks
.
length
,
equals
(
1
));
expect
(
scheduler
.
handleEventLoopCallback
(),
isFalse
);
expect
(
executedTasks
,
hasLength
(
1
));
expect
(
executedTasks
[
0
],
equals
(
0
));
});
}
packages/flutter/test/scheduler/ticker_test.dart
View file @
186d1e9b
...
...
@@ -3,13 +3,14 @@
// found in the LICENSE file.
import
'package:flutter/scheduler.dart'
;
import
'package:flutter/services.dart'
;
import
'package:flutter_test/flutter_test.dart'
;
void
main
(
)
{
testWidgets
(
'Ticker mute control test'
,
(
WidgetTester
tester
)
async
{
int
tickCount
=
0
;
void
handleTick
(
Duration
duration
)
{
++
tickCount
;
tickCount
+=
1
;
}
final
Ticker
ticker
=
new
Ticker
(
handleTick
);
...
...
@@ -81,4 +82,25 @@ void main() {
expect
(
ticker
,
hasOneLineDescription
);
expect
(
ticker
.
toString
(
debugIncludeStack:
true
),
contains
(
'testFunction'
));
});
testWidgets
(
'Ticker stops ticking when application is paused'
,
(
WidgetTester
tester
)
async
{
int
tickCount
=
0
;
void
handleTick
(
Duration
duration
)
{
tickCount
+=
1
;
}
final
Ticker
ticker
=
new
Ticker
(
handleTick
);
ticker
.
start
();
expect
(
ticker
.
isTicking
,
isTrue
);
expect
(
ticker
.
isActive
,
isTrue
);
expect
(
tickCount
,
equals
(
0
));
final
ByteData
message
=
const
StringCodec
().
encodeMessage
(
'AppLifecycleState.paused'
);
await
BinaryMessages
.
handlePlatformMessage
(
'flutter/lifecycle'
,
message
,
(
_
)
{});
expect
(
ticker
.
isTicking
,
isFalse
);
expect
(
ticker
.
isActive
,
isTrue
);
ticker
.
stop
();
});
}
packages/flutter/test/widgets/binding_test.dart
View file @
186d1e9b
...
...
@@ -84,4 +84,56 @@ void main() {
WidgetsBinding
.
instance
.
removeObserver
(
observer
);
});
testWidgets
(
'Application lifecycle affects frame scheduling'
,
(
WidgetTester
tester
)
async
{
ByteData
message
;
expect
(
tester
.
binding
.
hasScheduledFrame
,
isFalse
);
message
=
const
StringCodec
().
encodeMessage
(
'AppLifecycleState.paused'
);
await
BinaryMessages
.
handlePlatformMessage
(
'flutter/lifecycle'
,
message
,
(
_
)
{});
expect
(
tester
.
binding
.
hasScheduledFrame
,
isFalse
);
message
=
const
StringCodec
().
encodeMessage
(
'AppLifecycleState.resumed'
);
await
BinaryMessages
.
handlePlatformMessage
(
'flutter/lifecycle'
,
message
,
(
_
)
{});
expect
(
tester
.
binding
.
hasScheduledFrame
,
isTrue
);
await
tester
.
pump
();
expect
(
tester
.
binding
.
hasScheduledFrame
,
isFalse
);
message
=
const
StringCodec
().
encodeMessage
(
'AppLifecycleState.inactive'
);
await
BinaryMessages
.
handlePlatformMessage
(
'flutter/lifecycle'
,
message
,
(
_
)
{});
expect
(
tester
.
binding
.
hasScheduledFrame
,
isFalse
);
message
=
const
StringCodec
().
encodeMessage
(
'AppLifecycleState.suspending'
);
await
BinaryMessages
.
handlePlatformMessage
(
'flutter/lifecycle'
,
message
,
(
_
)
{});
expect
(
tester
.
binding
.
hasScheduledFrame
,
isFalse
);
message
=
const
StringCodec
().
encodeMessage
(
'AppLifecycleState.inactive'
);
await
BinaryMessages
.
handlePlatformMessage
(
'flutter/lifecycle'
,
message
,
(
_
)
{});
expect
(
tester
.
binding
.
hasScheduledFrame
,
isTrue
);
await
tester
.
pump
();
expect
(
tester
.
binding
.
hasScheduledFrame
,
isFalse
);
message
=
const
StringCodec
().
encodeMessage
(
'AppLifecycleState.paused'
);
await
BinaryMessages
.
handlePlatformMessage
(
'flutter/lifecycle'
,
message
,
(
_
)
{});
expect
(
tester
.
binding
.
hasScheduledFrame
,
isFalse
);
tester
.
binding
.
scheduleFrame
();
expect
(
tester
.
binding
.
hasScheduledFrame
,
isFalse
);
tester
.
binding
.
scheduleForcedFrame
();
expect
(
tester
.
binding
.
hasScheduledFrame
,
isTrue
);
await
tester
.
pump
();
expect
(
tester
.
binding
.
hasScheduledFrame
,
isFalse
);
int
frameCount
=
0
;
tester
.
binding
.
addPostFrameCallback
((
Duration
duration
)
{
frameCount
+=
1
;
});
expect
(
tester
.
binding
.
hasScheduledFrame
,
isFalse
);
await
tester
.
pump
(
const
Duration
(
milliseconds:
1
));
expect
(
tester
.
binding
.
hasScheduledFrame
,
isFalse
);
expect
(
frameCount
,
0
);
tester
.
binding
.
scheduleWarmUpFrame
();
// this actually tests flutter_test's implementation
expect
(
tester
.
binding
.
hasScheduledFrame
,
isFalse
);
expect
(
frameCount
,
1
);
});
}
packages/flutter_test/lib/src/binding.dart
View file @
186d1e9b
...
...
@@ -583,6 +583,7 @@ class AutomatedTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding {
handleBeginFrame
(
null
);
_fakeAsync
.
flushMicrotasks
();
handleDrawFrame
();
_fakeAsync
.
flushMicrotasks
();
}
@override
...
...
@@ -829,6 +830,13 @@ class LiveTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding {
super
.
scheduleFrame
();
}
@override
void
scheduleForcedFrame
()
{
if
(
framePolicy
==
LiveTestWidgetsFlutterBindingFramePolicy
.
benchmark
)
return
;
// In benchmark mode, don't actually schedule any engine frames.
super
.
scheduleForcedFrame
();
}
bool
_doDrawThisFrame
;
@override
...
...
packages/flutter_tools/test/commands/test_test.dart
View file @
186d1e9b
...
...
@@ -38,6 +38,11 @@ void main() {
return
_testFile
(
'test_async_utils_unguarded'
,
automatedTestsDirectory
,
flutterTestDirectory
);
});
testUsingContext
(
'report a nice error when a Ticker is left running'
,
()
async
{
Cache
.
flutterRoot
=
'../..'
;
return
_testFile
(
'ticker'
,
automatedTestsDirectory
,
flutterTestDirectory
);
});
testUsingContext
(
'report a nice error when a pubspec.yaml is missing a flutter_test dependency'
,
()
async
{
final
String
missingDependencyTests
=
fs
.
path
.
join
(
'..'
,
'..'
,
'dev'
,
'missing_dependency_tests'
);
Cache
.
flutterRoot
=
'../..'
;
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment