聊聊我对知识付费的理解

写在前面

Emmmmm…上一篇文章结尾聊到自己要尝试开始去《小专栏》写文章的事儿,本来是准备简单写几句一笔带过的,不过在我写完《Flutter 状态管理》系列第二篇文章之后仔细想了想还是应该抽出来聊聊自己对于知识付费的看法。

索引

  • 知识付费
  • 时间成本
  • 正向激励
  • 商业化

知识付费

知识付费,知识付费主要指知识的接收者为所阅览知识付出资金的现象。知识付费让知识的获得者间接为向知识的传播者与筛选者给予报酬,而不是让参与知识传播链条的人通过流量或广告等其它方式获得收益。

请原谅我这里引用了百度百科,因为我其实觉得百度百科对于知识付费的描述很对我的胃口,作为技术从业者我知道很多人比较鄙视百度生态,但是我觉得还是不能为了鄙视而鄙视,也不是说鄙视了百度用了谷歌就能说明自己高人一等,你说对吧?

时间成本

百科里对我胃口的主要是「时间成本」小节的描述:

选择太多,用户决策瘫痪,自己选择的时间成本增加,愿意通过付费来代替个人搜寻选择,这使知识付费成为可能。

这句话从技术从业者的角度来讲也是适用的,我相信也期望所有知识付费的付费者都具备一定的内容识别能力,即自己可以辨别文章/音频/视频等媒介传播的内容质量。

正向激励

随着互联网越来越发达,各种碎片化的教程类文章/音频/视频越来越多,辨别这些媒介内容质量并过滤出对自己有用的内容这件事儿的时间成本越来越大了…

遗憾的是,之前自己在学习过程中好不容易收集和订阅的各种牛人的博客更新频率越来越低了,甚至有些以及停更了。一种可能是这些自己喜欢的人他们真的越来越忙所以没有时间来写文章分享了,另外一种可能就是缺乏正向激励。

做技术手艺人在中国特色国情下其实还是蛮焦虑的,即便是做到了专家 Title 带了团队之后这种焦虑也会存在,甚至随着年龄的提升而逐渐增大。这种情况下有些聪明人踩对了时代的节奏,做独立开发,少数人坚持并成功了,相当于有了自己的事业;另外一部分人也渴望有自己的事业,但是这毕竟不可强求,所以只有两种选择:

  • 深度优先,持续深入自己的工作领域,深耕并保持分享输出,保持业界影响力在业界扎根
  • 广度优先,做其他副业,甚至群里有朋友转 PM

很多真正喜欢编程的人都更倾向于第一种选择,但是一直用心写的技术文章被各种无良网站和公众号剽窃走甚至被「套娃」,还是挺让人伤心和打击人积极性的。因此很多技术大佬们纷纷转投知识付费领域,将自己用心写的内容付费传播,其中不乏一些 Level 很高的大厂专家 Title 的牛人。其实这些人的薪资并不低,从专栏定价和付费用户数量来看算一算也不一定有一个月工资的收益,我理解更多的是借助知识付费对自己产生正向激励让自己保持分享输出。

商业化

涉及付费就有利益,有利益的地方就有商机,所以知识付费经历数年之后慢慢有了成熟的体系。

商业化的加入喜忧参半:

  • 好处是有专业的商业团队帮助真正想要在自己领域深耕并扎根的技术手艺人做推广,让更多优秀的内容和创作者被人知晓
  • 坏处是商业团队缺乏内容把控,内容质量出现下降,不少付费用户表示被当成了「技术韭菜」,被收了智商税,非常伤心

我觉得目前国内做的比较好的平台就是「极客时间」了,其他的平台也有比较小众做的不错的比如「小专栏」。不过平台不重要,内容这块我觉得更多的是看准内容创作者本身,高质量内容的创作者无论是在哪个平台都会对自己严格要求的,这种人也会比较有口碑并看中自己的口碑,久而久之就树立起了个人品牌,非常不容易。

总结

Emmmmm…貌似也没什么好总结的,如你所见这是一篇吹水文。

如果你想系统全面的学习一些经典基础知识,我建议直接买书,比如想学操作系统推荐买《深入理解计算机系统》,这种口碑爆表且经典的教材类书籍是我这边的不二推荐;如果你想快速系统的掌握一些新进技术,我建议可以尝试从知识付费领域下手。

Flutter 状态管理 0x00 - 基础知识及 State.setState 背后逻辑

前言

Emmmmm…正式开始文章前先交代个事儿,2019 开年计划那篇文章被我删了。原因没啥特别的,就真的只是脸疼…

嘛~ 好久没更 Blog 了,最近笔者正在学习 Flutter 相关的知识(貌似现在学也不算特别晚),所以后续可能会有一波连续的 Flutter 相关的更新(关键字已加粗 😂)。

因为之前我的文章大多是源码剖析相关的,所以这次决定换种方式先从 Flutter 开发的状态管理聊起。由于个人能力不足、水平有限,文章中难免有纰漏和谬误,加上工作确实比较忙可能没有时间校验,希望发现的朋友能够在文章下方评论交流讨论(主要是避免误人子弟,不过评论应该需要科学上网)。

本文会简单介绍 Flutter 以及声明式编程思想和代码画风,对比 StatelessWidget & StatefulWidget 这两个重要的 Widget,再聊聊 setState 背后的那些事儿。

索引

  • Flutter 简介
  • 声明式 UI & 响应式编程
  • Flutter 如何渲染 Widget
  • StatelessWidget & StatefulWidget
  • State.setState 背后的那些事儿

Flutter 简介

Flutter 是 Google 开源的 UI 工具包,帮助开发者通过一套代码库高效构建多平台精美应用,支持移动、Web、桌面和嵌入式平台。

Write once, run anywhere.」始终是大前端开发乃至整个程序界的一个永恒的话题。诚然 Flutter 的目标也是如此,不过它与之前业界已经存在的 React Native、Xamarin、Hybrid 等框架有何不同?

从上图可以看到 Flutter 的设计整体上有三层抽象:

  • 最上层 Framework 封装好了 Material 和 Cupertino 这两种分别对应 Android 和 iOS 官方设计风格的 UI 库,除此之外还包含了渲染、动画、绘制、手势等等,这一层主要是提供给开发者便捷构建 App 使用的,其在开发时为 JIT、发布时为 AOT 的特性兼顾了开发调试的便捷与线上运行的高性能;
  • Engine 作为中间层,使用 C/C++ 实现了一套高性能、可移植的 Runtime,屏蔽了平台间差异的同时支撑了上层的 Flutter 应用。
  • Embedder 由平台指定语言实现,提供了 Flutter 所需的事件循环、线程、渲染等基础 API。

Flutter 通过这套机制接管了最底层的系统接口,提供了一整套从底层渲染逻辑到上层开发语言的完整解决方案,使得它有着超越 React Native 的高保真、多端一致的体验,和超越 Web 容器的高性能渲染能力。

声明式 UI & 响应式编程

声明式 UI

这张图很好的描述了声明式 UI 的核心思想,简单来说就是通过 state 作为入参根据已经写好的构建 func 就能得到我们想要的 UI 效果。举个 🌰 更直观:

默认的 Flutter Demo 界面:

对应的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.display1,
),
],
),
),
floatingActionButton: FloatingActionButton(
// _incrementCounter 内部逻辑可忽略
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);

In flutter, everything is a widget.

Emmmmm…可以注意到上面的代码中 children: <Widget>[...] 一行,Flutter 开发中就是通过这种方式嵌套组合 Widget 来描述用户界面的。从这一角度讲,Widget 是对一部分用户界面的不可变描述。

默认的 Flutter Demo 跑起来很简单,推荐大家亲身体验。简单说一下我的感受,声明式 UI 的优势很大,具体表现为只需要在写 UI 时将不同 state 对应的 UI 展示考虑并描述清楚就可以省去后续 state 变更时命令式 UI 需要手动管理视图层级(新增或删除视图元素)和更新属性(颜色、字号等)等麻烦。

值得一提的是 SwiftUI 也使用了声明式 UI 的思想(声明式 UI 才是未来)。

响应式编程

响应式编程 ,在计算机领域,响应式编程是一个专注于数据流和变化传递的异步编程范式。
这意味着可以使用编程语言很容易地表示静态(例如数组)或动态(例如事件发射器)数据流,
并且在关联的执行模型中,存在着可推断的依赖关系,这个关系的存在有利于自动传播与数据流有关的更改。

响应式编程对于大家来说应该早已不是什么新鲜事物了,单在移动客户端领域就有诸如 ReactiveCocoaRxSwiftRxJava、RxXxx…简单来说这种编程思想或者说范式下开发者只需关注可能存在的数据状态以及与之对应的逻辑从而大大减轻了维护这些对应关系与状态细节的工作负担。因为用过的人都说好,所以目前渐渐被推为移动客户端乃至整个大前端的主流编程思想。

不难看出其实声明式 UI 和响应式编程从思想上是完全契合的,我们只需要将数据流对应到 UI=f(state) 公式中的 state 就可以了。

Flutter 如何渲染 Widget

在介绍 StatelessWidgetStatefulWidget 之前先要了解一下 Flutter 的渲染模型,简单来讲 Flutter 在渲染 Widget 时用到三棵树:

  • Widget,负责描述 Element 的配置。
  • Element,负责管理 Widget 和 RenderObject 的生命周期。
  • RenderObject,负责控制尺寸,布局和绘制工作。

一言以蔽之,Element 会根据我们书写的 Widget 对其的配置描述来生成并管理对应的 Element 与 RenderObject,由 RenderObject 负责最终的绘制工作。

NOTE: Element 会根据 Widget 的描述最大程度复用现有 Element 与 RenderObject 以提升性能。

StatelessWidget & StatefulWidget

StatelessWidget

StatelessWidget, A widget that does not require mutable state.

StatelessWidget 如其名 Stateless,它不需要追踪 State 并根据 State 更新 UI,所以一般 StatelessWidget 内部的属性用 final 修饰声明且构造方法一般以 const 修饰。

NOTE: const 修饰的 Widget 构造方法使用时可以提效。

StatefulWidget

StatefulWidget, A widget that has mutable state.

StatefulWidgetStatelessWidget 一样,也可以通过一系列(组合嵌套)其他更具体描述 UI 的 Widget(比如 Text)来构建部分用户界面。区别在于 Stateful,即它需要追踪 State 并根据 State 更新 UI。

区别

StatelessWidgetStatefulWidget 大概是我们用 Flutter 技术栈开发 App 时最常打交道的两个 Widget 了。

共同点:

  • 这两个 Widget 都可以用来通过对其他一系列 Widget 构建完成一部分用户界面的封装。

不同点:

  • StatelessWidget 更适用于一些不需通过用户交互或其他原因通过 State 控制更新的 UI 封装。
  • StatefulWidget 更适用于追踪某个会根据用户交互或其他因素影响的 State,并根据最新的 State 实时更新的 UI 封装。

此外,Flutter 对于 StateLessWidgetStatefulWidget 的绘制也有差异:

  • StatelessWidget 通过 StatelessElement.build 触发 build
  • StatefulWidget 通过 StatefulElement.build 触发 State.build

State.setState 背后的那些事儿

NOTE: 下面分析的 State.setState 源码版本为 Flutter SDK v1.9.1+hotfix.6。

我们还以 Flutter 默认 Demo 来分析,在 Demo 中我们点击界面右下角的 FloatingActionButton (就是蓝色带有 + 号的圆形按钮)之后会刷新界面,屏幕中间的 Text Widget 会显示按钮被按下的次数。

FloatingActionButton 点击之后调用了 _incrementCounter 方法:

1
2
3
4
5
6
7
8
9
10
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;

void _incrementCounter() {
setState(() {
_counter++;
});
}
...
}

_MyHomePageState._incrementCounter 内部逻辑也仅仅是调用了 State.setState 而已。正如 Flutter 所宣传的公式 UI = f(state) 一样,开发者只需要调用 State.setState 传入 VoidCallback 内部写好相关数据更新逻辑即可更新 UI,那么 Flutter 是如何做到这一点的呢?

State.setState 背后逻辑

State.setState 背后调用嵌套较多,实际所做的事情理解起来却很简单。如不喜在文内阅读源码可直接跳转至「总结 State.setState 背后逻辑」小节看相关逻辑总结。

State.setState

