探究 React Native 中 Props 驱动的 SVG 动画和 Value 驱动动画

引言

一直以来,动画都是移动开发中极为特殊的一块。一方面,动画在交互体验上有着不可替代的优越处,然而另一方面,动画的开发又极为的耗时,需要消耗工程师大量的时间用于开发和调试。再来看前端,前端的动画实现,经过多年的发展,已分为 CSS3 动画和 JavaScript 动画。

React Native 作为一个复用前端思想的移动开发框架,并没有完整实现CSS,而是使用JavaScript来给应用添加样式。这是一个有争议的决定,可以参考这个幻灯片来了解 Facebook 做的理由。自然,在动画上,因为缺少大量的 CSS 属性,React Naive 中的动画均为 JavaScript 动画,即通过 JavaScript 代码控制图像的各种参数值的变化,从而产生时间轴上的动画效果。

React Native 的官方文档已经详细地介绍了 React Native 一般动画的使用方法和实例,在此不再赘述。然而阅读官方文档后可知,官方的动画往往是给一个完整的物体添加各种动画效果,如透明度,翻转,移动等等。但是对于物体的自身变化,比如如下这个进度条,明显是在旋转的同时也在伸缩,则缺乏必要的实现方法。这是因为,动画的本质既是图形的各种参数的数值变化的过程,文档中的 Animated.Value 就是用作被驱动的参数,可以,想要让一个圆环能够伸缩,就必须让数值变化的过程,深入到图形生成的过程中,而不是如官方文档的例子一样,仅仅是施加于图形生成完毕后的过程,那么也就无法实现改变图形自身的动画效果了。

拙作初窥基于 react-art 库的 React Native SVG已讨论了 React Native 中静态 SVG 的开发方法,本文则致力于探究 React Native 中 SVG 与 Animation 结合所实现的 SVG 动画。也就是可以改变图形自身的动画效果。此外还探究了 Value 驱动动画在实现方法上的不同之处。

Props 驱动的 SVG 动画

本节即以实现一个下图所示的旋转的进度条的例子,讲述 React Native SVG 动画的开发方法。

rotating-wedge

Wedge.art.js 位于 react-art 库下 lib/ 文件夹内,提供了 SVG 扇形的实现,然而缺乏对 cx, cy 属性的支持。另外拙作之前也提到了,Wedge中的扇形较为诡异,只有一条半径,为了实现进度条效果我把另一条半径也去掉了。我将 Wedge.art.js 拷贝到工程中,自行小修改后的代码如下。

然后就是实现的主体。其中值得关注的点是:

  1. 并非任何 Component 都可以直接用 Animated.Value 去赋值 Props,而需要对 Component 做一定的改造。AnimatedAnimated.createAnimatedComponent(Component component),是 Animated 库提供的用于把普通 Component 改造为 AnimatedComponent 的函数。阅读 React Native 源代码会发现,Animated.Text, Animated.View, Animated.Image,都是直接调用了该函数去改造系统已有的组件,如Animated.createAnimatedComponent(React.Text)
  2. Easing 库较为隐蔽,明明在react-native/Library/Animated/路径下,却又需要从React中直接引出。它为动画的实现提供了许多缓动函数,可根据实际需求选择。如 linear() 线性,quad() 二次(quad明明是四次方的意思,为毛代码实现是t*t….),cubic() 三次等等。官方文档中吹嘘 Easing 中提供了 tons of functions(成吨的函数),然而我数过了明明才14个,233333。
  3. 该动画由起始角度和终止角度两个变化的参数来控制,因此,两个Animated.Value需要同时启动,这涉及到了动画的组合问题。React Native 为此提供了 parallelsequencestaggerdelay 四个函数。其主要实现均可在react-native/Library/Animated/Animate中找到,官方文档中亦有说明。这里用的是Animated.parallel

开发中遇到的问题有:

  1. 该动画在 Android 上可以运行,但是刷新频率看上去只有两帧,无法形成一个自然过渡的动画,笔者怀疑是 React Native Android 对 SVG 的支持仍有缺陷。
  2. SVG 图形和普通 React Native View 的叠加问题,目前我还没有找到解决方法。感觉只能等 React Native 开发组的进一步支持。
  3. 动画播放总会有一个莫名其妙的下拉回弹效果,然而代码上没有任何额外的控制。

Value 驱动的动画

接下来看 Value 驱动的 SVG 动画。先解释一下 Value 和 Props 的区别。<Text color='black'></Text>,这里的 color 就是 Props,<Text>black</Text>这里的 black 就是 value。

为什么要特意强调这一点呢,如果我们想要做一个如下图所示的从1到20变动的数字,按照上节所述的方法,直接调用 Animated.createAnimatedComponent(React.Text)所生成的 Component ,然后给 Value 赋值一个Animated.Value(),然后Animated.timing…,是无法产生这样的效果的。

rising-number

必须要对库中的createAnimatedComponent()函数做一定的改造。改造后的函数如下:

为了获取必须要用到的AnimatedProps,笔者甚至违背了道德的约束,访问了双下划线前缀的变量Animated.__PropsOnlyForTests,真是罪恶啊XD。

言归正传,重要的修改有:

  1. 修改了 attachProps 函数。对于任何变动的 props,原来的代码会试图使用 setNativeProps 函数进行更新,若 setNativeProps 函数为空,才会使用 forceUpdate() 函数。对于 props,setNativeProps 函数是可行的,然而对 value 无效。我猜测,setNativeProps 方法在 Android 底层可能就是 setColor() 类似的 Java 方法,然而并没有得到实证。目前这种 forceUpdate,由注释知,是彻底更新了整个 Component,相当于先从 DOM 树上取下一个旧节点,再放上一个新节点,在性能的利用上较为浪费。
  2. 使用 PropTypes.xxx.isRequired 来进行参数的类型检查。PropTypes 检查支持的类型可在 react-native/node_modules/react/lib/ReactPropTypes.js 中看到,在此不再赘述。
  3. Animated.value() 从1到20变化的过程是一个随机采样的过程,并不一定会卡在整数值上,因此还需要做一些小处理。

值得注意的是,该动画在 Android 上虽然可以正常运行,但也存在丢帧的问题,远远不能如 iOS 上流畅自然。对于这一点,只能等待 Facebook 的进一步优化。

全部的代码如下:

初窥基于 react-art 库的 React Native SVG

技术背景

在移动应用的开发过程中,绘制基本的二维图形或动画是必不可少的。然而,考虑到Android和iOS均有一套各自的API方案,因此采用一种更普遍接受的技术方案,更有利于代码的双平台兼容。

art是一个旨在多浏览器兼容的Node style CommonJS模块。在它的基础上,Facebook又开发了react-art ,封装art,使之可以被react.js所使用,即实现了前端的svg库。然而,考虑到react.js的JSX语法,已经支持将<circle> <svg>等等svg标签直接插入到dom中(当然此时使用的就不是react-art库了)此外还有HTML canvas的存在,因此,在前端上,react-art并非不可替代。

然而,在移动端,考虑到跨平台的需求,加之web端的技术积累,react-art成为了现成的绘制图形的解决方案。react-native分别在0.10.0和0.18.0上添加了iOS和Android平台上对react-art的支持,当然,没有文档。在文档基本等于没有的情况下,笔者苦逼地翻源代码,为大家带来了(全球首发?=_=)的入门文档。

示例代码

推荐大家采用react-art自带的Example: Vector-Widget。React.js和React-Native的区别,只在于下文所述的ART获取上,然后该例子就可以同时应用在Web端和移动端上了。

成功运行Vector-Widget后的效果图:
rotating-react

Vector-Widget额外实现了旋转,以及鼠标点击事件的旋转加速响应。Web端可以看到点击加速,但是在移动端无效,原因是React Native并未对Group中onMouseDown和onMouseUp属性作处理。本文着重于静态svg的实现,暂时无视动画部分效果即可。

此外还可以用该例子感受一下叹为观止的svg动画的性能占用(摊手)。

原理和调用

获取ART

package.json中需要引入art库,笔者的版本设置是art: ^0.10.0

Android与iOS

或者使用ES6的Destructuring特性:

Web端

基本组件

获取方式

接下来的所述的代码,web端和移动端都是通用的,这也是React Native的诱惑所在。

Surface

所有的svg component必须被一个Surface标签所包含。
Props如下:

  • width: Surface的宽度。
  • height: Surface的高度。
  • style: margin系列和padding系列都生效。

Group

Group用于组合art component。比如在一个函数中返回多个svg component的情况,此时就必须要用<Group>包一下,否则即报错。<Group>可以嵌套使用。

style:margin和padding系列均无效,我怀疑不接受style。

Shape

Shape用于生成路径,语法与svg中的<path>很相似。Shape的Props如下:

  • d: 语法与svg规范相同
  • stroke: 线条颜色,”#FFFFFF”的形式
  • strokeWidth: 线条宽度,{3}的形式
  • transform:接受 new ART.Transform()生成的object,具体见下文Transform条目。

Path

语法更近似于移动端。使用方法:

可以看到,取出的Path是一个构造函数。Path对象的中的函数功能如下,大多与svg规范一致,我就再啰嗦一遍了。svg规范中<path>的d属性请参见https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d :

  • push():
  • reset(): 清空Path
  • move(x, y): 等同于’m’,移动到目的坐标,参数x和y是相对目标下的目的坐标
  • moveTo(x, y): 等同于’M’,与move只差别在x和y是绝对坐标。
  • line(x, y): 等同于’l’,从一个坐标点到另一个坐标点画直线,参数x和y是相对坐标下的目的坐标
  • lineTo(x, y): 等同于’L’,与line只差别在x和y是绝对坐标。
  • arc(x, y, rx, ry, outer): 等同于’a’,从一个坐标点向另一个坐标点画椭圆曲线,x和y是相对坐标下的目的坐标,rx和ry是椭圆的长轴半径和短轴半径,outer只有0和1两个数字,代表是大角度还是小角度。
  • arcTo(x, y, rx, ry, outer): 等同于’A’,与arc只差别在x和y是绝对坐标。
  • curve(2个,4个或6个参数): 从一个坐标点向另一个坐标点画贝塞尔曲线。
    • 当参数为两个时,等同于’t’,绘制光滑二次贝塞尔曲线。
    • 当参数为4个时,等同于’q’,绘制二次贝塞尔曲线。
    • 当参数为6个时,等同于’c’,绘制三次贝塞尔曲线。
    • 有些精通SVG的同学这时候可能就要问我了,不对啊,二次贝塞尔曲线和光滑三次贝塞尔曲线的参数都是4个,你这里没有光滑三次啊?因为开发的同学留坑没写了呀(微笑)。

Transform

实现代码路径:art/core/transform.js

Transform对象中的函数:

  • transform(xx, yx, xy, yy, x, y): transform的相对坐标版本
  • transformTo: 完整的矩阵变换,把这张位图上所有的点都做一次矩阵乘法,得到的新位图,公式如下图所示

matrix

  • translate(x, y): 位移
  • move: 相对于原参考点坐标,增减参考点的x,y坐标
  • moveTo: 等同于translateTo,容易误以为是move的绝对左边版本,吐糟不能
  • scale(x, y): 将一个元素拉伸或者压缩指定的倍数,x是宽度缩放比例,y是长度缩放比例,如果只有一个参数,则x = y。如
  • scaleTo: 在缩放的同时保持原来的长宽比。例子和效果图如下:

three-rec

  • rotate(deg, x, y): 将一个元素旋转角度deg。x和y则是用于指定旋转的原点。

效果如下图所示:

rotate-rec2
rotate-rec1

react-art中的lib

引入lib中的module

在react-art的库中,有个神奇的lib文件夹,下面除了ReactART.js以外,还有Circle.art.js,Rectangle.art.js,Wedge.art.js等,其中Circle和Rectangle分别对应于svg规范中的圆形<circle>,矩形<rect>,而Wedge则是用于生成扇形。当然,这些module都很不完善,很有可能需要二次开发,如果这样,推荐拷贝库文件到工程中再行修改。

引入它们的语句为var Circle = require('react-art/lib/Circle.art');。这种不同寻常地引用方式是Facebook开发和维护的fbjs引入的,react-native依赖了fbjs,而所有需要被输出的js文件的头部都会以// @providesModule Circle.art
的形式标明。

Circle

使用示例:

值得一提的是,Circle.art.js是个半成品,可以看到它根本没有实现svg规范中的cx和cy,因此画出来的圆的圆心始终在左上角,显示出来的也就只有半个圆。请在实际应用中自行实现,或者使用笔者提供的修改版本

当然,不用cx和cy的话,也可以设置tranform来实现平移。如:

两种方法都可以达到平移效果,但是cx, cy的方式相对来说更简洁,可读性也更好。

Rectangle

使用示例:

使用上述代码,就很直观看到这个module的缺陷了,矩形四条边不等宽(扶额)。
rectangle
此外还接受的props有:

  • radius
  • radiusTopLeft
  • radiusTopRight
  • radiusBottomLeft
  • radiusBottomRight

这里的radius指的是圆角矩形的圆角半径,接受的值类型为数字,radius为四个角的通用半径,但如果设置了具体某个角的半径,则用后者。

Wedge

Wedge是楔子的意思,然而在这里却是生成各种角度的扇形=_=。
使用示例:

生成的图形如下图:

wedge

可选Props为innerRadius,用于生成一个圆环扇形,如下图。

binWedge

呐,一看这个module也是半成品,如果stroke有颜色而fill为白色就露馅了,曲线没闭合。因此,如有需求,请自行弥补。

技术缺陷

除了之前所提到的各种各样的实现上的不完善以外,svg规范在React Native上应用的最大问题在于,有一大批web上的svg支持的css属性,还没有在React Native上实现。

以stroke类的一大批属性为例,目前可用的只有线条颜色stroke和strokeWidth,而如stroke-dasharray,stroke-dashoffset这样的可以用之实现神奇的描边等效果的CSS属性,目前还没有被支持。

在性能占用方面, 静态svg尚可接受,但如果是svg annimation,实测红米Note 2上的CPU占用率即可达50%左右,故其在生产环境的大规模应用,恐怕还需进一步的性能优化。

多React Native项目时依赖管理的最佳实践

在实际开发过程中,经常需要同时运行和修改多个React Native工程,比如运行github上的开源项目以观察某种控件的实际效果。那么此时,各项目下的初始化(npm install)就会非常的痛苦,因为React Native的文件非常大,以0.17.0为例,安装后达到309MB。尽管,我们可以通过阿里npm等镜像站的方式加速下载的过程,但是下载后的进一步编译也非常地耗时。

此外,多React Native工程还带来了React Native自身的冗余,如果创建了十几个工程,那么多占用的空间轻松达到3GB以上,非常地不友好。

npm link原理

我的解决思路是:用npm link替代npm install。npm link [package-name]命令的原理是,去检索是否已经全局安装该package,去prefix/lib/node_modules/下检索是否已经安装了当前的package,如果是,则直接用软链接的方法在本地路径指向全局package。如果没检索到,则会先在全局路径下安装该package,再去建立软链接。npm获取全局路径的命令是:npm config get prefix

需要注意的是,有package.json的路径下,不要类比npm install,就这么执行npm link。此时npm link会把当前路径作为一个本地package,在全局路径下创建一个软链接。由此可知,npm link并不会像npm install一样,读取package.json中的依赖并自动配置。

配置过程

简单三步搞定。然后运行react-native run-android,打个Android包检测一下。

纳尼,报错如下:

Looks like you installed react-native globally, maybe you meant react-native-cli?
To fix the issue, run:
npm uninstall -g react-native
npm install -g react-native-cli

原因很简单,react-native框架其实由两个部分组成:react-native和react-native-cli,前者用于提供编译环境,后者则是封装了react-native开发过程中所要用到的命令,如react-native start,实质就是封装了sh ./node_modules/react-native/packager/packager.sh

官方文档要求全局安装react-native-cli,但是局部安装react-native,这是有原因的。如果你先全局安装了react-native-cli,会在/usr/local/bin下生成一个名为react-native的软链接,其指向为:react-native -> ../lib/node_modules/react-native-cli/index.js*。而随后再次”全局”安装react-native的时候,又会生成一个名为react-native的软链接,覆盖了react-native-cli安装时生成的软链接,其指向是:../lib/node_modules/react-native/local-cli/wrong-react-native.js。由此可见,React Native官方已经意识到了这个问题,然而不知何原因并不推荐全局安装React Native。然而笔者从节约硬盘空间和加快初始化的角度,认为还是有必要全局安装React Native,从而快速npm link的,所以有必要研究该报错的原理。

因此,针对这个报错,两种解决方法:

  1. npm install -g react-native,再npm install -g react-native-cli。然而,如果以后使用过程中又升级了全局React Native,此时需看方案2。
  2. cd /usr/local/bin ln -s react-native ../lib/node_modules/react-native-cli/index.js,即可重新创建一个指向react-native-cli的软链接。如果prefix的地址不是默认的,则ln -s prefix/lib/node_modules/react-native-cli/index.js

缺陷

当前这个自动添加统一依赖的方法,存在一个问题。所有依赖于全局路径下的React Native都必须是一个版本的,npm link并没有提供多版本号依赖的解决方法。因此,还是建议选择一个常用的React Native版本安装在全局路径,个别需求其他版本号的React Native的项目,使用npm install来配置局部依赖。

插说一句,npm自身的依赖管理设计还是非常优秀的,然而React Native实在是太大了,而且我们完全有理由相信,他会更大。他其实应该是与Android SDK, Java SDK一般重量级的开发SDK,因此更应该借鉴rvm,设计一个React Native Version Manager。然而却委身于node_modules,因而产生了这种无奈的冗余。

解决OS X 10.11上Ducky 2108s机械键盘无响应的问题

我的Macbook Pro mid 2013在升级到OS X 10.11后,ducky 2108s插上去后无反应。在StackExchange中找到了该问题的解决方法,总结如下:

  1. 怀疑OS X 10.11进一步削弱了USB口的供电能力,致使部分机械键盘可能会因为供电不足而没有反应。笔者在之前版本的OS X的使用中也遇到过,机械键盘用着用着可能就突然没有响应了,通常都是重启解决。解决办法:购买独立供电的USB Hub,出门左拐万能的淘宝
  2. ducky 2108和2108s两款机械键盘都在OS X下无法使用,无论是否使用了外接供电的USB Hub。联系support@duckychannel.com.tw 后得知,需要刷新键盘的固件。刷新只能在Windows下进行,但刷新完成后,键盘即可在OS X上正常使用。本文后附固件文件(仅适配2108和2108s)、固件刷新程序和固件刷新帮助手册。其他版本固件请联系 support@duckychannel.com.tw 。

点击下载 ducky_2108_firmware_and_tool.zip

点击下载 firmware update流程Chinese.pdf

探究Express Request中的url

url在解析过程是会被逐级删减。如:

采用第一种写法,浏览器会得到404 Not Found。第二种则正常。原因即在于,url在解析过程是会被逐级删减。

通过node-inspector调试上面的例子,可得到运行中req.url,req.originalUrl,req.baseUrl的值。

解释如下:
req.url = req.originalUrl – req.baseUrl。也是router.get传入的匹配路径的匹配对象。这也就可以解释上面的例子的运行结果了。all,post,put等等函数同理。

req.originalUrl与req.url类似。不同的是,它用于备份最初的请求url,从而你可以随意重写req.url来到处跳转,而不用担心丢失初始的url。比如,用于挂载中间函数的app.use()就会重写req.url来压缩挂载点的长度。

req.baseUrl存储当前router挂载的路径。即app.use('/path', router);。此时/path就是baseUrl。即便在挂载的时候使用的是正则等匹配表达式,baseUrl存储的也会是匹配的字符串,而不是正则表达式。

next函数在Express Router中的应用

例1

运行结果:
Hello!

运行结果:
Hello!
World!

上述例子说明了,next函数的作用是调用挂载在同一路径下的下一个中间函数。换而言之,如果没有next(),这一次request的处理过程就结束了。

例2

换一种写法:

无next函数运行结果:
Hello!

有next函数运行结果:
Hello!
World!

解释

需要说明的是,Express文档中出现了很多次middleware这个词。其本义为中间件,未免太大了,在这里译作中间函数。

中间函数栈是开发者把许多中间函数按一定顺序组织起来的,新收到的请求被开发者定义的第一个中间函数接收并处理,随后被传给下一个中间函数,直到栈底,就像一个自来水管道一样。这也正是为何在Express文档中,next函数的作用被描述为control flow。

Express中的文档中写到:
app.use函数的参数function,可以接受单个函数,多个函数,函数数组,以及混用上述三种形式。这是中间函数栈的一种组成方式,如例2。另一种组织方式为重复多次调用app.use函数来挂载同一路径下的多个函数,如例1。

因此可见,重复多次调用app.use函数来挂载同一路径下的多个函数,与使用多参数传入函数,效果相同。另外,例2又一次佐证了next函数的作用,即传递给控制权给挂载在当前路径的中间函数栈的下一个函数。

我的大学

大学的奋斗至此,初见曙光,就此记录一下自己的感慨。

大一的时候刻苦地学习高数,晚上刷题刷到一点钟,但是成绩依然很差。由此明白了,自己实在不是学习数学的料。幸而在做科创时窥见了java的大门,大一下的时候又学会了C,一直坚持着有空就刷HOJ。虽然刷的很少很简单的水题,但至少,对敲代码更为熟练了。但其实内心非常的苦闷,因为刘汝佳的书完全看不懂,去参加校内ACM又被打击的无以复加。内心对本专业和编程都充满了绝望。

大二的时候,鬼使神差地在大二的创新研修课上选了输入法,从此加入实验室,从而看到了一个崭新的大门。惊才绝艳的师兄们,简直是我心目中的神,而且还是一堆神,把实验室堆成了奥林匹斯山。算法狂魔吴嘉伟,无所不能的老任,充满产品气质的李阳,神秘而强大的hadoop高人李爱宝,超级学霸岑武斌,肌肉比代码更强大的孟哥,项目经验无比丰富的秦师兄,超级超级有钱的恭叔。开始写Java的时候,真的是非常的痛苦,代码量太大,幸得咬牙坚持了下来,用一个学期熟悉了整体的代码,在大二下的时候疯狂地刷代码,每天晚上七点到十点走,坚持了差不多一个学期,完整参与了WI九键的开发,也积攒了初步的项目经验。这时候,跟在师兄们身边,虽然很多所见所闻,当时都不一定能理解,但正如谢逊要求年幼的张无忌硬背《九阴真经》一般,当师兄们离开后,我慢慢地琢磨师兄们所说过的一言一语,领悟了更多的深意。

我一直深深地感谢实验室和师兄,是因为他们给予在最黑暗的时光里苦苦地追寻的我,一个光明的道路。身处一个大牛云集的牛棚之内,我学习到的是师兄们的视野,师兄们的做事风格,还有师兄们对技术的洞察。

大二下的暑假,我一个人留在学校,自学了git,vim,linux等等一系列的开发工具和环境,梳理了自己大二一年的技术积淀,一点一点地打磨WI输入法2.1版,从而实现了技术上的升华。

大三上,自学了Python。系统地阅读了输入法内核代码,从这个庞大的C语言项目中,感受到了开源代码的灵魂-C语言中无穷无尽的奇技淫巧,伟大的UNIX哲学,以及经典不朽的VIM,GCC,GDB。认识了新加入的一批软院大神,号称”赵师兄不走,阿里不穷“的铨哥,懒虚怂大神亮叔,萌神高扬,帅的不当gay可惜的富帅,巨有钱的骚博,安卓大神兰兰。在这一个充满活力的团队环境,我从他们身上,学到了太多,无论是技术知识,技术理念还是技术视野。

特别是亮叔,坦率而言,我真的很嫉妒亮叔。作为一个拿github当朋友圈刷的宅男,一个看美剧不用字幕的英语达人,一个遍历了几乎所有的软件技术的人,亮叔简直是为技术而生的“计算机之子”。在和亮叔“撕逼”的大半年里,我总是通过抬杠获得亮叔气急败坏的被动传授(^_^),从而学到了很多珍贵的技术视野。

大三上的暑假,系统地阅读《现代操作系统》《数据结构与算法分析》《计算机网络:自顶向下方法》,涉猎《设计模式之禅》,《代码整洁之道》,理论与实践相印证,有如任督二脉皆通,对技术满怀敬意,对代码满怀热爱,对生活满怀期盼。

还要深深地感谢斯逗比,盛哥,CC哥,飞哥。在我作死翘课的半年多里,帮我各种掩护,使我能够全身心地投入到代码之中。

我一直觉得,WI输入法,也许不能成为一个呼风唤雨的输入法霸主,也许始终只是保持着一个小众的地位,那又怎么样?WI输入法团队里的大学生,始终代表了工大最优秀的移动开发的团队,我们,就无愧于闫于闻师兄和其他三位创始师兄所开创的事业。“留一口气,点一盏灯,有灯就有人。须知念念不忘,必有回响。”

什么是IT?什么是技术?什么是GPL?什么是FSF?什么是GNU?什么是OpenSource?他们都基于一个根本的崇高的理想,推动信息的平坦化,最终实现政治的清明,并让每个人希望拥有平坦的人都能享有这种知识。这就是我对技术的梦想。我,并没有忘记高中时的理想,匹夫虽弩,未敢忘忧国。

信息的获取成本

这个寒假闭门谢客,专心看书,深深地感受到看书之难,坚持不易,然而收获亦不菲。其中感悟,记录如下:

看书难,看硬书更难。所谓硬书,就是那些厚度能比砖,字体小于蝇,图画稀如金的书,而往往已经不属于普及阅读之流,列入一个专业的经典之例。前半个寒假啃一本《现代操作系统》,早上起床,先对着书页发呆,呆上半个小时。时而翻翻昨日的日记,时而看看印象笔记中的摘录的励志文章,如此反复地做足了半个小时的思想工作,再翻开书,一行一行地琢磨字句的含义。比如文件系统中的一个“段”字,顿时就能联想到segmentation fault,想到多少次编译不通过,却始终不能理解报错的本义。我本是处于项目经验有余,理论知识不足的状态,旁人或许以为如此一来,看书就可以与实际经验结合,速度自然一日千里。大谬。正因为实际敲代码中遇到了很多业务场景,对理论知识的印证,就往往会有多种解释。这正是,看的多,想的也多,而且生怕想错了,以后用的这块知识,还要走弯路,因此读书时所写的笔记越发不敢下笔。子曰:述而不作。《诗》曰:兢兢业业,如临深渊,如履薄冰。书法家说,王羲之之后,苏黄米蔡,都逃不开二王的笔意,这些例子,讲的都是同一个道理。如此读书,当然费时,硬书之硬,正是因为此。

