这样的实现有几个优势:

  • 提供了诬陷的扩展性。

  • Flutter可以直接合成所有的场景,而无需在Flutter与原生平台之间来回的切换,从而避免了明显的性能瓶颈。

  • 将应用的行为与操作系统的依赖解耦。

组成


Widget通常由更小的且用途单一的widgets组合而成,提供更强大的功能。

在设计的时候,相关的概念设计已尽可能地少量存在,而通过大量的内容进行填充。eg,Flutter在widgets层中使用了相同的概念(一个Widget)来表示屏幕上的绘制、布局(位置和大小)、用户交互、状态管理、主题、动画以及导航。在动画层,Animation和Tween这对概念组合,涵盖了大部分的设计空间。在渲染层,RenderObject用来描述布局、绘制、触摸判断以及可访问性。在这些场景中,最终对于包含的内容都很多:有数百个widgets和Render objects,以及数十种的动画和补间类型。

类的层次结构是有意的浅而广,以最大限度的增加可能的组合数量,重点放在小的,可组合的widget上,确保每个widget都能横好的完成一件事情。核心功能均被抽象,甚至像编剧和对齐这样的基础功能,都被实现为单独的组件,而不是内置于核心中。(这样的实现也与传统的API形成了对比,类似于边距这样的功能通常都内置在了每个组件的公共核心内,Flutter中的widget则不同。)因此,如果你需要讲一个widget居中,预期调整Align这样的属性,不如将他包裹在一个Center widget内。

Flutter中包含了边距,对齐,行,列和网格系列的widgets。这些布局类型的widgets自身没有视觉内容,而只用于控制其他的widgets的部分布局条件。Flutter也包含了以这种组合方法组成的实用性widgets。

例如,一个常用的widget Container,是由几个widget组合而成,包含了布局、绘制、定位和大小的功能。更具体地说,Container是由LimitedBox、ConstrainedBox、Align、Padding、DecoratedBox和Transform组合而成的,你也可以通过查看源码看到这些组合。Flutter 有一个典型的特征,即你可以深入到任意一个 widget,查看其源码。因此,你可以通过同样的方式组合其他的 widgets,也可以参考 Container 来创建其他的 widget,而不需要继承 Container 来实现自定义的效果。

构建widgets


先前提到,可以通过重写build()方法,返回一个新的元素树,来定义视觉展示。这棵树用更为具体的术语表示了widget在UI中的部分。例如,工具栏widget的build方法可能会返回水平布局,其中可能包含了一些文字,各种各样的按钮。根据需要,框架会递归请求每个widget进行构建,直到整棵树都被具体的可渲染的对象描述为止。然后框架会将可渲染的对象缝合在一起,组成可渲染的对象树。

Widget的build方法应该是没有副作用的。每当一个方法要求构建市,widget都应当能返回一个widget的元素树,与先签返回的widget也没有关联。框架会根据渲染对象树来确定哪些构建方法需要被调用,这是一响略显繁重的工作。

每个渲染帧,Flutter都可以根据变换的状态,调用build()方法重建部分UI。因此,保证build方法轻量且能够快速返回widget是非常关键的,繁重的计算工作应该通过一些异步的方法来完成,然后作为构建方法build的一部分存储。

尽管这样的实现看起来不够成熟,但是这样的自动对比方法非常有效,可以实现高性能的交互应用。同时,以这种方式设计的build方法,将重点放在widget组合的声明上,从而简化了代码,而不是以一种状态去更新另一种状态这样的复杂过程。

状态管理


那么,在众多的widget都持有状态的情况下,系统中的状态是如何被传递和管理的呢?

与其他类相同,你可以通过widget的构造函数来初始化数据,如此一来build()方法可以确保子widget使用其所需要的数据进行实例化:

@override

Widget build(BuildContext context) {

return ContentWidget(importantState);

}

然而,随着widget树层级的逐渐增加加深,依赖树结构上下传递状态信息会变得十分麻烦。这时,第三张类型的widget——InheritedWidget,提供了一种从共享的祖先节点获取数据的简易办法。你可以使用InheritedWidget创建包含状态的widget,该widget会将一个共同的祖先节点包裹在widget树中,如下:

现在,当ExamWidget或者GradeWIdget对象需要获取StudentState的数据时,可以直接使用以下方式:

final studentState = StudentState.of(context);

调用of(context)会根据当前构建的上下文(即当前的widge位置的句柄),并返回类型为StudentState的在树中距离最近的祖先节点。InheritedWidget同时也包含了updateShouldNotify()方法,Flutter会调用它来判断依赖了某个状态的widget是否需要更新重建。

InheritedWidget在Flutter中被大量用于共享状态,例如应用的视觉主题,包含了应用于整个应用的颜色和字体样式等属性。MaterialApp的build()方法会在构建市在树中插入一个主题,更生层次的widget便可以使用.of()方法来查找相关的主题数据,例如:

Container(

color: Theme.of(context).secondaryHeaderColor,

child: Text(

‘Text with a background color’,

style: Theme.of(context).textTheme.headline6,

),

);

类似的,以该方法实现的还有提供了路由页面的Navigator,提供了屏幕信息指标,包括方向,尺寸和高度的MediaQuery等等。

随着应用程序的不断迭代,更高级的状态管理方法变得更加有吸引力,它们可以减少有状态的widget的创建。许多Flutter应用使用了provider用于状态管理,它对InheritedWidget进行了进一步的包装。FLutter的分层架构也允许使用其他的实现来替换状态管理只UI的方案,例如flutter_hooks

渲染和布局

====================================================================

本节介绍Flutter的渲染机制,包括将widget层级结构转换成屏幕上绘制的实际像素的一系列步骤。

Flutter的渲染模型


你可能思考过:既然Flutter是一个跨平台的框架,那么它又是如何提供与原生平台框架相当的性能的呢?

让我们从Android原生应用的角度开始思考。当你在编写绘制的内容的时候,你需要调用Android框架的Java代码。Android的系统库提供了可以将自身绘制到Canvas对象的组件,接下来Android就可以使用由C/C++编写的Skia图形引擎,调用CPU和GPU完成在设备上的绘制。

跨平台框架都会在Android和IOS的UI底层库上创建一层抽象,该抽象层尝试抹平各个系统之间的差异。这时,应用程序的代码通常使用JavaScript等解释型语言来进行编写,这些代码会与基于Java的Android和基于OC的IOS进行交互,最终展示UI界面。所有流程都增加了显著的开销,在UI和应用逻辑有凡在的交互时更为如此。

相比之下,Flutter通过染过系统UI组件库,使用自己的widget内容集,消减了抽象层的开销。用于绘制Flutter图像内容的Dart代码被编译成机器码,并使用Skia进行渲染。Flutter同时也嵌入了自己的Skia副本作文引擎的一部分,让开发者能再设备未更新到最新系统时,也能跟进升级自己的应用,保证稳定性并提升性能。

从用户操作到GPU


对于Flutter渲染机制而言,首要原则就是简单快捷。Flutter为数据流向系统提供了直通的通道,如以下的流程图所示:

Render pipeline sequencing diagram

接下来让我们更加深入了解其中的一些阶段。

构建:从Widget到Element


首先观察以下的代码片段,它代表了一个简单的widget结构:

Container(

color: Colors.blue,

child: Row(

children: [

Image.network(‘https://www.example.com/1.png’),

const Text(‘A’),

],

),

);

当Flutter需要绘制这段代码时,框架会调用build()方法,返回一颗基于当前应用状态来绘制UI的widget子树。在这个过程中,build()方法可能会在必要时,根据状态引入新的widget。在上面的例子中,Container的color和child就是电信的例子。我们可以查看Container的源码,会发现当color属性不为空时,ColoredBox会被加入用于颜色布局。

if (color != null)

current = ColoredBox(color: color!, child: current);

与之对应的,Image和Text在构建过程中也会引入RawImage和RichText。如此一来,最终生成的widget结构比代码表示的层级更深,在该场景中如下图:

Render pipeline sequencing diagram

这就是为什么你在使用Dart DevTools的Flutter inspector调试widget树结构时,会发下实际的结构比你原本代码中的结构更深。

在构建阶段,Flutter会将代码中描述的widgets转化成对应的Element树,每一个Widget都有一个对应的Element。每一个Element代表了梳妆层次结构中特定位置的widget实例。目前有两种Element的基本类型:

  • ComponentElement,其他Element的宿主。

  • RenderObjectElement,参与布局或绘制阶段的Element。

Render pipeline sequencing diagram

RenderObjectElement是底层RenderObject与对应的widget之间的桥梁,我们晚点会介绍。

任何widget都可以通过其BuildContext引用到Element,它是该widget在树中的位置的句柄。类似于Theme.of(context)方法调用中的context,它作为build()方法的参数被传递。

由于widgets以及它上下节点的关系都是不可变的,因此,对widget树做任何操作(例如将Text(‘A’)修改成Text(‘B’))都会返回一个新的widget对象集合。但是这并不是意味着底层呈现的内容必须要重新构建。Element树每一帧之间都是持久化的,因此起着至关重要的性能作用,Flutter依靠该优势,实现类一种好似widget树被完全抛弃,而缓存了底层表示的机制。Flutter可以根据发生变化的widget,来重建需要重新配置的Element树的部分。

布局和渲染


很少有应用只绘制单个widget。因此,有效的排布widget的结构以及在渲染完成前决定每个Element的大小和位置,是所有UI框架的重点之一。

在渲染树中,每个节点的基类都是RenderObject,该基类为布局和绘制定义了一个抽象的模型。这是再平凡不过的事情:它并不总是一个固定大小,甚至不尊徐笛卡尔坐标系规律。每一个RenderObjectElement都了解其父节点的信息,对于其子节点,除了如何访问和获得他们的布局约束,并没有更多的信息。这样设计让RenderObject拥有高效的抽象能力,能够处理各种各样的使用场景。

在构建阶段,Flutter会为Element树中的每个RenderObjectElement创建或更新其对于的一个从RenderObject继承的对象。RenderObject实际上是原语:渲染文字的RenderParagraph、渲染图片的RenderImage以及在绘制子节点内容前应用变换的RenderTransform是更为上层的实现。

Differences between the widgets hierarchy and the element and render trees

大部分的Flutter widget是由一个继承了RenderBox的子类对象渲染的,他们呈现出的RenderObject会在二维迪卡空间中拥有固定的大小。RenderBox提供了盒子模型限制,为每个widget关联了渲染的最小和最大的宽度和高度。

在进行布局的时候,Flutter会议DFS(深度优先遍历)方式遍历渲染书,并将限制以自上而下的方式从父节点传递给子节点。子节点若要确定自己的大小,则必须遵循父节点传递的限制。子节点的响应方式是在父节点简历的约束内将大效益自下而上的方式传递给父节点。

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

img
img

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V:vip204888 备注Android获取(资料价值较高,非无偿)
img

总结

首先是感觉自己的基础还是不够吧,大厂好像都喜欢问这些底层原理。

另外一部分原因在于资料也还没有看完,一面时凭借那份资料考前突击恶补个几天居然也能轻松应对(在这里还是要感谢那份资料,真的牛),于是自我感觉良好,资料就没有怎么深究下去了。

之前的准备只涉及了Java、Android、计网、数据结构与算法这些方面,面对面试官对其他基础课程的考察显得捉襟见肘。

下一步还是要查漏补缺,进行针对性复习。

最后的最后,那套资料这次一定要全部看完,是真的太全面了,各个知识点都涵盖了,几乎我面试遇到的所有问题的知识点这里面都有!希望大家不要犯和我一样的错误呀!!!一定要看完!

居然也能轻松应对(在这里还是要感谢那份资料,真的牛),于是自我感觉良好,资料就没有怎么深究下去了。

之前的准备只涉及了Java、Android、计网、数据结构与算法这些方面,面对面试官对其他基础课程的考察显得捉襟见肘。

下一步还是要查漏补缺,进行针对性复习。

最后的最后,那套资料这次一定要全部看完,是真的太全面了,各个知识点都涵盖了,几乎我面试遇到的所有问题的知识点这里面都有!希望大家不要犯和我一样的错误呀!!!一定要看完!
[外链图片转存中…(img-GPMWPYrH-1711566427648)]

[外链图片转存中…(img-jZuCR1FB-1711566427649)]

[外链图片转存中…(img-kRYESqn0-1711566427649)]

本文已被CODING开源项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》收录

Logo

一起探索未来云端世界的核心,云原生技术专区带您领略创新、高效和可扩展的云计算解决方案,引领您在数字化时代的成功之路。

更多推荐