1
2
3
4
5
6
7
8
9
10
11
12
@optionalTypeArgs
abstract class State<T extends StatefulWidget> extends Diagnosticable {
...
@protected
void setState(VoidCallback fn) {
// assert ...
final dynamic result = fn() as dynamic;
// assert ...
_element.markNeedsBuild();
}
...
}

可见 State.setState 内除去断言的逻辑只有两行代码:

  • 执行入参 VoidCallback 逻辑,Demo 中对应 _counter++;
  • 执行 _element.markNeedsBuild();

Element.markNeedsBuild

1
2
3
4
5
6
7
8
9
10
11
12
13
14
abstract class Element extends DiagnosticableTree implements BuildContext {
...
void markNeedsBuild() {
// assert ...
if (!_active)
return;
// assert ...
if (dirty)
return;
_dirty = true;
owner.scheduleBuildFor(this);
}
...
}

Element.markNeedsBuild 内部的逻辑也很简单明了:

  • 如此 Element 不再活跃则直接 return 无需做任何事
  • 如此 Element 已经被标记为 dirty 也无需重复标记直接 return
  • 以上条件不满足时说明此 Element 是活跃且需要重新构建的,所以标记为 dirty 后调用 owner.scheduleBuildFor(this);

BuildOwner.scheduleBuildFor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class BuildOwner {
...
void scheduleBuildFor(Element element) {
// assert ...
if (!_scheduledFlushDirtyElements && onBuildScheduled != null) {
_scheduledFlushDirtyElements = true;
onBuildScheduled();
}
_dirtyElements.add(element);
element._inDirtyList = true;
// assert ...
}
...
}

BuildOwner.scheduleBuildFor(this); 内部逻辑也不复杂:

  • !_scheduledFlushDirtyElements && onBuildScheduled != null 可以理解为如果还没有安排过刷新此 BuildOwner 下被标记为 Dirty 的 Elements 且安排构建的逻辑不为空,满足以上条件则将此 BuildOwner 安排刷新 Dirty Elements 的标记置为 true
  • 将传入的 Element 加入此 BuildOwner 管理的 _dirtyElements 并将此 Element _inDirtyList 标记为 true

BuildOwner.onBuildScheduled

其实 onBuildScheduled(); 跟下去是 BuildOwner 内部属性 onBuildScheduled,类型为 VoidCallback,在 WidgetsBinding.initInstances 时被赋值为 WidgetsBinding._handleBuildScheduled:

1
2
3
4
5
6
7
8
9
10
mixin WidgetsBinding on BindingBase, SchedulerBinding, GestureBinding, RendererBinding, SemanticsBinding {
...
@override
void initInstances() {
...
buildOwner.onBuildScheduled = _handleBuildScheduled;
...
}
...
}

WidgetsBinding 可以理解为 Widget 层与 Flutter engine 之间的胶水层,篇幅原因不展开讲,我们直接看 WidgetsBinding._handleBuildScheduled

1
2
3
4
5
6
7
8
mixin WidgetsBinding on BindingBase, SchedulerBinding, GestureBinding, RendererBinding, SemanticsBinding {
...
void _handleBuildScheduled() {
// assert ...
ensureVisualUpdate();
}
...
}

SchedulerBinding.ensureVisualUpdate

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
mixin SchedulerBinding on BindingBase, ServicesBinding {
...
void ensureVisualUpdate() {
switch (schedulerPhase) {
case SchedulerPhase.idle:
case SchedulerPhase.postFrameCallbacks:
scheduleFrame();
return;
case SchedulerPhase.transientCallbacks:
case SchedulerPhase.midFrameMicrotasks:
case SchedulerPhase.persistentCallbacks:
return;
}
}
...
}

这里通过当前 SchedulerPhase 的状态进行取舍,仅在 SchedulerPhase.idle & SchedulerPhase.postFrameCallbacks 时调用 scheduleFrame();

SchedulerPhase.scheduleFrame

1
2
3
4
5
6
7
8
9
10
11
mixin SchedulerBinding on BindingBase, ServicesBinding {
...
void scheduleFrame() {
if (_hasScheduledFrame || !_framesEnabled)
return;
// assert ...
window.scheduleFrame();
_hasScheduledFrame = true;
}
...
}

