将以下资源本地化,打包到项目中
ps:最初canvaskit资源上传到阿里云,采用国内镜像资源加载。这次为了保险起见,也放入到本地。
好处:只要服务器正常能访问,这些资源文件就能够正常加载成功,最大程度上规避页面加载异常的风险!
最终项目中添加一些配置文件,如下图:
index.html
文件中更改canvaskit加载方式为本地
https://unpkg.com/canvaskit-wasm@0.35.0/bin/canvaskit.js
https://unpkg.com/canvaskit-wasm@0.35.0/bin/canvaskit.wasm
下载地址方式获取:F12 ->Network
https://fonts.gstatic.com/s/roboto/v20/KFOmCnqEu92Fr1Me5WZLCzYlKw.ttf
https://fonts.googleapis.com/css2?family=Noto+Sans+SC
下载的样式保存在本地,命名为 localfonts.css
更改CSS中引用的woff2字体文件,更改为本地路径
1 | curl -O https://fonts.gstatic.com/s/notosanssc/v26/k3kXo84MPvpLmixcA63oeALhLIiP-Q-87KaAaH7rzeAODp22mF0qmF4CSjmPC6A0Rg5g1igg1w.[1-120].woff2 |
下载的文件装入到woff2文件夹放在 web/canvaskit 路径下
执行flutter build web打包命令后生成build/web文件
由于打包后的文件资源引用还是url链接形式,故需要修改build/web目录下的main.dart.js
将https://fonts.gstatic.com/s/roboto/v20/KFOmCnqEu92Fr1Me5WZLCzYlKw.ttf
替换成canvaskit/KFOmCnqEu92Fr1Me5WZLCzYlKw.ttf
将https://fonts.googleapis.com/css2?family="+A.bF(o," ","+")
替换成canvaskit/localfonts.css
flutter_build.sh脚本自动打包以及打完包后修改build/web目录下的main.dart.js中资源引用路径替换成本地路径
1 | !/bin/zsh |
文/ Nayuta,CFUG 社区
状态管理一直是 Flutter 开发中一个火热的话题。谈到状态管理框架,社区也有诸如有以 Get、Provider 为代表的多种方案,它们有各自的优缺点。 面对这么多的选择,你可能会想:「我需要使用状态管理么?哪种框架更适合我?」 本文将从作者的实际开发经验出发,分析状态管理解决的问题以及思路,希望能帮助你做出选择。
首先,为什么需要状态管理? 根据笔者的经验,这是因为 Flutter 基于 声明式 构建 UI , 使用状态管理的目的之一就是解决「声明式」开发带来的问题。
「声明式」开发是一种区别于传原生的方式,所以我们没有在原生开发中听到过状态管理,那如何理解「声明式」开发呢?
以最经典的的计数器例子分析:
如上图所示:点击右下角按钮,显示的文本数字加一。 Android 中可以这么实现:当右下角按钮点中时, 拿到 TextView
的对象,手动设置其展示的文本。
实现代码如下:
1 | // 一、定义展示的内容 |
而在 Flutter 中,我们只需要使变量增加之后调用 setState((){})
即可。setState
会刷新整个页面,使得中间展示的值进行变更。
1 | // 一、声明变量 |
可以发现,Flutter 中只对 _counter
属性进行了修改,并没有对 Text 组件进行任何的操作,整个界面随着状态的改变而改变。
所以在 Flutter 中有这么一种说法: UI = f(state):
上面的例子中,状态 (state) 就是 _counter
的值,调用 setState
驱动 f
build 方法生成新的 UI。
那么,声明式有哪些优势,并带来了哪些问题呢?
优势: 让开发者摆脱组件的繁琐控制,聚焦于状态处理
习惯 Flutter 开发之后,回到原生平台开发,你会发现当多个组件之间相互关联时,对于 View 的控制非常麻烦。
而在 Flutter 中我们只需要处理好状态即可 (复杂度转移到了状态 -> UI 的映射,也就是 Widget 的构建)。包括 Jetpack Compose、Swift 等技术的最新发展,也是在朝着「声明式」的方向演进。
声明式开发带来的问题
没有使用状态管理,直接「声明式」开发的时候,遇到的问题总结有三个:
接下来,我先带领大家逐个了解这些问题,下一章向大家详细描述状态管理框架如何解决这些问题。
1) 逻辑和页面 UI 耦合,导致无法复用/单元测试、修改混乱等
一开始业务不复杂的时候,所有的代码都直接写到 widget 中,随着业务迭代, 文件越来越大,其他开发者很难直观地明白里面的业务逻辑。 并且一些通用逻辑,例如网络请求状态的处理、分页等,在不同的页面来回粘贴。
这个问题在原生上同样存在,后面也衍生了诸如 MVP 设计模式的思路去解决。
2) 难以跨组件 (跨页面) 访问数据
第二点在于跨组件交互,比如在 Widget 结构中, 一个子组件想要展示父组件中的 name
字段, 可能需要层层进行传递。
又或者是要在两个页面之间共享筛选数据, 并没有一个很优雅的机制去解决这种跨页面的数据访问。
3) 无法轻松的控制刷新范围 (页面 setState 的变化会导致全局页面的变化)
最后一个问题也是上面提到的优点,很多场景我们只是部分状态的修改,例如按钮的颜色。 但是整个页面的 setState
会使得其他不需要变化的地方也进行重建, 带来不必要的开销。
Flutter 中状态管理框架的核心在于这三个问题的解决思路, 下面一起看看 Provider、Get 是如何解决的:
传统的原生开发同样存在这个问题,Activity 文件也可能随着迭代变得难以维护, 这个问题可以通过 MVP 模式进行解耦。
简单来说就是将 View 中的逻辑代码抽离到 Presenter 层, View 只负责视图的构建。
这也是 Flutter 中几乎所有状态管理框架的解决思路, 上图的 Presenter 你可以认为是 Get 中的 GetController
、 Provider 中的 ChangeNotifier
或者 Bloc 中的 Bloc
。 值得一提的是,具体做法上 Flutter 和原生 MVP 框架有所不同。
我们知道在经典 MVP 模式中, 一般 View 和 Presenter 以接口定义自身行为 (action), 相互持有接口进行调用 。
但 Flutter 中不太适合这么做, 从 Presenter → View 关系上 View 在 Flutter 中对应 Widget, 但在 Flutter 中 Widget 只是用户声明 UI 的配置, 直接控制 Widget 实例并不是好的做法。
而在从 View → Presenter 的关系上, Widget 可以确实可以直接持有 Presenter, 但是这样又会带来难以数据通信的问题。
这一点不同状态管理框架的解决思路不一样,从实现上他们可以分为两大类:
1) 通过 Flutter 树机制处理 V → P 的获取
1 | abstract class Element implements BuildContext { |
Element 实现了父类 BuildContext 中操作树结构的方法
我们知道 Flutter 中存在三棵树,Widget、Element 和 RenderObject。 所谓的 Widget 树其实只是我们描述组件嵌套关系的一种说法,是一种虚拟的结构。 但 Element 和 RenderObject 在运行时实际存在, 可以看到 Element 组件中包含了 _parent
属性,存放其父节点。 而它实现了 BuildContext
接口,包含了诸多对于树结构操作的方法, 例如 findAncestorStateOfType
,向上查找父节点; visitChildElements
遍历子节点。
在一开始的例子中,我们可以通过 context.findAncestorStateOfType
一层一层地向上查找到需要的 Element 对象, 获取 Widget 或者 State 后即可取出需要的变量。
provider 也是借助了这样的机制,完成了 View -> Presenter 的获取。 通过 Provider.of
获取顶层 Provider 组件中的 Present 对象。 显然,所有 Provider 以下的 Widget 节点, 都可以通过自身的 context 访问到 Provider 中的 Presenter, 很好地解决了跨组件的通信问题。
2) 通过依赖注入的方式解决 V → P
树机制很不错,但依赖于 context,这一点有时很让人抓狂。 我们知道 Dart 是一种单线程的模型, 所以不存在多线程下对于对象访问的竞态问题。 基于此 Get 借助一个全局单例的 Map 存储对象。 通过依赖注入的方式,实现了对 Presenter 层的获取。 这样在任意的类中都可以获取到 Presenter。
这个 Map 对应的 key 是 runtimeType
+ tag
, 其中 tag 是可选参数,而 value 对应 Object
, 也就是说我们可以存入任何类型的对象,并且在任意位置获取。
这个问题其实和上一部分的思考基本类似,所以我们可以总结一下两种方案特点:
Provider
Get
最后就是我们提到的高层级 setState
引起不必要刷新的问题, Flutter 通过采用观察者模式解决,其关键在于两步:
系统也提供了 ValueNotifier
等组件的实现:
1 | /// 声明可能变化的数据 |
了解到最基础的观察者模式后,看看不同框架中提供的组件:
比如 Provider 中提供了 ChangeNotifierProvider
:
1 | class Counter extend ChangeNotifier { |
还是之前计数器的例子,这里 Counter
继承了 ChangeNotifier
通过顶层的 Provider 进行存储。 子节点通过 Consumer 即可获取实例, 调用了 increment
方法之后,只有对应的 Text 组件进行变化。
同样的功能,在 Get 中, 只需要提前调用 Get.put
方法存储 Counter
对象, 为 GetBuilder
组件指定 Counter
作为泛型。 因为 Get 基于单例,所以 GetBuilder
可以直接通过泛型获取到存入的对象, 并在 builder 方法中暴露。这样 Counter
便与组件建立了监听关系, 之后 Counter
的变动,只会驱动以它作为泛型的 GetBuilder
组件更新。
1 | class Counter extends GetxController { |
在使用这些框架过程中,可能会遇到以下的问题:
1 | class MyApp extends StatelessWidget { |
如代码所示,当我们直接将 Provider 与组件嵌套于同一层级时, 这时代码中的 Provider.of(context)
运行时抛出 ProviderNotFoundException
。 因为此处我们使用的 context 来自于 MyApp, 但 Provider 的 element 节点位于 MyApp 的下方, 所以 Provider.of(context)
无法获取到 Provider 节点。 这个问题可以有两种改法,如下方代码所示:
改法 1: 通过嵌套 Builder 组件,使用子节点的 context 访问:
1 | class MyApp extends StatelessWidget { |
改法 2: 将 Provider 提至顶层:
1 | void main() { |
正如前面提到 Get 通过全局单例,默认以 runtimeType
为 key 进行对象的存储, 部分场景可能获取到的对象不符合预期,例如商品详情页之间跳转。 由于不同的详情页实例对应的是同一 Class,即 runtimeType
相同。 如果不添加 tag 参数,在某个页面调用 Get.find
会获取到其它页面已经存储过的对象。 同时 Get 中一定要注意考虑到对象的回收,不然很有可能引起内存泄漏。 要么手动在页面 dispose
的时候做 delete
操作, 要么完全使用 Get 中提供的组件,例如 GetBuilder
, 它会在 dispose
中释放。
GetBuilder
中在 dispose
阶段进行回收:
1 |
|
通过本文,我向大家介绍了状态管理的必要性、它解决了 Flutter 开发中的哪些问题以及是如何解决的, 与此同时,我也为大家总结了在实践中常见的问题等,看到这里你可能还会有些疑惑,到底是否需要使用状态管理?
在我看来,框架是为了解决问题而存在。所以这取决于你是否也在经历一开始提出的那些问题。 如果有,那么你可以尝试使用状态管理解决;如果没有,则没必要过度设计,为了使用而使用。
其次,如果使用状态管理,那么 Get 和 Provider 哪个更好?
这两个框架各有优缺点,我认为如果你或者你的团队刚接触 Flutter, 使用 Provider 能帮助你们更快理解 Flutter 的核心机制。 而如果已经对 Flutter 的原理有了解,Get 丰富的功能和简洁的 API, 则能帮助你很好地提高开发效率。
]]>如果想在对应的 APP 的生命周期事件中做相应的处理,比如 APP 从后台进入前台、从前台退到后台,或是在 UI 绘制完后做一些处理,则可以应用 WidgetsBindingObserver 类来实现。
WidgetsBindingObserver 中的回调方法
1 | // Accessibility 相关特性回调 |
要使用以上回调方法,只需通过给 WidgetsBindingObserver 单例对象设置监听器即可监听相关回调方法
didChangeAppLifecycleState 回调方法中,有一个参数类型为 AppLifecycleState 的枚举类,这个枚举类是 Flutter 对 App 生命周期状态的封装,常用的状态包括 inactive、paused、resumed
1 | class AppLifecycleReactor extends StatefulWidget { |
可以试着切换下前后台,观察下控制台输出的 App 状态
从前台退到后台,控制台打印的 App 生命周期变化如下:
AppLifecycleState.resumed->AppLifecycleState.inactive->AppLifecycleState.paused
从后台切换回前台,控制台打印的 App 生命周期变化如下:
AppLifecycleState.paused->AppLifecycleState.inactive->AppLifecycleState.resumed
有时除了需要监听 App 的生命周期回调外,还需要在组件完成渲染后做一些与显示相关的其他处理,比如切换线程等,此时可以使用 WidgetsBinding 来实现
WidgetsBinding 提供了单次 Frame 绘制回调及实时 Frame 绘制回调两种机制
单次 Frame 绘制回调:通过 addPostFrameCallback 实现。在当前 Frame 绘制完后进行回调,且只会回调一次,如果需要多次回调则需设置多次
1 | WidgetsBindingObserver.instance.addPostFrameCallback((_){ |
实时 Frame 绘制回调:通过 addPersistentFrameCallback 实现。在每次绘制 Frame 结束后进行回调
1 | WidgetsBindingObserver.instance.addPersistentFrameCallback((_){ |
1 | 查看版本: flutter --version |
例如-> git reset –hard 4cc385b4b84ac2f816d939a49ea1f328c4e0b48e
查看版本-> flutter doctor 或者 flutter –version
]]>Flutter的诞生是为了彻底改变应用程序开发方式:将Web的迭代开发模式与硬件加速图形渲染和像素级控制相结合,这些以前可是游戏的专利,而现在它被来进行应用开发。自从Flutter 1.0测试版发布以来,谷歌对其不断地进行优化,让Flutter的功能日趋完善,比如添加了新的框架功能和小部件、与底层平台进行更加深入的集成、提供了丰富的软件包库,并且在性能和工具上也做了许多的改进。
从1.0到3.0版本,Flutter被越来越多的开发者使用来构建应用程序。据谷歌最新用户研究报告显示:
实现“大一统”,一个Flutter,横跨iOS、Android、Web、Windows、macOS、Linux六大平台
Flutter 3.0为开发者提供了一种使用Dart编程语言为六大面向消费者的主流平台(iOS、Android、Web、Windows、macOS、Linux)目标编写应用程序的方法。基于Flutter 3,开发者能拥有从单个代码库为六个平台构建应用程序的体验,这将使其生产力大大提高。
不同于之前的版本,Flutter 3新增了对macOS和Linux应用程序的稳定支持。添加新平台的支持并不像我们想象中那么简单,其需要的不仅仅是渲染像素,还需要做很多其他的工作,比如新的输入和交互模型、编译和生成支持、可访问性和国际化以及特定于平台的集成等。Flutter不仅让用户能灵活地充分利用底层操作系统,同时能够根据需要共享尽可能多的UI。
具体来看,在macOS上,Flutter3可以通过通用二进制构建方式支持英特尔和Apple Silicon,允许应用打包在这两种架构上本机运行的可执行文件。同时,由于Dart对Apple Silicon的支持,编译速度也将更快。在Linux上,Canonical和谷歌合作提供了一个高度集成且在同类中最佳的开发选项。
另外,Flutter 3 还改进了许多基础功能,包括性能改进、Material You 支持和生产力更新。
除了上述所提到的内容,在新版本中,Flutter可以在苹果芯片上进行原生开发。虽然Flutter自发布以来,一直与搭载M1芯片的苹果设备兼容,但Flutter现在充分利用了Dart对Apple芯片的支持,可以在M1驱动的设备上实现更快的编译,并支持macOS应用程序的通用二进制文件。
另外,随着谷歌的Material Design 3在Flutter 3这个版本中基本已经完成,开发人员能够利用适应性强的跨平台设计系统,该系统提供动态配色方案和更新的视觉组件:
Flutter是由Dart驱动的,Dart是一种用于多平台开发的高生产力、可移植的语言。谷歌在Dart中添加了一些新的语言功能。这些新的语言功能带来的好处颇多,比如能够减少模板、提高可读性、提供实验性的RISC-V支持,以及升级的linter和新的文档等。在专用的博客中可以获得关于Dart 2.17所有新更改的细节,可通过链接查看https://medium.com/dartlang
Firebase和Flutter深度集成
当然,构建应用的不仅仅是UI框架。一套全面的工具在构建、开发和操作应用中也是必不可少的,比如身份验证、数据储存、云功能和设备测试等服务。而包括Sentry、AppWrite和AWS Amplify在内的很多服务都支持Flutter。
谷歌提供的应用服务Firebase使用率很高,在SlashData的开发者基准测试中,有62%的Flutter开发者表示会在开发应用程序时使用Firebase。因此,在以前的版本中,谷歌一直致力于将Flutter与Firebase更好地结合,以创造一流的的Flutter集成。为此,谷歌将Flutter中的Firebase插件提升到1.0(用于添加更好的文档和工具),以及提供像FlutterFire UI这样的新小部件(为开发人员提供可重用的UI用于身份验证和配置文件屏幕)。
今天,谷歌宣布将Flutter和Firebase的集成作为Firebase产品的核心,正在将源代码和文档移动到Firebase主存储库和站点中,而且谷歌有可能与Android和iOS同步发展Firebase对Flutter的支持。
此外,为了使用Crashlytics(Firebase广受欢迎的实时崩溃报告解决服务)谷歌还进行了以下重大改进。
Flutter休闲游戏工具包
对于大多数开发者来说,Flutter只是一个应用程序框架。但是,在休闲游戏开发领域也有Flutter的一番天地。围绕休闲游戏开发的社区也在不断壮大,很多开发者利用Flutter提供的硬件加速图形支持和Flame等开源游戏引擎。
为了让休闲游戏开发者能够更能更容易上手,在今天的I/O大会上,谷歌宣布发布Flutter Casual Games Toolkit,它不仅提供了一个模板和最佳实践的入门工具包,还能带来广告和云服务的良好体验。
虽然Flutter并不是为高强度的3D动作游戏而生的,但是有跨度是常有的事,即使是一些游戏也转向Flutter的非游戏UI,比如像PUBG Mobile这样拥有数亿用户的流行游戏。那么谷歌能把技术发展到什么程度呢?为了得到这个问题的答案,谷歌创建了一个有趣的弹球游戏(由Firebase和Flutter的网络支持提供支持)。
I/O弹球游戏中设计了一个带有四个吉祥物(Flutter的Dash、Firebase的Sparky、Android机器人和Chrome的恐龙)的定制桌子。玩家能在游戏中与别人一教高下,谷歌通过这种方式来展示Flutter多功能性,这很有趣。
Snearh认为,游戏开发和企业开发对性能和用户体验有着共同的追求,而Flutter团队一直在努力解决界面卡顿等问题,最终让框架能够更适用于各种类型的应用程序。
「一套代码,到处运行」,从 Flutter 3 开始
作为一款开源工具,Flutter不仅仅是谷歌的框架,也是一个业界开发者都能使用的产品。无论是通过贡献新的代码或文档、创建赋予核心框架新的超能力的软件包、编写指导他人的书籍和培训课程,还是帮助组织活动和用户组等等,人人都可以参与其中。Flutter 3的发布大大地提高了开发者的效率,也终让大家都实现了「一套代码,到处运行」的梦想。
原文地址:https://medium.com/flutter/introducing-flutter-3-5eb69151622f
]]>Flutter 在目前跨平台方案中有更好的平台一致性以及更优的体验。但对于本身已有成熟的业务代码的项目来说,更多的是采用混合栈的方式,在不变更原有 App 业务的基础上,将 Flutter 能力扩展为子模块进行接入和开发。这样并不影响原有的业务和原生能力,又可以结合业务需求进行技术选择。
标准的Flutter App工程,包含标准的Dart层与Native平台层
Flutter组件工程,仅包含Dart层实现,Native平台层子工程为通过Flutter自动生成的隐藏工程
Flutter平台插件工程,包含Dart层与Native平台层的实现
Flutter纯Dart插件工程,仅包含Dart层的实现,往往定义一些公共Widget
Flutter工程之间的依赖管理是通过Pub来管理的,依赖的产物是直接源码依赖,这种依赖方式和IOS中的Pod有点像,都可以进行依赖库版本号的区间限定与Git远程依赖等,其中具体声明依赖是在pubspec.yaml文件中,其中的依赖编写是基于YAML语法,YAML是一个专门用来编写文件配置的语言。
声明依赖后,通过运行flutter packages get命名,会从远程或本地拉取对应的依赖,同时会生成pubspec.lock文件,这个文件和IOS中的Podfile.lock极其相似,会在本地锁定当前依赖的库以及对应版本号,只有当执行flutter packages upgrade时,这时才会更新。
上述说的如果我们要利用Flutter来开发我们现有Native工程中的一个模块或功能,肯定得不能改变Native的工程结构以及不影响现有的开发流程,那么,以何种方式进行混合开发呢?
Flutter混合开发模式一般有两种方式:
Flutter module创建方式一般有两种:
a、通过命令来创建
flutter create -t module –org com.vhall.module vhall_flutter_module
1 | Creating project vhall_flutter_module... |
b、使用 As 创建 Flutter Module
在 As 中选择 File->New->New Flutter Project,选择 Flutter Module 创建 Flutter Module 子项目
a、以aar的方式集成到现有Android项目中
创建好 Flutter Module 之后需要将其编译成 aar 的形式,可以通过如下命令进行 aar 的编译:
cd vhall_flutter_module flutter build aar
在 Android 中也可以通过 As 工具来编译 aar,选择 Build->Flutter->Build AAR 来进行 aar 的编译。
然后根据提示在主项目工程的 build.grade 文件中进行相关配置,参考如下:
1 |
|
优点:
b、以 Flutet module 的方式集成到现有 Android 项目中
在 setting.gradle 文件中配置 flutter module 如下:
1 | setBinding(new Binding([gradle: this])) |
在 app 项目的 build.gradle 依赖 flutter module 模块
1 | dependencies { |
缺点:
Android
在Android中本地依赖方式为:
对于Android的本地依赖,主要是由include_flutter.groovy和flutter.gradle这两个脚本负责Flutter的本地依赖和产物构建。
a、include_flutter.groovy
在settings.gradle中注入时,分别绑定了当前执行Gradle的上下文环境与执行include_flutter.groovy脚本,该脚本只做了下面三件事:
其中.flutter-plugins文件,是根据当前依赖自动生成的,里面包含了当前Flutter工程所依赖(直接依赖和传递依赖)的Flutter子工程与绝对路径的K-V关系,子工程可能是一个Flutter Plugin或者是一个Flutter Package。
b、flutter.gradle
该脚本位于Flutter SDK中,内容看起来很长,其实主要做了下面三件事:
有了上述三步,则直接在Native工程中运行构建即可自动构建Flutter工程中的代码并自动拷贝产物到Native中
IOS
在IOS中本地依赖方式为:
对于IOS的本地依赖,主要是由podhelper.rb和xcode_backend.sh这两个脚本负责Flutter的Pod本地依赖和产物构建
a、podhelper.rb
因Podfile是通过ruby语言写的,所以该脚本也是ruby脚本,该脚本在pod install/update时主要做了三件事:
上述事情即可保证Flutter工程以及传递依赖的都通过pod本地依赖进Native工程了,接下来就是构建了
b、xcode_backend.sh
该Shell脚本位于Flutter SDK中,该脚本主要就做了两件事:
上述两个静态库*.framework是拷贝到${BUILT_PRODUCTS_DIR}”/“${PRODUCT_NAME}”.app/Frameworks”目录下
flutter_assets拷贝到${BUILT_PRODUCTS_DIR}”/“${PRODUCT_NAME}”.app”目录下
在XCode工程中,对应的是在${AppName}/Products/${AppName}.app
flutter 依赖提供了 FlutterActivity 来直接加载 flutter 页面,我们只需要在清单文件中配置该 Activity :
(通常我们会创建一个 Activity 继承 FlutterActivity)
1 | <activity |
三种打开flutter页面的方式:
1)普通跳转:
1 | myButton.setOnClickListener(new View.OnClickListener() { |
2)设置路由的方式跳转:
1 | myButton.setOnClickListener(new View.OnClickListener() { |
上述代码会在内部创建自己的 FlutterEngine 实例,每个 FlutterActivity 都创建自己的 FlutterEngine,这意味着启动一个标准的 FlutterActivity 会在界面可见时出现一短暂的延迟,可以选择使用预缓存的 FlutterEngine 来减小其延迟,实际上在内部会先检查是否存在预缓存的 FlutterEngine,如果存在则使用该 FlutterEngine,否则继续使用非预缓存的 FlutterEngine。
3)缓存 Flutter 引擎方式跳转:
1 | public class MyApplication extends Application { |
Platform Channel为Dart和平台之间提供了相互通信的机制,将Flutter、Android、iOS连接起来。
在移动H5开发中,webview自身提供的功能往往不够用,为了解决这个问题,引入了jsbridge,即web与native之间进行数据交互的一种方法,可以方便的将native的功能扩展给webview使用,从而可以快速开发。在Flutter中,也存在和jsbridge一样的用法,那就是Platform Channel,我们可以通过Platform Channel,将Flutter和Native方便的连接在一起,架构图如下:
在Channel中
Flutter定义了三种不同类型的Platform Channel用于Flutter与Host App平台进行通信,它们分别
其构造方法都需指定一个通道标识、解编码器以及 BinaryMessenger,BinaryMessenger 是一个 Flutter 与平台的通信工具,用来传递二进制数据、设置对应的消息处理器等。
解编码器有两种分别是 MethodCodec 和 MessageCodec,前者对应方法后者对应消息,BasicMessageChannel 使用的是 MessageCodec,MethodChannel 和 EventChannel 使用的是 MethodCodec。
Platform Channel 提供不同的消息解码机制,如 StandardMessageCodec 提供基本数据类型的解编码、JSONMessageCodec 支持 Json 的解编码等,在平台之间通信时都会自动转换,各平台数据类型对照如下:
前面讲了Flutter和Native的混合开发模式,Flutter作为Native工程的一个Module存在,这样可以有效的将Flutter和Native进行物理隔离,但随着Flutter承载的业务越来越多,与Native交互的接口变的越来越多,带来了很多管理问题,因此我们迫切需要采用新的开发模式,即Flutter的组件化开发方案。
采用组件化开发Flutter,将会有如下的优势:
组件划分,通过Flutter Module作为所有通过Flutter实现的模块或功能的聚合入口,通过它进行Flutter层到Native层的双向关联。而Flutter开发代码写在哪里呢?当然可以直接写在Flutter Module中,这没问题,而如果后续开发了多个模块、组件,我们的Dart代码总不可能全部写在Flutter Module中lib/吧,如果在lib/目录下再建立子目录进行模块区分,这不失为一种最简单的方式,不过这会带来一些问题,所有模块共用一个远程Git地址,首先在组件开发隔离上完全耦合了,其次各个模块组件没有单独的版本号或Tag,且后续模块组件的增多,带来更多的测试回归成本。
正确的组件化方式为一个组件有一个独立的远程Git地址管理,这样各个组件在发正式版时都有一个版本号和Tag,且在各个组件开发上完全隔离,后续组件的增多不影响其它组件,某个组件新增需求而不需回归其它组件,带来更低的测试成本。
前面提到Flutter Plugin可以有对应Dart层代码与平台层的实现,所以可以这样设计,一个组件对应一个Flutter Plugin,一个Flutter Plugin为一个完整的Flutter工程,有独立的Git地址,而这些组件之间不能互相依赖,保持零耦合,所以这些组件都在业务层,可以叫做业务组件,这些业务组件之间的通信和公共服务可以再划分一层基础层,可以叫做基础组件,所有业务组件依赖基础层,而Flutter Module作为聚合层依赖于所有Flutter组件,这些Flutter工程之间的依赖正是通过Pub依赖进行管理的。
所以,综合上述,整体的组件化架构可以设计为:
对于上面的基础组件比如还可以进行更细粒度的划分,不过不建议划分太多,对于与Native平台层的通信,每个业务组件对应一个Channel,当然内部还可以进行更细粒度的Channel进行划分,这个Channel主要是负责Native层服务的提供,让Flutter层消费。而对于Native层调用Flutter层的Api,应该尽可能少,需要调也只有出现一些值回调时。
因为Flutter的出现最本质的就是一次开发两端运行,而如果有太多这种依赖于平台层的实现,反而出现违背了,最后只是UI写了一份而已。对于平台层的实现也要尽量保持一个原则,即:
尽量让Native平台层成为服务层,让Flutter层成为消费层调用Native层的服务,即Dart调用Native的Api,这样当两端开发人员编写好一致基础的服务接口后,Flutter的开发人员即可平滑使用和开发。
对于现有工程使用Flutter进行混合开发,坑点还是有的,比如性能、页面栈管理等方面,加上目前Flutter上一些基础库不成熟,对于项目内的重要页面以及动态化强度比较高的页面,目前还是不建议使用Flutter进行开发,如果要使用也须做好降级方案,相反可以使用稍微轻量级点的页面,且在设计时对于Flutter与Native层的通信,应该让Flutter作为消费层消费Native层提供的服务,Native端应做尽量少的改动等等。与纯原生开发或纯 Flutter 开发相比,混合开发由于需要打通原生和 Flutter 的数据和服务,需要有大量桥接实现,各个模块互相协作也需要考虑各种异常或降级的情况。
参考:
将 Flutter module 集成到 Android 项目 https://flutter.cn/docs/development/add-to-app/android/project-setup
将 Flutter module 集成到 iOS 项目 https://flutter.cn/docs/development/add-to-app/ios/project-setup
在 Android 应用中添加 Flutter 页面https://flutter.cn/docs/development/add-to-app/android/add-flutter-screen
在 iOS 应用中添加 Flutter 页面 https://flutter.cn/docs/development/add-to-app/ios/add-flutter-screen
Add-to-App Samples https://github.com/flutter/samples/blob/beface247a/add_to_app/README.md
这算是一个非常好的消息了,之前在实际应用开发中,因为模拟器缺少对 arm 的支持,基本上都使用真机进行开发与调试。
随着 Android 11预览版的发布
Android 11 系统映像能够在不影响整个系统的前提下,直接将 ARM 指令转换成 x86 指令。开发者无需搭建高负载的 ARM 环境即可执行 ARM 二进制文件并进行测试。
携程技术
携程Android 10适配踩坑指南
新版本适配一直是 Android 开发者的痛楚之一,但是这件事如果长期不升就会越来越困难。加上现在应用市场比较强势,所以保持一个较高的版本的适配是有必要的。
携程旅行分享了他们从API 26 到 29 的适配经验,大家可以参考一波。
全网最详!暗黑模式在 Trip.com App 的实践
暗黑模式也最近的一个非常热门的话题,甚至微信支持暗黑模式都成为热点了。如果你对这方面感兴趣,或者刚好有暗黑实践的需求,那么可以看这一篇由携程 UED 团队+研发团队撰写的文章。
我个人其实一直没有体验过暗黑模式,毕竟没适配的 app 太多了,所以我不认为把手机切换为暗黑模式有很好的的体验…
Google 开发者
在 Android 开发中使用协程 | 背景介绍
可以看一下官方的文章了解下协程的一些背景知识,用来解决什么问题,如何解决,以及性能相关的一些知识。
字节跳动技术团队
抖音包大小优化-资源优化
可以看下抖音团队为了减少 apk 的体积,针对资源这一方面做了哪些极致的事情。
另外 apk 体积优化也可以看下 jsonchao 的文章:
吹爆系列:深入探索Android包体积优化
百度 APP 技术
Gradle 与 Android 构建入门
写了一篇 Gradle 构建入门的文章,其实很多同学都非常害怕 Gradle,这篇文章会给大家解释为什么需要 Gradle,以及 Gradle 相关的一些基础知识,帮你更好的了解相关知识,比较轻松,可以一看。
西瓜技术团队
AwCookieManager.nativeGetCookie crash 排查
分享了西瓜的一个CookieManager.getCookie(String url) 过程中的 native crash,分析过程较为复杂,不过给出了解决方案,解决方案涉及到 hook,西瓜用了自研的方案,开源的 lancet 应该也能做到,这篇文章也会提到 lancet。
历史分享(点击可直接访问):
https://github.com/yangkun19921001/Blog
比较适合系统性的复习,而且作者应该比较精通音视频,我知道很多同学对音视频很好奇,苦于没有什么入门的书籍等,不妨看下这位作者系列文章。
这是一个国外开发者维护的仓库,所以问题以及相关问题解答的博客都是外文。
https://github.com/MindorksOpenSource/android-interview-questions
上述问题在我们看来不一定能作为面试题,但是有些问题还是可以用来差缺补漏的,也能帮助大家发现一些写的比较好的外文博客。
https://github.com/eleme/lancet
这个库非常久了哈,饿了么对外开源的,但是好像开发者关注度并不高。
实际上还是非常好用的,我们传统意义 hook 的想法,都是换掉某个实现。
比如 A 类,有个 b 方法,我们需要修改 b方法返回值,我们一般会考虑 hook 一些对象,由这些对象导致 b 方法的执行返回逻辑发生改变,但是这种hook 并不是那么容易找到突破口。
而 lancet 的概念就很有意思了,它不修改 b方法,他修改所有调用 b 方法的地方,把对b 方法的调用hook 到你准备好的方法中,所以你可以随意的控制返回值,当然这种 hook 肯定是基于编译期修改字节码的,优势就是一定能换到,只要是参与编译的代码。
我说的可能大家不太容易理解,还是建议大家了解下这个库的原理,确实是 hook 的一个非常好的新思路。
见到一个库是这样的:
https://github.com/m-zylab/SketchyComponent
主要是手绘风,相信大家根据之前的灰白化的文章:
App黑白化实现探索,有一行代码实现的方案吗?
都知道我们可以基于换肤的方案把一些控件换成我们自己的,那么假设我们可以提供手绘风格的:TextView,Button…等,是不是有可能可以将一个 app 瞬间变成手绘风格?
appcomapt 1.1.0 版本在 android 5.0,5.1的设备上可能会造成 webview 崩溃
这是周六的时候一个朋友遇到的问题,查了下发现的。
这是个官方bug:
https://issuetracker.google.com/issues/141132133
一些解决方案:
https://stackoverflow.com/questions/41025200/android-view-inflateexception-error-inflating-class-android-webkit-webview/49024931#49024931
如果你升级1.1.0要谨慎。
好了,祝大家元气满满!
]]>大家可以在 B 站看到所有的内容:
https://space.bilibili.com/64169458/channel/detail?cid=91608
有个值得关注的是,官方回复了一些 Android 开发的问题:
一些新技术都有涉及,比如 ViewPager2 正式发布,关于 Camerax,Camera2,Camera1 之间如何选择,以及Kotlin,Jetpack Compose 相关问题。
我当时比较好奇的一个问题是这个:
挺奇怪的,难道官方 app 会考虑只用一个 Activity 么?
详细的大家直接看这里:
Android 开发者峰会 2019 常见问题解答
https://github.com/baiduapp-tec/ELinkageScroll
如果大家每篇推文都看的话,这个库我上次已经专门介绍过啦。
这个效果使用非常广,基本所有的资讯类 app 都有,感谢百度开源。
历史类似文章:
大厂的文章详情页 WebView与 RecyclerView如何连贯滑动的?
另外感谢「this 蜗牛」这位朋友留言说道 QMUI也有这个效果,还后台给我发了个截图,我也给大家补充上,大家可以一起参考。
https://github.com/Tencent/QMUI_Android
之前的系列我也给大家推荐过 QMUI,这样的大厂出的类库还是可以关注下,尤其针对个人开发者,在快速迭代自己项目的时候还是很有用的。
百度 App 技术
一种简单优雅的TextView行间距适配方案
以前用 TextView就怕UI 跟我们纠结行高,因为在 Android 里面没有一个非常明确的 lineHeight 概念,百度这边分享了自己的解决方案,还是学到了一些新知识的,文章中提到的LineHeightSpan我之前就不知道,还是学到了。
方案对于中文的支持还是很不错的,值得借鉴。
Android 10分区存储介绍及百度APP适配实践
这个就不用介绍了吧,标题很清晰。
字节跳动技术团队
抖音BoostMultiDex优化实践:Android低版本上APP首次启动时间减少80%
抖音BoostMultiDex优化实践:Android低版本上APP首次启动时间减少80%(二)
介绍了抖音团队自研的一个针对 4.x 级以下机型对于 MultiDex 的优化。
目前还没有开源,大家可以了解下原理,后续开源我也会周知大家的。
此外相关知识也可以学习下:
Android 一种在Dalvik虚拟机上多Dex加载优化的方案
https://blog.csdn.net/sbsujjbcy/article/details/53381663
Google 开发者
Room 中的数据库关系
对于 Room 使用者,如果你对 1 对 1,1 对N,N 对 N 不知道如何处理的可以学习下。
历史分享(点击可直接访问):
上次有同学留言问我怎么在 Github 找 Android项目,大家能想到的肯定是看 trending:
https://github.com/trending/java
不过现在能上 trending 的 Android 项目太少了,毕竟和 Java 项目在一个分类。
其实 Github 有主题的概念,直接选择 Android 主题就可以了:
https://github.com/topics/android
可以看到有9W+项目。
也可以按条件筛选:
有这么一个仓库:
https://github.com/Hack-with-Github/Awesome-Hacking
看名字大家就知道介绍啥了,是一个安全相关的仓库汇总,里面包含了太多的子项目,例如有些工具不知道在哪下载,在这里面很好找:
https://github.com/carpedm20/awesome-hacking
LeakCanary 不再使用 haha
我们熟悉的 LeakCanary 已经不再使用之前的 haha 做内存分析,换成了自研的 Shark。
https://square.github.io/leakcanary/shark/
RxJava 已经更新了 3.0.0 版本
https://github.com/ReactiveX/RxJava
不过 Retrofit2 暂时还无法使用 RxJava3,因为相关 Adapter 库还未适配 RxJava3。
好了,祝大家元气满满!
]]>目前随着移动端的兴起,早期的很多中文 PC 博客已经很少有人去阅读了,也很难被大家所发现。
因为我之前关注一个小伙的创业实验,比较好奇他最终会不会成功,也好奇他的灵感,他的下一个产品是一个中文博客相关的 RSS 产品。
所以他收集了一波中文博客列表,推荐给大家:
https://github.com/timqian/chinese-independent-blogs
大概有 300 多位中文博客列表,偶尔读一些这上面的文章,应该还是能激发不少灵感的。
其实如果这些独立博客数量够多,可以聚合起来,做一个阅读 app,支持订阅也是不错的。
实际使用起来,实在太好用了,强烈推荐下。
注意:需要网络良好。
其他一些在线源码阅读工具:
https://www.androidos.net.cn/sourcecode
https://github.com/bytedance/ByteX
基本都是编译时字节码相关的,目前应用于多款字节跳动产品上。
一方面这个可以接入,做一些产品上的安全、优化。
另一方面是学习的好资料,如果都能看明白,AS 的 Transform 和字节码算是已经玩转了。
相关的开源项目就是滴滴的 booster:
https://github.com/didi/booster
说了一些方案,实践还是之前字节跳动给出的方案更适合:
字节跳动:二维码扫描优化
2. 腾讯 Bugly:动态下发 so 库在 Android APK 安装包瘦身方面的应用
动态下发 so库,是减少 apk 体积非常明显的一个方案,之前在百度的时候,也搞过这样的方案,一度是瘦身黑科技,当然坑也不少,so 这玩意搞不好崩溃就比较多,如果需要可以参考目前一些插件化方案、热修方案去做。
3. 天猫精灵:史上最全Android渲染机制讲解(长文源码深度剖析)
恩…技术文章还是很赞的。
Android 测试一直被忽略,我也没有太多这方面涉猎,当然还是值得了解下。
好了,祝大家元气满满!
]]>再有两年就奔三了,自己一直北漂在外,爸妈身体一直是自己比较担心的,总是把最好的都给我们,他们自己的毛病只是不经常跟做儿女的说。陪伴家人的时间也是屈指可数,自己不是一个称职的儿子,只希望自己努力的脚步能超越父母慢慢变老的速度。
就像剧里的爸爸忍着胃癌晚期的疼痛不让家里的孩子知道一样,用生命在为自己的儿子铺路,存下了那两张银行卡。
真正戳到我泪点的是弟弟写给哥哥的那封说不出口的些话,再叛逆的孩子内心深处都存在着最真挚、最纯粹的感情,只是有些时候不会表达出来,比如父亲病床桌子上的一杯水;早上喂了父亲生前总喂的猫。其实生活中真的有好多情感难以用语言表达出来,但是我们彼此都真诚,都愿意付出。
关于哥哥的友情和爱情。他能有一个真正不离不弃的好哥们,在他需要的时候挺身而出,真的挺幸运的。包括兄弟说的那句话“有的人是靠脸活着,而有的人是为了脸活着。”
成年人的世界可能就是这样吧,有生活的重担连恋爱的勇气都没有,但最后他俩在一起真的很开心。
整体而言看完这部电影,
不是职业的电影评论。
可以说这部电影制作很成功了。
一个没有上过大学的哥哥,一个青春期叛逆的弟弟,和一个为了家努力的父亲。
成年人的世界,没有容易二字,每个人都有压力,哥哥觉得弟弟不争气,去跟已故的父亲聊聊天,收拾收拾心情还是再次回到家。
弟弟从最初的不表达爱和不接受这些爱,变成了一个懂事的孩子,每个孩子年少的时候都会经历叛逆时候。后来也会发现,当初是有多么不懂事。
弟弟打那个孩子,错了么?没错,对了么?不对。以前打架是发泄情绪,现在打架是打钱,挺好一句话,现在孩子看了可以想一想,打那个架为了什么?
整个剧可以说环环相扣,从最初哥哥每天都给那个流浪汉钱,是傻么?只是活的乐观而已。
弟弟逃学打游戏,没有光为了打游戏,打游戏得的奖品也去送给了父亲,唯一一个过错就是没有当着面叫他一声爸。
哥哥只要点点头,就能得到一份挣钱的工作,但是他没有,他活着要着一张脸,可以通过自己的努力去挣这个钱,但不想靠感情来挣这个钱。
一个简单的故事,很温暖,源于生活。
里面有句话挺好,有人是靠脸活着,有人为了脸活着。
]]>官方发布说明:medium.com/flutter/wha…
北半球的冬意已至,黄叶与气温均随风而落,而年终的最后一个稳定版本已悄然来到你的面前。 让我们向 Flutter 2.8 打声招呼~ 本次更新包含了 207 位贡献者和 178 位审核者 的辛勤劳作, 所有人共同产出了 2424 个 PR,关闭了 2976 个 issue。 在此特别感谢本次发布中最突出的社区贡献者:来自 Very Good Ventures 的 Flutter 开发工程师 Bartosz Selwesiuk, 他专注于 Web 平台的 camera 插件并提交了 23 个 PR。
以上的所有产出让 Flutter engine 和 DevTools 都有了非常显著的性能提升, 同时带来的还有 Google 移动端广告 SDK Flutter 版本的稳定版发布、 一系列针对 Firebase 的新功能和优化、Flutter WebView 3.0、 新的 Flutter Favorite package、向桌面端 Stable 迈出的一大步, 以及支持更多 package 的新版本 DartPad。 虽然这是今年最后一个稳定版本,但它并不是最不重要的。让我们一起来看看!
Flutter 的首要目标是一如既往地保证其质量。 我们花费了大量时间以确保 Flutter 在多种多样的设备上都能流畅且稳定地运行。
本次更新优化了应用启动的延迟。 我们在拥有一百万行以上的代码量的 GPay 应用上进行了测试,以确保改动在实际生产的应用上有效。 这些改动将 GPay 在低端 Android 设备上启动的时间减少了约 50%,高端设备上减少了约 10%。
我们对 Flutter 调用 Dart VM 的 GC 策略也做了一些改进,以此避免在程序启动期间出现不合时宜的 GC。 例如,在 Android 设备上渲染出第一帧前,Flutter 仅在 TRIM_LEVEL_RUNNING_CRITYCAL
及高于其等级的信号出现时,通知 Dart VM 有内存压力 。 在本地测试中,低端 Android 设备的初始帧出现间隔时间最多减少了约 300ms。
在先前的 Flutter 版本中, 出于谨慎考虑 , 在创建 PlatformView 时会阻塞平台线程。 在经过仔细的推理和测试后 , 我们删除了部分序列化的步骤,使得 GPay 在低端设备上的启动时间至少减少了 100ms。
长久以来,在初始化首个 Dart isolate 前初始化默认的字体管理器会引入人为的延迟。 由于它是首要的延迟瓶颈,所以 将默认字体管理器的初始化延迟 到与首个 Dart isolate 同时运行,降低了启动的延迟,并让上述的所有启动优化的表现更加明显。
由于 Flutter 会尽可能快地加载 Dart VM 的服务 isolate, 并将其和绑定在应用内的 AOT 代码一并加载到内存中, 这会导致 Flutter 开发人员在部分内存 有限制的设备上难以追踪内存指标 。 在 Flutter 2.8 版本中,Android 设备上 Dart VM 的服务 isolate 已被拆分至单独的 bundle 中 , 可以单独加载,减少了在其加载前约 40MB 的内存使用。 原本 Dart VM 向操作系统发送 AOT 程序的内存用量的通知, 已转由一个无需多次读取的文件支持,后续的内存占用量进一步减少了约 10%。 因此,先前保存了文件数据拷贝的内存可以回收并用于其他用途。
某些场景下,开发者希望能同时看到 Flutter 和 Android 的性能追踪事件, 又或者是在生产模式下查看追踪事件来更好地了解应用的性能问题。 为了这一需求,Flutter 2.8 现在可以选择在应用启动后, 将性能追踪事件发送至 Android 的事件记录器,在生产模式下也同样如此。
此外,一些开发人员想要更多的关于光栅缓存行为的性能跟踪信息, 以减少制作动画效果时的卡顿,这允许 Flutter 快速地对昂贵的、重复使用的图片进行复用而不是重新绘制。 性能跟踪中的新的 流事件让开发人员可以跟踪光栅缓存图片的生命周期。
对于调试性能问题,新版的 DevTools 添加了一个新的「增强跟踪」功能, 用来帮助开发者诊断消耗较大的构建、布局和绘制操作引起的 UI 卡顿。
启用任何一个追踪功能后,时间轴中将视情况展示 Widget 的构建、RenderObject 布局和 RenderObject 绘制的事件。
此外,新版的 DevTools 增加了应用程序启动性能的分析支持。 该配置文件包含从 Dart VM 初始化到第一个 Flutter 帧渲染的 CPU 样本。 在你按下「Profile app start up」按钮并加载应用程序启动配置文件后, 你将看到为配置文件选择了「AppStartUp」用户标签。 你还可以通过在可用用户标签列表中选择此用户标签过滤器(如果存在)来加载应用程序启动配置文件。 选择此标签会显示你的应用程序启动的个人资料数据。
不仅仅是 Android 和 iOS 平台获得了性能提升,本次发布同时包含了对 Flutter Web 平台视图的性能优化。 平台视图是从宿主平台向 Flutter 嵌入 UI 组件的媒介。 Flutter Web 使用 HtmlElementView
widget 实现了这一功能,让你能在 Flutter Web 应用中嵌入 HTML 元素。 如果你正在使用 google_maps_flutter 插件或 video_player 插件的网络版本, 或者你正在遵循 Flutter 团队关于 如何优化网络上显示图像 的建议,那么你正在使用平台视图。
在之前版本的 Flutter 中,嵌入平台视图会创建一个新的 canvas,每嵌入一个平台视图都会新增一个 canvas。 创建额外的 canvas 是十分消耗性能的操作,因为每个 canvas 的大小都与整个窗口相等。 在 Flutter 2.8 中,将 复用为先前的平台视图创建的 canvas , 因此,你不会在应用程序的整个生命周期内产生每秒 60 倍的成本,而是只有一次创建的成本。 这意味着你可以在 Web 应用程序中拥有多个 HtmlElementView
实例而不会降低性能, 同时还可以减少使用平台视图时的滚动卡顿。
Flutter 不仅仅是框架、引擎和工具——pub.dev 上现有超过 2w 个与 Flutter 兼容的包和插件,而且每天都在增加。 Flutter 开发人员大量的日常操作也是庞大的生态系统的一部分, 所以让我们来看看自上一个版本以来 Flutter 生态系统中有什么改变。
首先也是最重要的是, Google Mobile SDK for Flutter 已于 11 月正式发布 。 此版本支持 5 种广告格式,集成了 AdMob 和 Ad Manager 支持, 并包含一个新的中转功能的测试版,可以帮助你优化广告展现的效果。 有关将 Google Ads 集成到 Flutter 应用程序以及其他货币化选项的更多信息, 请查看 flutter.dev 上的页面 。
这次 Flutter 附带的另一个新版本是 webview_flutter 插件 的 3.0 版本。 因为新功能的数量增加,我们提升了主要版本号,但也因为 Web 视图在 Android 上的工作方式可能发生了重大变化。 在之前的 webview_flutter
版本中,Hybrid composition 已经可用,但不是默认的。 而现在它修复了先前默认以虚拟显示模式运行的许多问题。 根据用户反馈和我们的问题跟踪,我们认为是时候让 Hybrid composition 成为默认设置了。 此外,webview_flutter
还增加了一些呼声极高的功能:
此外,在 3.0 版本中,webview_flutter
为新平台提供了初步支持:Flutter Web。 已经有很多人要求能够在 Flutter Web 应用程序中托管 Web 视图, 这允许开发者利用单个源代码库构建移动或 Web 应用程序。 在 Flutter Web 应用程序中托管 Web 视图是什么样的? 从编写代码的角度来看,其实是一样的:
1 | import 'package:flutter/foundation.dart'; |
在 Flutter Web 上运行时,它会按你的预期工作:
请注意,当前 webview_flutter
的 web 实现有许多限制,因为它是使用 iframe 构建的, iframe 仅支持简单的 URL 加载,无法控制加载的内容或与加载的内容交互。 但是,由于需求呼声太高,我们决定将 webview_flutter_web 作为未经认可的插件提供。 如果你想尝试一下,请将以下内容添加到你的 pubspec.yaml 中:
1 | dependencies: |
如果你对 webview_flutter v3.0 有任何反馈,无论是否是关于 Web 平台, 请将问题记录在 Flutter 仓库中 。 此外,如果你之前没有使用过 webview 或者你想复习一下, 请查看新的 webview codelab , 它将带你逐步完成在 Flutter 应用程序中托管 web 内容的过程。
Flutter 生态系统委员会再次召开会议,将以下 package 指定为 Flutter Favorite 的 package:
祝贺这些 package 的作者,并感谢你通过你的辛勤工作支持 Flutter 社区。 如果你有兴趣提名你最喜欢的 Flutter 包加入 Flutter Favorite 嘉奖, 请按照 Flutter Favorite 计划页面 上的指南和说明进行操作。
如果你是插件作者,你必须决定你将支持哪些平台。 如果你正在使用特定于平台的原生代码构建插件, 你可以 使用项目 pubspec.yaml 中的 pluginClass 属性 来实现,该属性将指定提供原生功能的原生类名:
1 | flutter: |
然而,随着 Dart FFI 变得更加成熟,有可能使用 100% 的 Dart 实现特定平台的功能, 就像 path_provider_windows package 所做的那样。在这种情况下,你没有任何本地类可以使用, 但你仍然希望将你的包指定为仅支持某些平台。 此时你可以改用 dartPluginClass 属性:
1 | flutter: |
经过这样的设置后,即使你没有任何本机代码,也可以为特定平台定制插件。 你还必须提供 Dart 插件的类, 有关详细内容,你可以在 flutter.dev 上的仅 Dart 平台实现文档 中进行扩展阅读。
Flutter 2.8 版本在 Windows、macOS 和 Linux 稳定版本的道路上又迈出了一大步。 我们的目标质量标准很高,包括国际化和本地化支持, 例如 新的中文输入法支持 、 韩语输入法支持 以及刚刚合并的 Kanji(日文)输入法 支持。 或者,就像我们在紧密构建 Windows 辅助功能的支持 一样。 对于 Flutter 来说,在 Stable 渠道的 desktop 上运行是不够的, 它必须在世界各地的语言和文化以及不同能力的设备上运行良好。 我们还没有达到我们想要的目标,但未来可期!
其中一个例子是我们重构了 Flutter 处理键盘事件以允许同步响应的架构。 这使 widget 能够处理按键并拦截它在整个 widget tree 中的其余部分中的传递。 我们在 Flutter 2.5 中完成了这项工作的落地,并在 Flutter 2.8 中修复了许多问题。 这是对我们如何处理特定于设备的键盘输入的方式的重新设计, 和重构 Flutter 处理文本编辑方式的持续工作的补充, 所有这些都是用键盘这样输入密集型的桌面应用程序所必需的。
此外,我们还在继续 向 Flutter 扩展视觉密度的定义 , 暴露对话框对齐方式的设置,以便开发者可以实现更加友好的桌面 UI。
最后,Flutter 团队并不是唯一一个在为了 Flutter desktop 付出心血的团队。 举个例子,Canonical 的桌面团队正在与 Invertase 合作, 在 Linux 和 Windows 上开发最流行的 Flutter Firebase 插件。
你可以在 Invertase 博客上 阅读有关预览版的更多信息。
如果没有工具的改进,那么这个 Flutter 新版本的发布是不完整的。 我们将重点介绍 DartPad 的改进,其中最大的改进是对更多软件包的支持。 事实上,目前共有 23 个 package 可供导入使用。除了几个 Firebase 服务之外,该列表还包括诸如 bloc、characters、collection、google_fonts 和 flutter_riverpod 等流行的 package。 DartPad 团队会继续添加新的 package,如果你想查看当前支持哪些软件包,可以单击右下角的信息图标。
如果你想了解随着时间的推移向 DartPad 添加新包的计划, 请查看 Dart wiki 上的这篇文章 。 还有另一个新的 DartPad 功能也非常方便。 在此之前,DartPad 总是以运行最新的 stable 版本运行。 在新版本中,你可以使用状态栏中新的 渠道菜单 来切换最新的 Beta 渠道版本以及之前的 stable 版本(称为“旧渠道”)。
如果你正在撰写一篇博客文章,而最新的稳定版本还不够新,这将非常有用。 (其实方便了切换不同的渠道进行调试和 BUG 验证。)
Flutter「渠道」决定了 Flutter 框架和引擎在你的开发机器上变化的速度, stable 代表最少的变更,而 master 代表最多。 受到团队资源的限制,我们最近将停止更新 dev 渠道。 虽然我们确实收到了一些相关的问题,但我们发现只有不到 3% 的 Flutter 开发人员使用 dev 渠道。 因此,我们决定启动正式停用 dev 渠道的进程。 虽然很少有开发人员使用 dev 渠道,但 Flutter 工程师需要花费大量时间和精力来维护它。 如果你将所有时间都花在 stable 渠道上(正如超过 90% 的 Flutter 开发人员所做的那样), 那么这项改动将不会影响你的日常开发。 通过放弃它,你可以少做一个决定,而 Flutter 团队可以将时间和精力花在其他事情上。 你可以使用 flutter channel
命令来决定你想要哪个渠道。 以下是 Flutter 团队对每个渠道的看法:
当我们在未来几个月停用 dev 渠道时,请考虑使用 beta 或 master 渠道, 这取决于你对变更的容忍度以及你对最新和最好的 SDK 的平衡点。
与往常一样,我们努力减少每个版本中破坏性更改的数量。 在此版本中,Flutter 2.8 除了已过期并根据我们的 重大变更政策 被删除的已弃用 API 之外,没有重大变更。
如果你仍在使用这些 API 并想了解如何迁移代码,你可以阅读 flutter.dev 上的迁移指南 。 与往常一样,非常感谢社区 贡献了测试用例 , 帮助我们识别这些重大更改。
在我们结束 2021 年并展望 2022 年之际,Flutter 团队要对整个 Flutter 社区的工作和支持表示感谢。 诚然,我们正在为世界上越来越多的开发人员构建 Flutter, 但如果没有你和每位开发者的存在,我们也无法维护并构建它。 Flutter 社区与众不同,我们感谢你所做的一切。 祝你假期愉快,我们新的一年见!
]]>声明性编程范式:声明性的函数构建一个简单的界面组件,无需修改任何 XML 布局,也不需要使用布局编辑器,只需要调用 Jetpack Compose 函数来声明想要的元素,Compose 编译器即会完成后面的所有工作。
简单的组合函数
1 |
|
声明性范式转变:在 Compose 的声明性方法中,微件相对无状态,并且不提供 setter 或 getter 函数。实际上,微件不会以对象形式提供。您可以通过调用带有不同参数的同一可组合函数来更新界面。这使得向架构模式(如 ViewModel)提供状态变得很容易,如应用架构指南中所述。然后,可组合项负责在每次可观察数据更新时将当前应用状态转换为界面。
动态 :组合函数是用 Kotlin 而不是 XML 编写
重组:在 Compose 中,您可以使用新数据再次调用可组合函数。这样做会导致函数进行重组 – 系统会根据需要使用新数据重新绘制函数发出的微件。Compose 框架可以智能地仅重组已更改的组件。
Arctic Fox 2020-3-1 版本以上,下载最新AndroidStudio
ComposeApp仅支持Kotlin 最低sdk 版本为21,Android 5.0
Gradle Compose相关依赖
1 | plugins { |
需要安装最新Java11, java 8 环境会报以下错误
1 | Android Gradle plugin requires Java 11 to run. You are currently using Java 1.8. |
@Preview生效,则环境正常
所有关于构建View的方法都必须添加@Compose
注解才可以。并且@Compose
协程的Suspend
的使用方法比较类似,被@Compose
注解的方法只能在同样被@Comopse
解的方法中才能被调用。
1 |
|
@Preview
注解的方法可以在不运行App的情况下就可以确认布局的情况。
常用的参数:
name
: String: 为该Preview命名,该名字会在布局预览中显示。showBackground
: Boolean: 是否显示背景,true为显示。backgroundColor
: Long: 设置背景的颜色。showDecoration
: Boolean: 是否显示Statusbar和Toolbar,true为显示。group
: String: 为该Preview设置group名字,可以在UI中以group为单位显示。fontScale
: Float: 可以在预览中对字体放大,范围是从0.01。widthDp
: Int: 在Compose中渲染的最大宽度,单位为dp。heightDp
: Int: 在Compose中渲染的最大高度,单位为dp。1 |
|
setContent的作用是和Layout/View中的setContentView是一样的。
setContent的方法也是有@Compose注解的方法。所以,在setContent中写入关于UI的@Compopse方法,即可在Activity中显示。
1 | override fun onCreate(savedInstanceState: Bundle?) { |
在创建新的Compose项目时会自动创建一个Theme.kt文件。 我们可以通过更改颜色来完成对主题颜色的设置。
1 | package com.zm.myjetpackcompose.ui.theme |
Modifier是各个Compose的UI组件一定会用到的一个类。它是被用于设置UI的摆放位置,padding等信息的类。
1 | Modifier.padding(10.dp) // 给上下左右设置成同一个值 |
1 | Modifier.plus(otherModifier) // 把otherModifier的信息加入到现有的modifier中 |
1 | Modifier.fillMaxHeight() // 填充整个高度 |
1 | Modifier.width(2.dp) // 设置宽度 |
1 | Modifier.widthIn(2.dp) // 设置最大宽度 |
1 | Modifier.gravity(Alignment.CenterHorizontally) // 横向居中 |
1 | Modifier.rtl // 从右到左 |
Column 线性布局 ≈ Android LinearLayout-VERTICAL
Row 水平布局 ≈ Android LinearLayout-HORIZONTAL
Column和Row可以理解为在View/Layout体系中的纵向和横向的ViewGroup。
1 | Column { |
帧布局≈Android FrameLayout,可将一个元素放在另一个元素上,如需在 Row 中设置子项的位置,请设置 horizontalArrangement 和 verticalAlignment 参数。对于 Column,请设置 verticalArrangement 和 horizontalAlignment 参数
需要引入implementation "androidx.constraintlayout:constraintlayout-compose:1.0.0-beta02"
可以滚动的布局
1 | //我们可以使用 verticalScroll() 修饰符使 Column 可滚动,但以上布局并无法实现重用,可能导致性能问题 |
LazyColumn/LazyRow == RecylerView/listView 列表布局,解决了滚动时的性能问题,LazyColumn和LazyRow之间的区别就在于它们的列表项布局和滚动方向不同。
1 | LazyColumn( |
item间距
1 | LazyColumn( |
浮动列表的浮动标题,使用 LazyColumn 实现粘性标题,可以使用stickyHeader()函数
1 |
|
网格布局LazyVerticalGrid
1 |
|
1 | Canvas(modifier = Modifier.fillMaxSize()) { |
Compose
整体来看,布局实现上相对于xml更加简单高效,也是官方日后力推的开发方式。Compose
写法与Flutter
代码上有很高的相似之处,都是通过响应式的快速搭建UI布局。通过设置好触发器、输入、操作、实用程序、输出,就可以自由搭建工作流。在本教程中,我将创建一个简单的热键工作流,用来一键启动我每天多次使用的一些应用程序和网页。
进入Alfred的偏好设置中的workflows*标签页,点击左下角的“+”,然后选择Templates > Files and Apps > Launch file group from hotkey*,创建一个用热键打开的工作流。
然后为你的工作流编辑名称、图标等信息,便于识别。(图标可以直接拖入)
编辑完成后,点击右下角的Save即可创建出一个名为work的工作流。左边图标是热键,右边图标是你要创建的动作。
双击左边图标,打开热键设置窗口,选中输入框,直接在键盘上键入你想要设置的热键,然后保存。
双击右侧图标,打开动作设置窗口,你可以选择一次启动多个Mac软件或文件夹,比如我想要一次打开AndroidStudio、Safari、Sourcetree三个应用程序,直接将它们拖入此窗口即可。
Alfred除了能设置打开多个Mac软件外,还可以设置打开多个网页。比如我们想同时打开马可菠萝网站,可以在窗口任意位置右键,选择Actions > Open URL。
在弹窗中将马可菠萝的网址 https://zhangmiao.cc/ 复制粘贴进去,并选好默认浏览器,保存即可。
最后再把这个新的动作链接到热键的后面,即完成打开马可菠萝网站的设置了。
了解Alfred的工作流程,能够帮助你轻松完成各种重复任务,让你以前所未有的方式在Mac上提高效率!
]]>为了快速打开Alfred
,我们需要为它设置一个快捷键,打开Alfred
偏好设置的General
选项卡,选中Alfred Hotkey
输入框,直接使用键盘键入你喜欢的快捷键即可。
我使用的alt+空格键
,当初设置时,由于这个快捷键被Mac系统的Spotlight占用了,无法设置成功。如遇到相同的情况,需要先到系统偏好设置-键盘-快捷键-聚焦
中取消勾选alt+空格键
打开“聚焦”搜索的设置,然后再返回到Alfred
中设置即可。
进入Appearance
选项卡,Alfred为我们提供了几种外观样式,如果你都不喜欢,也可以自定义外观。点击左下角的“+”,创建你的专属样式。通过双击相应的组件,即可打开系统的调色器,可以自由的搭配自己喜欢的颜色。
进入Features
选项卡,在左侧列表中,罗列的是Alfred
的基础功能,包括默认搜索
、文件搜索
、网页搜索
、网页书签
、计算器
、字典
、联系人
、剪贴板
、iTunes
、系统操作
、终端
等。
Essentials和Extras设置我们的搜索类型,可以设置搜索应用程序、联系人、偏好设置、文件夹、文本文件、压缩文件、文档、图片、脚本等。
Unintelligent建议不要勾选,会影响我们的搜索速度以及搜索结果。
Search Scope可以设置Alfred的搜索范围,点击右上角的”+”,可以添加其他的搜索范围;或者选中某项,按Delete
键移除该搜索范围。
默认搜索会搜索出所有类型的内容,包括邮件、联系人等其他内容,都是我根本不想搜索到的文件类型,这时就可以使用文件搜索,把一些不需要的类型过滤掉。
这是小编使用频率最高的功能,有了它,再也不怕记不住网址了。Alfred中已经默认设置了很多国外的网站,但大多数都是用不上的,不需要的只要取消勾选就行。点击右下角的“Add Custom Search”
,即可添加新的网站搜索。
比如添加马可菠萝网站,只要到马可菠萝的搜索页面随便搜索一个内容,然后复制结果页面的网址,把具体的内容改成{query}
即可,关键字填写macbl,然后保存。
使用时,在Alfred中输入 macbl Alfred,按回车键即可打开马可菠萝搜索页面,找到Alfred啦!
剪贴板功能是我选择Alfred的主要原因,可以查看Alfred的所有剪贴历史记录,节省了重复操作的时间,非常强大
用简单的命令,来控制系统操作,比如最常用的清空垃圾桶(enptytrash),休眠(sleep),强制退出应用程序(forcequit)等,快捷键都可以自由设置。
]]>Flutter 作为一门新的技术,确实相关的资料书籍不太多。但就 Flutter 生态的影响力而言,已经是越来越强了。譬如,在 StackOverflow 网站上2021年度最受欢迎的技术中,Dart 语言排在了第7位。随着谷歌对 Flutter 跨平台解决方案的推进,估计会有越来越多的开发者使用 Flutter 构建他们的应用。
先上一份思维导图,让大家有个整体认识。
在学习 Dart 语言前,若没有任何编程基础,建议先了解一下计算机基础知识。Dart 作为一门现代化的面向对象编程语言,具备了市面上大多数编程语言的特点,具体来说会分为下面这些内容:
作为一个合格的App 开发,能够将一个UI 界面还原出来是基本的要求。建议一开始需要熟悉Flutter框架提供的自带组件,然后可以通过自带的组件组合成为自己的自定义组件。这部分内容包括:
Container
,SizedBox
,Padding,Stack
,ListView
,GridView
等组件。TextField
,按钮,文本,图片,图标等组件。应用中,表单在界面中出现的频率很高。如何处理表单对开发效率的影响很大。建议可以一开始从简单的表单页面开始,例如登录页、注册页。然后再做一些复杂的表单页面,具体如下:
状态管理是 Flutter 的核心,如何处理数据实体、业务逻辑、界面之间的关系对代码的可维护性十分关键,而这都依赖于状态管理的实现。对于状态管理,建议按如下方式学习:
StatefulWidget
和 StatelessWidget
的源码,会有更深刻的理解。关于状态管理的内容,可以通过阅读下面两篇文章来进行了解:
🚩🚩🚩建议收藏
Flutter状态管理插件哪家强?请看岛上码农的排行榜!
Flutter 入门与实战(九十二):状态管理系列大汇总
App 的业务功能开发,相当一部分工作是在与和后端对接口、联调接口。了解与后端的数据交互,封装好网络请求库非常重要。这里建议按如下的方式进行学习:
Headers
和 Cookie
:App 和浏览器不同,浏览器会自己管理Cookie
。而 App 需要自己管理 Cookie
。因此有必要了解如何设置请求头Headers
,以及如何获取后端的 Cookie
并回写到Headers
里面。当你对界面、状态管理、网络请求都掌握差不多到时候,使用 Flutter
开发基本的 App 就基本没问题了。这个时候需要考虑应用结构如何优化。对于Dart
而言,提供了 Stream
和 StreamListener
这样的工具来通过流的方式驱动关联业务或界面更新,实现响应式编程。这里面典型的是 BLoC
模式 (BLoC
也可以用于状态管理)。了解一下 BLoC
的理念对设计整个应用程序框架十分有帮助。
当你掌握上述的基本技能后,你看到别人 App 的酷炫动效时肯定心痒痒,想自己偶尔也能玩一下这类高大上的东西。这个时候就需要了解动画的实现了,Flutter
提供了很多动画构建方式,比如:
AnimatedContainer
,AnimatedOpacity
等等,通过这些组件可以实现简单但有趣的动画。AnimatedBuilder
可以构建可复用的动效。Flutter
自带了很多动画曲线效果,如果不满足也可以实现自定义曲线。有了动画曲线,你就可以定义一些自己的动画过渡效果了。Lottie
就可以将 AE 的动画转换为 Flutter
动画。如何查找动画插件,这需要懂得搜索,比如搜索关键字 Animation
,或者经常逛一些技术社区,会让你的视野开拓很多,也许,不经意间就能发现一个酷炫的插件。当你的动画都能搞定的时候,你会发现产品和设计可能已经对你刮目想看了,这个时候他们提出的交互或者界面效果会提高(千万别觉得升级自己的技能是在给自己挖坑)。比如,可能会出一个奇怪的外形,然后需要你实现,这个时候就需要用到绘图了。绘图其实需要挺高的数学知识辅助的,你可能需要提前复习一下高等数学、线性代数知识😜😜😜 —— 所以大厂筛选学校和学历其实也有一定的道理的,这些筛选出来的人的基础知识一般都不会差。
ClipPath
:自定义裁剪路径对于绘制有规律的形状来说可以轻松搞定,当然有些复杂的可能需要一些贝塞尔曲线知识。CustomPaint
和 Canvas
:使用 CustomPaint
和 Canvas
可以随心所欲地绘图,包括你想搞个小游戏也行。但是,这个也是很烧脑的一环,说到底,数学真的很重要!随着网络的升级,本地数据存储可能不像之前那么重要。但是,不论是对用户体验还是减轻后端压力都是必不可少的。譬如,微信就把整个个人的聊天记录存储在了本地 —— 既节省了服务器的存储空间和加载请求量,还能够对外宣称是“保护个人隐私”。本地存储主要有三个方面:
Flutter
通常是使用 SharedPreferences
实现。path_provider
插件实现。SQLite
数据库,SQLite
的数据库操作语法和 MySQL
这类的标准 SQL
基本一致,可以用于存储关系数据。在Flutter
中也有不少封装好的插件,比如 sqflite
。实际上页面导航在一开始就会用到,大部分情况下,自带的导航和路由管理都能够满足需求。对于路由可以按如下方式进阶:
fluro
、GetX
的路由管理的优缺点,选择使用自带的路由管理还是使用第三方插件。如果你的公司业务条线比较多,也许此时已经成为公司大神的你会被邀请做基础设施建设,或者是你自己想为开源社区做做贡献,这个时候就需要构建自有插件或开源插件了。Flutter 提供了插件构建模板工程,你可以按步骤构建自有插件,然后供整个公司的各个业务条线使用,提高各个业务条线的生产力。
原生交互分为三个部分:
这块对于混编的应用来说是必不可少的,此时你的知识体系需要升级了,你需要学习安卓的 kotlin 开发,iOS 的 Swift 开发(呃,本来想一站式搞定,结果又绕回来了)。当然,到这个阶段,相信这些已经难不倒你了!
恭喜你!你的应用可以在各大应用市场上架了!记得我的第一个应用在 AppStore过审的时候别提多兴奋了(之前被拒了好几次😂😂😂)!如何进行应用打包这个搜索一下就能搞定了,但是如何应对AppStore 每年都变的审查规则也是一场斗智斗勇的过程。 而安卓,如果搞定碎片化的操作系统分布也是头疼的一件事情。建议提前在应用内做应用统计,以及异常上报,避免发布后在用户机器上出现奇怪的问题。
技术永无止境,再往后,你可能会深入去做性能优化、应用架构设计。这些方面很大程度靠个人平时的积累,多输入新的知识,同时了解其他的应用框架和特性(不限于 Dart,比如 Java 的 Spring 框架,Web 端的 React、Vue)都会让你对当前的应用架构设计有新的认识。扩充视野和技术深度,也许你就是下一个 CTO 的人选💪🏻💪🏻💪🏻!!!
]]>开启对web开发的支持
flutter开发要支持web,需要在命令行中输入以下命令打开支持的平台(以下列举了各个平台支持的命令行):
flutter config —enable-web-desktop
flutter config —enable-windows-desktop
flutter config —enable-macos-desktop
flutter config —enable-linux-desktop
之后再次输入 flutter config
检测开启的情况,如果检测到如下图所示则表示开启成功。
这时候可以创建项目了,创建的时候勾选Web选项即可。
A. MediaQuery
响应式布局的本质是监听浏览器宽高的变化进而修改UI的样式,所以需要能够监听宽高的变化,此时可以使用MediaQuery控件。MediaQuery控件继承自InheritedWidget,通过MediaQuery.of(context).size
的方式实时获取到浏览器宽高的变化,进而对全局的UI进行调整。
B. LayoutBuilder
LayoutBuilder是MediaQuery的简化版本,可以实时监控父控件尺寸的变化(不是浏览器宽高的变化),进而对当前控件的UI进行调整。
C. AspectRatio
这是一个指定宽高比例的控件,会随着浏览器宽度变化进行等比例缩小或者放大。如果要保证某个控件的宽高保持一致,则需要使用AspectRatio。
D. Flexible 与 Expanded
Flexible与Expanded是用在Row或者Column中的控件,前者可以用来控制控件在Row或者Column中占用的比例,后者则用来填充Row或者Column中剩余的空间。
E. FractionallySizedBox
这是一个设置占位比例的控件,跟AspectRatio类似,可以设置占有父控件多大比例。
A. responsive_framework
这个库支持屏幕尺寸变化时对所用控件进行缩放控制或者进行实时UI调整,支持手机、平板、电脑尺寸的设置,使用方便。支持缩放控制是其最大亮点。
B. responsive_builder
这个库支持屏幕尺寸的变化时对所有控件进行实时UI调整,并能检测移动端横竖屏变化。
如果要做到开发web的时候也需要支持移动端,在引入开源库时要注意其支持的平台种类。如下图中支持的平台就包含了安卓、iOS、Linux、MacOS、Web以及windows。最好引入的开源库支持全平台。
参考资料:
https://betterprogramming.pub/how-to-build-responsive-apps-with-flutter-widgets-review-b22c6dec6904
https://medium.com/flutter-community/seven-things-you-should-know-before-starting-with-flutter-web-8e48555d819e
组件CachedNetworkImage可以支持直接使用或者通过ImageProvider
。
引入依赖
1 | dependencies: |
执行flutter pub get
,项目中使用
Import it
1 | import 'package:cached_network_image/cached_network_image.dart'; |
添加占位图
1 | CachedNetworkImage( |
进度条展示
1 | CachedNetworkImage( |
原生组件Image配合
1 | Image(image: CachedNetworkImageProvider(url)) |
使用占位图并提供provider给其他组件使用
1 | CachedNetworkImage( |
这样就可以加载网络图片了,而且,图片加载完成时,就被缓存到本地了,首先看下图片的加载流程
官网说了,它现在不包含缓存,缓存功能实际上是另一个库
flutter_cache_manager
中实现的
这里我们仅梳理图片加载和缓存的主流程,对于一些其他分支流程,或无关参数不做过多分析
首先,页面上使用的构造函数接收了一个必传参数imageUrl,用于生成ImageProvider提供图片加载
1 | class CachedNetworkImage extends StatelessWidget{ |
这里可以看到,构造函数初始化了一个本地变量_image
类型是CachedNetworkImageProvider
,它继承ImageProvider提供图片加载,看下它的构造函数
1 | /// 提供网络图片加载Provider并缓存 |
它的构造函数调用了image_provider.CachedNetworkImageProvider
的实例在_image_provider_io.dart
中是加载的具体实现类
1 | /// IO implementation of the CachedNetworkImageProvider; the ImageProvider to |
这里的load方法即是图片加载的启动入口,它会在页面可见时被调用
它返回了一个MultiImageStreamCompleter
传入_loadAsync
,看下这个方法
1 | /// 异步加载 |
这里我们看到了默认缓存管理器cacheManager
创建的地方,为DefaultCacheManager
,那么它如何缓存的呢,后边再分析。
下载的逻辑也是放在了ImageCacheManager
下了,返回结果是一个stream
完成多图下载的支持,下载完成通过yield 返回给ui解码最终显示。
1 | MultiImageStreamCompleter`支持多图加载继承自`ImageStreamCompleter |
这里做了显示逻辑,和最终转化成flutter上帧的处理,_scheduleAppFrame
完成发送帧的处理
上边的mngr
调用了ImageCacheManager
中的getImageFile
方法现在就到了flutter_cache_manager
这个三方库当中,它是被隐式依赖的,文件是image_cache_manager.dart
1 | mixin ImageCacheManager on BaseCacheManager { |
缓存判断逻辑在CacheStore
提供两级缓存
1 | class CacheStore { |
_cacheInfoRepository
缓存仓库是CacheObjectProvider
使用的数据库缓存对象
1 | class CacheObjectProvider extends CacheInfoRepository |
可见数据库缓存的是CacheObject
对象,保存了url、key、relativePath等信息
1 | class CacheObject { |
没有缓存下调用了_webHelper.downloadFile
方法
1 | class WebHelper { |
cached_network_image
图片加载流程依赖ImageProvider
,缓存和下载逻辑放在另一个库flutter_cache_manager
下载文件在WebHelper
中提供队列管理,依赖传入FileService
做具体获取文件方便扩展默认实现HttpFileService
,下载完成后路径保存在CacheObject
保存在sqflite
数据库
然而在这样一个信息过载的时代里,形形色色的声音与观点鱼龙混杂。生活在信息洪流中的我们,应该如何避免随波逐流,做到真正的独立思考?
或许可以了解一下“CriticalThinking”——「批判性思维」。
批判性思维是以一种合理的、反思的、心灵开放的方式进行思考,从而能够清晰准确地表达、逻辑严谨地推理、合理地论证,以及培养思辨精神。
在批判性思维的情感倾向方面,科南特(J.Kurland)于1995年提出:批判性思维与过分情感主义、智力懒惰和封闭思维相反,它关注证据、智力、诚实和开放思维。因此批判性思维强调依靠证据而非情感,全面考虑各种可能的观点和解释,警惕个人动机和偏见的影响,更关注寻求真理,不拒绝非流行的观点,意识到自己的偏见、歧视,自觉避免或减少这些偏见对判断的影响。此外,具有批判性思维并不意味着总是对任何人和任何事持否定态度和吹毛求疵,批判精神意味着敏锐的思维、好奇的探究、对推理的热情、对可靠信息的渴望。
当我们面对外界信息或是他人的论证时,可以在脑海里思考这些问题:
当我们需要表达自己的观点、建构自己的论证时,可以遵循以下的方法: