接触到Flutter之后,我第一时间尝了一下鲜。那时尚未详细的中文文档,只有Flutter的官网上的英文文档可参考。边看边学,捣鼓了些Flutter app以及插件。而后把这个过程当中的一些心得体会总结出来几篇文章:前端
总的感受就是Flutter是如此简洁却又如此的强大。在熟悉了FLutter app的开发以后,天然开始对Flutter框架如何运做产生了兴趣。因而花了一些时间研究了一下Flutter框架的源码,写了一系列Flutter源码分析的文章做为一个总结:less
近日有掘友提出可否分享一下学习Flutter源码的一些方法和技巧,我以为这是个很好的建议,因而就有了这篇文章供你们参考。但愿能帮到想研究一下框架源码的同窗,也但愿你们若是有本身的一些阅读框架源码的经验也能分享出来,共同窗习提升。
本篇文章把学习Flutter框架源码分为3个阶段,学习前,学习中和学习后。学习前的阶段主要是介绍一些在开始看框架源码以前我本身以为有帮助的一些准备工做;学习中阶段主要会介绍在看看框架源码的时候一些可能有用的方法或技巧;学习后阶段主要会介绍在理解了框架源码的基础上能够作一些事情来巩固提升本身对其的理解深度。
那么咱们就开始吧。
在开始看框架源码以前,我会作一些准备工做,直到这些条件都具有了,再开始正式学习源码,会有事半功倍的效果。
要学习框架源码首先要有强烈的兴趣,也就是好奇心,说简单点也就是要多问问题。为何会有StatefulWidget
和StatelessWidget
?为何调用setState()
之后Flutter会刷新你的页面?是什么在驱动Flutter框架运行?动画又是如何动起来的?等等等等。有了这些问题,你才会有动力去学习框架源码来本身寻找答案。若是你对Flutter并无特别大的兴趣,仅仅是为了了解一下新东西,照着开发文档能写app以为就ok了。或者是为了完成领导下达的任务,为了完成kpi而简单的学习一下,浅尝辄止。那我以为这种状况下一我的是很难有去学习框架源码的念头的。
除了要有兴趣,还要有信心。相信本身能征服框架源码,不会被庞大的代码给吓到。可能会有一些同窗在开发Flutter app的过程当中会点开StatefulWidget
的源码去看看,可是跳转来跳转去很快就迷失在代码的海洋里找不着方向。又或者点开Flutter源码的目录看到满屏的.dart文件不知所措,直接劝退。这就是缺少信心的表现。
这里我送你们一句话:“How hard can it be?” 直译过来就是“这能难到哪里去呢?” 这句话来自我最喜欢的前著名汽车评测节目“Top Gear”主持人,“大猩猩” Jeremy Clarkson。
因此在你以为Flutter源码很难的时候不如问问本身,How hard can it be?
Flutter框架的一个不一样之处是使用了你们不太熟悉的dart语言。和Java,JS,OC等都有很大的区别。虽然dart能很快上手,短期以内你就能够开发出像模像样的Flutter app。可是若是要去学习框架源码,则须要咱们对这门新语言的特性很是的习惯,注意我这里说的是习惯而不是了解,也不是熟悉。若是你仅仅是知道新语言的特性,而不是习惯这些特性的话,去看框架源码是很是困难的。
特别是当你已经习惯了一种语言,好比Java,你对Java越熟悉,就越会以为dart别扭。怎么在一个.dart文件里有好多个类?函数也能当参数?闭包是什么鬼?async
和await
是怎么个执行顺序?要克服惯性思惟,特别是从本身熟悉的语言切换到不熟悉的语言带来的不适。才能顺利的开始新征程。怎么克服呢?惟有多看,多练,造成习惯。当你再也不被新语言的特性所阻滞的时候,才能顺利的开启框架源码学习之旅。
在开始看框架源码以前,我先看了一些介绍框架的文章资料。这些文章可能参差不齐,相互矛盾。也可能介绍的比较简单粗暴,看不懂里面在说啥,从而带来更多的问题。Widget我知道,但是Element和RenderObject又是啥?它们有啥关系?怎么会有那么多的树?Layer又是什么东西?
可是这些文章须要反复的看,特别是要以官网上的权威文档为主,相互比较,相互补充,这样慢慢的在脑海中就会创建起粗浅的框架模型。有了这个模型,你就会知道哪些地方比较重要,哪些地方须要特别关注,从大的分块上对框架有一个大致的认识。这个模型就是咱们学习框架源码的指路明灯,避免咱们迷失方向。
特别是对渲染流水线的介绍,这张图能够说就是整个Flutter框架的核心,从个人分析系列文章能够看出,我学习Flutter源码就是牢牢围绕这张图进行的。
最后呢,就是你要对前端,对GUI编程有个更高层次的理解会极大的帮助你去理解Flutter源码,无论是Android ,iOS,Web仍是Flutter,各类各样的前端框架作的事情其实都是差很少的。因此它们面对的问题也是差很少的,它们各自框架的实现也是有共性的,互相也都在学习借鉴。全部要利用你以前之后的GUI开发经验,你会发如今各个框架中其实均可以找到类似的地方,若是能总结出通常性的规律,会极大帮助你理解新的框架。好比咱们都知道为了和用户交互,GUI编程一般采用的是事件驱动模型,那么你确定会去看Flutter的事件驱动模型是什么样的?咱们都知道16ms,那么确定有系统Vsync信号来驱动框架喽。GUI编程通常会有窗口(window)的概念,咱们是否是能够有目的的去找一下新框架的窗口在哪里。用户界面编程确定是逃不开界面元素的布局,绘制,光栅化。若是你已经有了这样的概念,那么剩下的只是去Flutter源码中去寻找具体实现了。有了这种通常性规律的指导,任何新GUI框架你均可以理解掌握。
作好以上准备之后,就能够进入源码的学习环节了。首先就是要找准学习框架源码的切入点。
通常来说,框架代码的切入点有两个,一个是咱们常见的是最上层的,也就是框架提供的那些API来入手,对于Flutter来说那就是StatefulWidget
,StatelessWidget
,State
,setState()
和runApp()
等等。另一个切入点就是框架最底层和engine交互的地方,也就是window
。这两个点也是框架的边界,入手的时候咱们能够自上而下,也能够自下而上,或者兼而有之。这个看本身的喜爱了。重要的是能找准框架的上界和下界。
框架是很复杂的,须要处理不少事情,因此其代码也会很是庞杂。咱们须要抓住主要流程,也就是我上面说的渲染流水线,而暂时忽略次要流程,例如点击事件的处理,辅助功能(Accessibility)等等,排除这些次要流程会使咱们专一于主要流程,从而减轻咱们的认知负担。在源代码中遇到touch,Accessibility等相关类,函数,代码可直接跳过,避免其带来干扰。这些次要流程能够待咱们搞清楚主要流程以后再回过头来熟悉,这样会顺畅不少。
Flutter框架源代码中除了主要功能相关的代码以外,还存在着不少和调试,异常处理等逻辑。特别是有大量的assert
断言语句。这些逻辑也是须要暂时剔除的以免干扰咱们的学习进程。每每一个函数在剔除了断言和异常处理等逻辑以后,关键代码可能只有几行甚至只有一行。
拿咱们熟悉的setState()
举例,完整代码以下(没必要细看,只须要知道代码量就好了),大概60多行的样子。
void setState(VoidCallback fn) {
assert(fn != null);
assert(() {
if (_debugLifecycleState == _StateLifecycle.defunct) {
throw FlutterError.fromParts(<DiagnosticsNode>[
ErrorSummary('setState() called after dispose(): $this'),
ErrorDescription(
'This error happens if you call setState() on a State object for a widget that '
'no longer appears in the widget tree (e.g., whose parent widget no longer '
'includes the widget in its build). This error can occur when code calls '
'setState() from a timer or an animation callback.'
),
ErrorHint(
'The preferred solution is '
'to cancel the timer or stop listening to the animation in the dispose() '
'callback. Another solution is to check the "mounted" property of this '
'object before calling setState() to ensure the object is still in the '
'tree.'
),
ErrorHint(
'This error might indicate a memory leak if setState() is being called '
'because another object is retaining a reference to this State object '
'after it has been removed from the tree. To avoid memory leaks, '
'consider breaking the reference to this object during dispose().'
),
]);
}
if (_debugLifecycleState == _StateLifecycle.created && !mounted) {
throw FlutterError.fromParts(<DiagnosticsNode>[
ErrorSummary('setState() called in constructor: $this'),
ErrorHint(
'This happens when you call setState() on a State object for a widget that '
'hasn\'t been inserted into the widget tree yet. It is not necessary to call '
'setState() in the constructor, since the state is already assumed to be dirty '
'when it is initially created.'
)
]);
}
return true;
}());
final dynamic result = fn() as dynamic;
assert(() {
if (result is Future) {
throw FlutterError.fromParts(<DiagnosticsNode>[
ErrorSummary('setState() callback argument returned a Future.'),
ErrorDescription(
'The setState() method on $this was called with a closure or method that '
'returned a Future. Maybe it is marked as "async".'
),
ErrorHint(
'Instead of performing asynchronous work inside a call to setState(), first '
'execute the work (without updating the widget state), and then synchronously '
'update the state inside a call to setState().'
)
]);
}
// We ignore other types of return values so that you can do things like:
// setState(() => x = 3);
return true;
}());
_element.markNeedsBuild();
}
复制代码
在剔除断言,调试代码,异常处理以后就只剩短短两行了。
void setState(VoidCallback fn) {
final dynamic result = fn() as dynamic;
_element.markNeedsBuild();
}
复制代码
在看源码的时候经过这样的操做能够排除干扰,快速掌握关键逻辑。
Flutter框架源代码中其实有一些套路在被广泛的使用,只要了解了这些套路,那么在看新的源码的时候会有似曾相识是的感受,其大体逻辑也能够被推测出来。能够指引咱们理解源码逻辑。例如,在渲染流水线的构建(build),布局(layout)和绘制(paint)阶段,都是类似的逻辑,首先把相应节点标记为脏(dirty)
Element.markNeedsBuild()
RenderObject.markNeedsLayout()
RenderObject.markNeedsPaint()
而后后续真正执行相应操做。若是搞清楚了构建流程的套路,那么在看布局,绘制的源码的时候就不会感受那么的陌生了。这样的例子还有不少,就不一一列举了。
Flutter框架也提供了不少的工具给开发者,你们在学习源码的的时候能够充分利用这些工具来帮助本身。好比Flutter inspector。可让你能清晰的看到element tree, renderObject tree。有助于理解它们之间的关系。
其次就是对于比较复杂的逻辑,要本身动手写一些demo app打上断点跑一跑,单步跟踪一下,也是很是有帮助的。
最后就是Flutter也提供一些调试用的Flag。例如如下这些标志位
debugPrintScheduleFrameStacks = true;
debugPrintBeginFrameBanner = true;
debugPrintEndFrameBanner = true;
debugPrintRebuildDirtyWidgets = true;
debugPrintBuildScope = true;
debugPrintScheduleBuildForStacks = true;
debugProfileBuildsEnabled = true;
debugPaintLayerBordersEnabled = true;
debugPrintMarkNeedsLayoutStacks = true;
debugPrintMarkNeedsPaintStacks = true;
复制代码
有选择的打开某些标志位再跑一下本身的程序,查看log输出,也有助于咱们理解Flutter框架。
俗话说好记性不如烂笔头。框架代码如此庞杂,若是咱们仅仅是在IDE里跳转来,跳转去。那么很快就会迷失方向,不知道本身身处何处。因此必要的记录是必须的。特别是有多个分支的状况,若是不作记录,顺着其中一个分支深刻下去,极可能就会忘记其余分支尚未照顾到。或者是看完一个分支再去看其余分支,也能把以前的分支干了些啥都给忘了。在后期有这些笔记也有助于对框架代码的总体理解。
最后就是必定要有耐心,不要中途放弃,也不要拖延,看了一部分剩下的过两个月再看的时候就全忘了。前功尽弃。遇到比较难以理解的复杂逻辑的时候要反复看,反复理解,强制本身去理解,强制本身去排除不正确的认知,把思路向框架开发者靠拢。没有翻不过去的山。
若是能坚持到这里,那么恭喜你,Flutter框架对你来说已经不是个陌生人了。剩下的,就是再作一些事情使大家更加熟悉。
在初步掌握了框架主流程以后,此时咱们能够回头再去看那些分支流程。继续丰富本身对框架的认识,同时也能够巩固相关知识。个人体会是学无止境,每次再回头看框架源码都会有新的收获,新的认识;再去看有关框架的文档,文章的时候也会有一些提高,好比那些地方说的对,比我本身的理解好,哪些地方可能有一些问题,和做者共同探讨一下都是很是好的。
更进一步,咱们能够多问一些为何,为何这个地方的逻辑是这样的?框架做者为何要这样设计?有没有更好,更高效的办法来实现一样的逻辑?带着这些问题去复习源码会给你带来更大的提升。
最后的最后呢。若是能把本身的收获写成文章分享出来那是“坠吼的”。写文章的好处太多了,我就很少说了。谁写谁知道。。。。。。
本文是应掘友要求写的学习Flutter源码的一点心得体会。其实里面不少方法,技巧可能并不局限于Flutter这个特定的框架,甚至不局限于代码。因为是我的的一些经验,可能有些东西说的也不是适合全部人,若是你们以为有不对的地方或者有更好的经验,也但愿大家能分享出来咱们互相学习,一同成长。