每个动画帧会回调一次。
-
创建时,ticker的初始状态是禁用的。调用[start]启动。
-
A [Ticker]可以通过将[muted]设置为true来进行沉默。在沉默状态下,时间仍然在流逝,[start]和[stop]仍然可以被调用,但是不会回调到Ticker中。
-
按照约定,[start]和[stop]方法由[ticker]的使用者使用,[muted]属性由[TickerProvider] 控制。
-
Ticker回调是由[SchedulerBinding]驱动的。[SchedulerBinding.scheduleFrameCallback]。
**总结: **每次frame绘制之前会回调到ticker,由SchedulerBinding进行驱动
ScheduleBinding
/// Scheduler for running the following:
///
/// * Transient callbacks, triggered by the system’s [Window.onBeginFrame]
/// callback, for synchronizing the application’s behavior to the system’s
/// display. For example, [Ticker]s and [AnimationController]s trigger from
/// these.
///
/// * Persistent callbacks, triggered by the system’s [Window.onDrawFrame]
/// callback, for updating the system’s display after transient callbacks have
/// executed. For example, the rendering layer uses this to drive its
/// rendering pipeline.
///
/// * Post-frame callbacks, which are run after persistent callbacks, just
/// before returning from the [Window.onDrawFrame] callback.
///
/// * Non-rendering tasks, to be run between frames. These are given a
/// priority and are executed in priority order according to a
/// [schedulingStrategy].
调度运行时的这些回调
-
Transient callbacks,由系统的[Window.onBeginFrame]回调,用于同步应用程序的行为到系统的展示。例如,[Ticker]s和[AnimationController]s触发器来自与它。
-
Persistent callbacks 由系统的[Window.onDrawFrame]方法触发回调,用于在TransientCallback执行后更新系统的展示。例如,渲染层使用他来驱动渲染管道进行build,layout,paint
-
_Post-frame callbacks_在下一帧绘制前回调,主要做一些清理和准备工作
-
Non-rendering tasks 非渲染的任务,在帧构造之间,他们具有优先级,通过[schedulingStrategy]的优先级进行执行,例如用户的输入
**总结:**FrameCallback:SchedulerBinding 类中有三个FrameCallback回调队列, 在一次绘制过程中,这三个回调队列会放在不同时机被执行:
-
transientCallbacks:用于存放一些临时回调,一般存放动画回调。可以通过
SchedulerBinding.instance.scheduleFrameCallback
添加回调。 -
persistentCallbacks:用于存放一些持久的回调,不能在此类回调中再请求新的绘制帧,持久回调一经注册则不能移除。
SchedulerBinding.instance.addPersitentFrameCallback()
,这个回调中处理了布局与绘制工作。 -
postFrameCallbacks:在Frame结束时只会被调用一次,调用后会被系统移除,可由
SchedulerBinding.instance.addPostFrameCallback()
注册,注意,不要在此类回调中再触发新的Frame,这可以会导致循环刷新。
/// Schedules the given transient frame callback.
///
/// Adds the given callback to the list of frame callbacks and ensures that a
/// frame is scheduled.
///
/// If this is a one-off registration, ignore the rescheduling
argument.
///
/// If this is a callback that will be re-registered each time it fires, then
/// when you re-register the callback, set the rescheduling
argument to
/// true. This has no effect in release builds, but in debug builds, it
/// ensures that the stack trace that is stored for this callback is the
/// original stack trace for when the callback was first registered, rather
/// than the stack trace for when the callback is re-registered. This makes it
/// easier to track down the original reason that a particular callback was
/// called. If rescheduling
is true, the call must be in the context of a
/// frame callback.
///
/// Callbacks registered with this method can be canceled using
/// [cancelFrameCallbackWithId].
int scheduleFrameCallback(FrameCallback callback, { bool rescheduling = false }) {
scheduleFrame();
_nextFrameCallbackId += 1;
_transientCallbacks[_nextFrameCallbackId] = _FrameCallbackEntry(callback, rescheduling: rescheduling);
return _nextFrameCallbackId;
}
调度 transient frame callback队列
添加一个callBack,确保fram绘制之前能够回调到他
void scheduleFrame() {
if (_hasScheduledFrame || !_framesEnabled)
return;
assert(() {
if (debugPrintScheduleFrameStacks)
debugPrintStack(label: ‘scheduleFrame() called. Current phase is $schedulerPhase.’);
return true;
}());
ensureFrameCallbacksRegistered();
window.scheduleFrame();
_hasScheduledFrame = true;
}
@protected
void ensureFrameCallbacksRegistered() {
window.onBeginFrame ??= _handleBeginFrame;
window.onDrawFrame ??= _handleDrawFrame;
}
赋给了window.onBeginFram方法
/// Called by the engine to prepare the framework to produce a new frame.
///
/// This function calls all the transient frame callbacks registered by
/// [scheduleFrameCallback]. It then returns, any scheduled microtasks are run
/// (e.g. handlers for any [Future]s resolved by transient frame callbacks),
/// and [handleDrawFrame] is called to continue the frame.
///
/// If the given time stamp is null, the time stamp from the last frame is
/// reused.
///
/// To have a banner shown at the start of every frame in debug mode, set
/// [debugPrintBeginFrameBanner] to true. The banner will be printed to the
/// 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 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
/// statements printed during a frame from those printed between frames (e.g.
/// in response to events or timers).
void handleBeginFrame(Duration rawTimeStamp) {
Timeline.startSync(‘Frame’, arguments: timelineWhitelistArguments);
_firstRawTimeStampInEpoch ??= rawTimeStamp;
_currentFrameTimeStamp = _adjustForEpoch(rawTimeStamp ?? _lastRawTimeStamp);
if (rawTimeStamp != null)
_lastRawTimeStamp = rawTimeStamp;
_hasScheduledFrame = false;
try {
// TRANSIENT FRAME CALLBACKS
Timeline.startSync(‘Animate’, arguments: timelineWhitelistArguments);
_schedulerPhase = SchedulerPhase.transientCallbacks;
final Map<int, _FrameCallbackEntry> callbacks = _transientCallbacks;
//调用callBack******
_transientCallbacks = <int, _FrameCallbackEntry>{};
callbacks.forEach((int id, _FrameCallbackEntry callbackEntry) {
if (!_removedIds.contains(id))
_invokeFrameCallback(callbackEntry.callback, _currentFrameTimeStamp, callbackEntry.debugStack);
//**************************callback(timeStamp);
});
_removedIds.clear();
} finally {
_schedulerPhase = SchedulerPhase.midFrameMicrotasks;
}
}
-
当框架准备构建一帧的时候由engine调用,可以猜想endgine调用的实际上是window的onBegainFrame
-
它会回调[scheduleFrameCallback]中的所有方法(包括一些future的方法),当执行完成后,系统会调用[handleDrawFrame]
/// Signature for frame-related callbacks from the scheduler.
///
/// The timeStamp
is the number of milliseconds since the beginning of the
/// scheduler’s epoch. Use timeStamp to determine how far to advance animation
/// timelines so that all the animations in the system are synchronized to a
/// common time base.
typedef FrameCallback = void Function(Duration timeStamp);
callback(timeStamp);
- “时间戳”是自scheduler开始以来的毫秒数。使用时间戳来确定动画时间线要前进多远,以便系统中的所有动画都同步到一通用的时间线上。
**总结:**当每次系统绘制的之前,会回调到ui.windows的onBegainFrame,而这个onBegainFrame执行的是handleBeginFrame,将时间值回调给了每一个callback。这里注意的是,是在在绘制之前,因为我们一般在绘制之前去通过改变控件的属性值完成动画,而这个动作必须在绘制前完成。
Ticker
反向搜索谁调用了scheduleFrameCallback,发现是在Ticker中的scheduleTick,而scheduleTick有几个地方调用后面来看
/// Schedules a tick for the next frame.
///
/// This should only be called if [shouldScheduleTick] is true.
@protected
void scheduleTick({ bool rescheduling = false }) {
assert(!scheduled);
assert(shouldScheduleTick);
_animationId = SchedulerBinding.instance.scheduleFrameCallback(_tick, rescheduling: rescheduling);
}
这里发现ticker传入了一个_tick对象到scheduleFrameCallback中
void _tick(Duration timeStamp) {
assert(isTicking);
assert(scheduled);
_animationId = null;
_startTime ??= timeStamp;
_onTick(timeStamp - _startTime);
// The onTick callback may have scheduled another tick already, for
// example by calling stop then start again.
if (shouldScheduleTick)
scheduleTick(rescheduling: true);
}
final TickerCallback _onTick;
Ticker(this._onTick, { this.debugLabel }) {
assert(() {
_debugCreationStack = StackTrace.current;
return true;
}());
}
这里发现,_ticke方法,将时间戳,转换为一个相对时间,回调的到onTick中。onTicker是Ticker构造时候创建的。而Ticker的创建其实主要只在TickProvider中
@override
Ticker createTicker(TickerCallback onTick) {
_ticker = Ticker(onTick, debugLabel: kDebugMode ? ‘created by $this’ : null);
return _ticker;
}
那谁调用了这个createTicker呢,其实自然能联想到是AnimationController
AnimationController({
double value,
this.duration,
this.reverseDuration,
this.debugLabel,
this.lowerBound = 0.0,
this.upperBound = 1.0,
this.animationBehavior = AnimationBehavior.normal,
@required TickerProvider vsync,
}) : assert(lowerBound != null),
assert(upperBound != null),
assert(upperBound >= lowerBound),
assert(vsync != null),
_direction = _AnimationDirection.forward {
_ticker = vsync.createTicker(_tick);
_internalSetValue(value ?? lowerBound);
}
void _tick(Duration elapsed) {
_lastElapsedDuration = elapsed;
final double elapsedInSeconds = elapsed.inMicroseconds.toDouble() / Duration.microsecondsPerSecond;
assert(elapsedInSeconds >= 0.0);
_value = _simulation.x(elapsedInSeconds).clamp(lowerBound, upperBound);
if (_simulation.isDone(elapsedInSeconds)) {
_status = (_direction == _AnimationDirection.forward) ?
AnimationStatus.completed :
AnimationStatus.dismissed;
stop(canceled: false);
}
///注意到了 notifyListeners()
方法,果然AnimationController继承自Animation继承自Listenable。
notifyListeners();
_checkStatusChanged();
}
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

文末
我总结了一些Android核心知识点,以及一些最新的大厂面试题、知识脑图和视频资料解析。
以后的路也希望我们能一起走下去。(谢谢大家一直以来的支持)
部分资料一览:
- 330页PDF Android学习核心笔记(内含8大板块)
-
Android学习的系统对应视频
-
Android进阶的系统对应学习资料
- Android BAT大厂面试题(有解析)
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
src=“https://i-blog.csdnimg.cn/blog_migrate/59530ff4e3b5ccdb9bf917af06bb2597.jpeg” />
文末
我总结了一些Android核心知识点,以及一些最新的大厂面试题、知识脑图和视频资料解析。
以后的路也希望我们能一起走下去。(谢谢大家一直以来的支持)
部分资料一览:
- 330页PDF Android学习核心笔记(内含8大板块)
[外链图片转存中…(img-ryVrKDyM-1713307467694)]
[外链图片转存中…(img-dJV9lXRU-1713307467695)]
-
Android学习的系统对应视频
-
Android进阶的系统对应学习资料
[外链图片转存中…(img-mxgZsPMg-1713307467696)]
- Android BAT大厂面试题(有解析)
[外链图片转存中…(img-zsD9HGBr-1713307467697)]
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!