这里的逻辑更简单:

  • _hasScheduledFrame || !_framesEnabled 可理解为如果已经安排过 Frame 或当前 Frame 不可用,这种情况直接 return
  • 不满足上述情况则调用 window.scheduleFrame(); 然后标记 _hasScheduledFrametrue

Window.scheduleFrame

1
void scheduleFrame() native 'Window_scheduleFrame';

这里的 Window.scheduleFrame 是 Native 方法,可以理解为通过 Window 与 Flutter engine 打交道,注册了 VSync 回调。

总结 State.setState 背后逻辑

简单来说 State.setState 就干了这么几件事:

  • 执行入参 VoidCallback 逻辑,即执行开发者写好的信息变更逻辑
  • 尝试将当前 StatefulWidget 对应的 StatefulElement 标记为 dirty
  • 通过 BuildOwner.onBuildScheduledSchedulerPhase.scheduleFrame 再到 Window.scheduleFrame 一步步完成了 VSync 的回调注册

注意上面 State.setState 第二条主要逻辑就是把 State 关联的 Element 标记为 dirty。在 VSync 回调后会通过 Native 到 Flutter engine 调用 Flutter _drawFrame 方法,将之前标记为 dirty 的 Element 重新构建,最终会执行到开发者熟悉的 State.build 方法。

State.build 是如何被执行的

这里因为篇幅原因简单描述下后续逻辑,毕竟本文重点不在于 Flutter 如何渲染 Widget:

  • State 是开发者重写 StatefulWidget.createState 返回的
  • State 对应的 Element 是 StatefulWidget.createElement 返回的,类型为 StatefulElement
  • StatefulElement 被标记为 dirty 后在 Flutter _drawFrame 时重新构建会调用 StatefulElement.build,其源码为 Widget build() => state.build(this);
  • state.build(this) 中的 state 就是我们的 State,开发者写的 State.build 方法就这样被执行了

总结

本文是 Flutter 状态管理的开篇,为了照顾一些还没来得及学习 Flutter 或者刚入门 Flutter 的初心者所以文章从前到后做了一些铺垫介绍过渡。文章内容也比较浅显易懂,基本上是围绕 Flutter 创建 App 默认的 Demo 来展开讲解一些基本的 Flutter 知识点:

  • Flutter 渲染 Widget 的三颗树概念
  • StatelessWidget & StatefulWidget
  • State.setState 背后逻辑

由于状态管理这个话题非常大且复杂,文章因为篇幅原因就到这里,后续的文章(如果有的话)应该不会再花大篇幅做 Flutter 基础知识的铺垫和过渡了(但是该有的前置知识点肯定还会有)。时间紧张,文章难免出现谬误,估计自己也没有时间做校对了,有问题还望在评论区提醒。

扩展补充

写到这里我意识到 Flutter 状态管理这个话题下可以引出很多值得挖掘的内容,而且笔者坚信声明式 UI + 响应式编程会是未来移动客户端乃至整个大前端的主流编程范式。鉴于此,后续的文章计划围绕 Flutter 状态管理逐步深挖,内容也将在 Flutter 技术栈的基础上做适度横向对比。

Emmmmm…后续的文章将会发布在我的《Flutter 状态管理》小专栏。至于专栏定价嘛,目前随便定了 ¥69(作为小透明的我瑟瑟发抖 ing~)。其实倒也不是很在意能卖出多少份,或者从中收到多少钱,毕竟自己写文章的精力拿去随便接个朋友的私活都应该比这次通过小专栏赚到的多。比起收入更多的是对自己的鞭策吧,毕竟是收费的东西,只要有一个人订阅了这个专栏就意味着对我的肯定,我就有责任把这个方向挖掘的更深入,同时文章质量也应该比博客内容更高。

其实早在去年就有幸被小专栏的开发者@安卓大王子邀请去小专栏写文章,不过当时自己感觉没什么好的内容方向所以只是开通了专栏但没有任何输出。时至今日终于找到了一个值得深挖的方向,自然而然想到了这个平台。个人感觉小专栏这个平台没有太重的商业化味道,这点很讨喜,在微信上面的阅读体验也比自己发公众号要好一些,更难能可贵的是省去了发公众号的繁琐操作流程。可能正是商业化氛围弱的原因,上面的文章质量还都挺不错的,推荐各位读者朋友可以上去试读或者写作。