当然,付出必有收获。虽然说来惭愧,看了半个月的书,一本《现代操作系统》看了不过四章,然而那十几页笔记不会骗人,都是我自己靠自己的知识,自己对知识点的理解,从而总结成的重点,画出的关键算法的流程图,一些大块知识点的思维导图。100多页的阅读,看似寥寥,却是真真切切地解答了我这一个学期,在Android NDK编程中遇到很多问题。因为NDK的核心就是Linux下的C编程,我所写的输入法内核,又涉及到了大量的内存映射,内存读写的问题,因此,很容易遇到类Unix环境的操作系统知识。概括而言,就是一个程序的生存环境的问题。至今记得一个数学老师的教诲,“一个数学函数,首先要搞清楚他的生存环境,就是他的定义域,才能更深刻地理解函数”。操作系统上的理论知识,其价值也正在于此,虽然大多数程序员不会有机会去写操作系统,然而深入地理解操作系统,才能使自己的程序适应他的生存环境,或者更进一步地,更好地利用自己的生存环境。前者谓之兼容,后者谓之优化,如是而已。

更进一步地推进我们的思考,我为什么明知自己长于实践,而困于理论,却一直以来没有去好好看硬书。原因有三:
1. 忙。在学期内的时候,我的时间很大程度上,并不为自己的意志所左右。课业上,被我怒弃的微电子,很可能突然一个通知收什么作业,我就不得不连夜抄作业。或者本周突然发现积压了几个电路实验,就只能放下手头的编程,准备如何混过。输入法团队内的开发任务,也大多数是在学期内推进的。此外,如果有学弟晚上来到了实验室,立刻得放下手头一切工作,给学弟讲解业务知识,分配开发任务。毕竟学弟来趟实验室不容易(哭),不能让他们来适应我的时间表,只能我去适应他们的时间表。总而言之,人一旦有了工作,处在一个团队之中,你的时间表就必须要适应很多人,正如一个图中的顶点,有很多入度很多出度,这直接导致了你的时间表被切的很碎。
2. 看硬书需要一个足够长的,安静的环境。这一点就是第一点的补充,我得承认我自己的懒惰,所以看书之前我需要半个小时来整理自己的心绪,从而心平气和地看硬书,也只有这样,读书才能读的进去。恐怕这也正是为什么,知乎上如此多的人,认为大学的时候不好好看书,工作后想要看大部头的硬书,难度更大的原因了。毋论结婚生娃,话说起来最烦小孩子了(逃)。Pocket,Instatpaper这样的软件之所以流行,也是因为这一点,即收集高信息含量的文章,将其留到一个更适合阅读的环境。
3. 平时解决理论问题,主要靠看网页上的文章。要么是博客,要么是技术网站上发的文章。这类文章,优点是短小精悍,一针见血。缺点也很多,其一是良莠不齐,往往要搜索大量的网页,才能找到一两篇满意的。其二是有所缺漏,因为大多数都是个人的技术感悟,往往会因为个人技术经验的问题,不能涵盖一个问题的方方面面。其三是不成系统,比如进程,内存,文件,三者是相辅相成的,很难脱离群体讲清楚单个概念,因此阅读技术文章,往往只能达到似懂非懂的境地,而不能彻底地掌握一个技术方向上的全局。“横看成岭侧成峰”,终究不如,“会当凌绝顶,一览众山小。”这,恐怕就是大部头硬书的优势了,虽然离实际生产环境太远,没有实践经验很难与开发相联系,但是作者都是洋人大牛,书也是洋洋洒洒几十万字,事无巨细,字字珠玑,细细品悟能咀嚼出很多技术上的精髓。
4. 综上所述,珍惜宝贵的寒暑假,毕业说远不远,就在眼前了。

由此想到,我们每天看微博,刷知乎,刷朋友圈,真正获取到的有用信息能有多少呢?发表言论越容易,言论也就越廉价,所以邮件列表比qq更具有沟通价值,所以硬书信息量大于博客,博客信息量大于微博,微博信息量大于朋友圈(这里假定微博关注的是大V,朋友圈关注的是周围朋友),朋友圈信息量大于某人人(转发low文章的信息量,还不如让我知道朋友去哪儿旅游了,后者无害,前者祸害)。阅读信息量高的文本,付出的脑力上的折磨,得到的是更高的单位有效信息获取量。正如健身付出的身体上的折磨,得到的是更强健的体魄,这都揭示了同样的道理,只有毅力顽强之人,才能给予自身以更大的升华。