作者归档:贺 利华

关于贺 利华

正在学习编程,享受编程 热爱文学,闲来读读《读库》 有思想,没理想 正在学会专注

「读库 App」给我带来了什么?

作为一个《读库》的 13 年长期订户,我从 2009 年毕业那一年开始每年都订阅当年的全年《读库》,在其推出「小册子」计划后每年订阅的就是全年《读库》+「小册子」。

时间由来已久,期间自己搬过大概 6 次家,每次搬家的时候都会发现自己需要搬的书中有近 1/4 是历年来在「读库」这个出版机构购买的各种书籍,每次搬家除了觉得书很沉,收拾起来和搬起来都很累(虽然每次都会找搬家公司,只不过自己每次搬家都是跟搬家师傅一起搬,不论是否有电梯,好像直到最近一次搬家才搬进电梯房,所以每次都有真正的切肤之感)之外,每次收拾这些书的时候还会发现一个巨大的真相,那就是「原来我订了这么多年的《读库》中至少有一半是我竟然都没有拆开塑封的或者没有翻开过的」。

所以每次搬家的时候,会觉得自己辜负了这些好书好文章,没能及时地跟她们在书中厮磨一番,将其冷落了,顺便也会怀疑自己是否还需要持续订阅下去。可是鉴于这些书实在是太便宜了,而买书又那么地能给自己提供一个虚妄的满足感,正所谓「买书如山倒,读书如抽丝」,显然不是我个人的感受和困惑。「买都买了,还需要读吗?」更是我们这帮买书不读人常挂在嘴边的自我开解之辞。所以每年只要老六开始吆喝新一年的饭票要续费的时候,总是第一时间就下单,感觉是给自己这一年的空虚又填上了一锹带有墨香的土。

作为一个卷心菜式的互联网打工人,连续创业多年,连续失败多次的自我压榨者,坦率地讲,这么多年来,一直没能做到传说中的 「work life balance」。作为两个男娃不太合格的爹,工作日基本上没有在 9 点之前下班的时候,留给自己捧读的时间和机会确实不太多。这倒不是想给自己读书不多找什么借口,我也不需要这样的自我承认,活到这个年纪了,自己大概是个什么样的鸟人,基本上自我认知已经比较真实了,就看自己愿不愿意面对了。

坦白地讲,我就是一个在普通不过的普通互联网打工人了,时间不多,疲于奔命,渴望精神世界的自由和财富自由而不得。想进步,每次制定了一个成长的计划后,基本上在 3 ~ 30 天之内就夭折,想健康,基本上在坚持了两周之后以各种各样的姿势再次花式扑街。基本上,大家都看的综艺,我也看一些;基本上,大家都看的电影,我也看一些;基本上,大家都看的美剧,我也看一些;基本上,大家都听的音乐,我也听一些;基本上,大家都买的基金,我也买一些;基本上,大家都炒的股,我也炒一些;基本上,大家犯过的错,我也犯一些;基本上,大家打的鸡血,我也打一些。

看吧,就是这么一个如此普通和无聊的人罢了。所以书买了没读,内在原因在那儿,外在原因在那儿。不过这一两周里,开始尝试使用「读库」App 之后,我发现还是有些变化的。

首先,我已经养成了每天在有空闲的碎片时间时,主动打开「微信读书」App 随便翻翻的习惯。所以当我把「读库」App 跟「微信读书」App 放在手机桌面隔壁时,已经养成习惯的我在想打开「微信读书」的时候,会有一定的概率会打开「读库」。就着这个「裙带关系」,在不到两周的时间里,利用工作日通勤路上的时间,午休的时间,休息日的闲暇时间,我已经读完了《读库 2201》了,而家里的纸质版的《读库 2201》才被翻开了没几回。

其次,由于「读库 App」实际上提供的内容不止限于其出版的《读库》每期刊载的内容,还有其他周边的内容,例如非当期的内容,往期成系列的文章,如我自己花时间最长读着尤为喜爱的《文学的故乡》系列文章。其中关于毕飞宇,莫言,迟子建,这三人的文章我也在往期的《读库》中已经读过了,但是借着这次「读库 App」上的主题系列阅读,我又非常愉快地先重读了一遍,然后接着一口气把刘震云,贾平凹和阿来的三篇文章都给读完了。这一系列的文章,要是从篇幅上来看,已经接近一部小书了,成系列地连续阅读会让人读着读着读出一些勾连和互通的感觉,我能读出这些作家对于自己写作的内源力的探究的殊途同归,我会发现优秀的作家或者说作者实际上他们的认知和行为方式都是那么的诚实,相似处很多,共通性很多,也有着故乡土地给予他们各自不同的底色,更有着不同成长路线给予他们的视角的不同,但是大爱是一样的,那就是书写自己的内心,书写自己看到的普通大众的内心。

借着 App 的便利性,不但不自觉中把原来基本上已经很难再按时读完的书给读了,还因着 App 中编辑的推荐和形式的灵活,有了系统化阅读的快乐和拓展阅读的可能,比如,关于拍了《盲山》和《盲井》的导演李杨的文章,关于区块链原理的文章等等。

认清这个事实了之后,自己蛮开心的,承认自己不是那么爱书的人,已经被手机和 App 驯化成为了一个普通的现代人,那么就借着这个事实,把手边能用起来的时间稍微分配一些给到阅读这件事情就好了,纸质阅读很好,继续保持就好了,手机上的电子阅读也很好,可以补充很多的场景,并且让阅读这件事情完成。

不要为了某种形式,追求某种完美,也不要因为缺失某个条件就不去做某件事情。大概就是这样吧。

「作者此处有删节」是一种什么样的体验?

这几日在读贾平凹的《废都》,之前读过《秦腔》和《浮躁》,想着把这名家的代表大作都读了吧,就在微信读书上找了来读,读着读着就感觉不太对了。诸多章节出内容中,直接使用了「此处作者有删节」这样的文字代替了大段的性描写,这倒不是咱们没读过小黄书,想看看这香艳刺激的场面具体是咋描写的,而是这样奇怪地保留作品的完整性而又要符合出版审查制度的自我阉割显得实在是过于行为艺术了。我甚至觉得这是一种反讽,也行是我过度解读了啊,作者兴许只是想让这本书的出版不那么艰难,也能让这么好的作品给更多的普通读者能读到罢了。

至此我的好奇心就被激起来了,我到要看看删节的内容具体都是个啥。于是我就搜索了一下,还真找到了未删节版的 mobi 和 epub 文件,最终选了一个看着还算满意的转换了格式,导入到了朋友送我的 Kindle 里头,自己调整了一下字体大小和行间距,就开始读了起来。

其实我们的贾平凹先生对于这种香艳场面的描写并没有特别出彩和骇人听闻之处,基本上跟我们实际生活中的性行为过程毫无区别嘛,也没有像小黄书那样为了满足读者猎奇的心理,刻意将性过程的描写扭曲和夸大,只是用词非常的准确和直白,在我们这个比较偏爱委婉美和谈性色变的主流文化审美环境中,可生存的空间确实不太大。

不过在对比阅读的过程中,发现了两个还蛮有意思的事就是,微信阅读的基础排版会让人读起来舒服不少,早年间的 epub 电子书基本上跟纯 txt 电子书毫无区别,全然没有任何排版,错别字频出(可能跟早年输入法和录入员的素质有关?或者这就是个盗版的电子式,何谈录入员)。在Z-Library找《废都》的同时,我也搜索了一本自己手上有纸质本和微信读书版本的《贪婪的多巴胺》,想着放到 Kindle 上读起来不费眼睛,将其导入到 Kindle 上一打开,这个排版和字体,完全就是纸质本的翻版啊,读起来舒服得很,相较而言微信读书的版本就是有些差点意思了,微信读书上的排版基本上都是以铺满手机屏幕为主(为了在窄小的手机屏幕上展示足够多的内容这个可以理解,不知道墨水屏版本对于微信读书中的内容是否有区别设计和适配,如果仅仅是简单的按比例放大,那就有点遗憾和不够了),而 Kindle 的排版由于设备尺寸相对固定并且比较接近口袋本的尺寸,所以整体排版还是很有书籍本体的那种边距的变化,段落感也会很明显,尤其对于我这次是先从纸质版开始阅读的,然后微信读书才上架了电子版,最后我又阴差阳错地读了一个 Kindle 版本,还真是不同的载体体验很是不一样呢。

【开发日志-00】给 Flutter Plugin 加一个自己需要的特性

【开发日志-00】给 Flutter Plugin 加一个自己需要的特性

开个坑,公开记录一下填坑的过程,如果烂尾了,也给自己打个脸。往年每次年底做总结的时候,会惨兮兮地发现这一年都白瞎了,啥进步没有,感觉又枉费这美好人间,腆个逼脸在本上写下明年一定要参与一个什么什么开源项目,然后本子都不知道到哪儿去了,到了又一年年底都忘了当时是不是立了啥具体的 Flag,只是依稀记得,好像自己又打了一次自己的脸,立了 Flag 但是没有立住。

虽说这个公开记录在互联网上多如牛毛,咱们这个犄角旮旯也照不进来啥阳光,估计也没啥人能看到,但是至少不会像本子一样最后找不到了(当然如果本子好好保留时长回顾也是很好的),姑且咱们假设这次可能有用吧,谁知道呢,或者到了明年这个时候,我们发现这个小破站也无法访问了,或者压根儿就没人在意,我自己也不把这次打脸当回事呢,是吧。


从 19 年 6 月开始,我就开始做 Flutter 开发的相关工作了,到现在也已经 2 年半时间了。由于我们做的项目是在线直播相亲业务,很多直播间类型都是强交互的,经常调试的时候会同时连接多台设备,分别调试不同的角色在直播间的行为,例如主播端、连麦用户端、观众端。开发调试的过程中,经常改了一些 UI 布局和逻辑代码后,想着利用 Flutter Hot Reload 或 Hot Restart 的特性来快速调整验证,但是当我想在特定的某一台设备上执行 Flutter Hot Reload 的动作时,却总是找不到合适的方法来快速实施,最终我只能通过 IDEA 底部的 Tool Window Run 或者 Tool Window Debug 中侧边栏的重载按钮在每台连接的设备上都执行一遍,然后通过日志输出中的 Launching lib/main.dart on PDPT00 in debug mode... 的设备名称来区分当前是在那台设备上执行的。这让我觉得一点都不酷,也不知道众多 Flutter 开发者平时是否有更好的其他小妙招之类的,兴许这个插件的开发人员们平时很少会有连接多台设备同时调试的需求吧,既然我有这个需求,而且一直都蛮强烈的,那么我就自己尝试开个坑自己填吧。

由于我自己常用的 IDE 是 JetBrains 的 Intellij IDEA,所以我自己首选的先是解决我自己的问题,从 Flutter Plugin for IntelliJ 入手吧。

我直接打开了 Github,搜索 Flutter Plugin for IntelliJ 找到了官方的仓库 https://github.com/flutter/flutter-intellij 顺手到了自己的仓库下,然后先 clone 下来。在等待仓库 clone 的过程中,我就开始阅读这个项目的 README 了,出人意料的是,我完全没有想到这么多人每天都在用的插件的官方 repo 的 README 中竟然没有关于如何编译和开发这项目的任何文档,最终我是通过这个 repo 的 issue tracker 中的 contributing guidelines 文档才找到了相关的指引文档。但是坦白讲,折腾经验还算丰富的我,按照文档一步步执行下来,最终还是败下阵来了——Gradle Build失败了。按照失败的各种错误提示,各种 Google + Stackoverflow + 官方的 issue tracker 还是没能解决我的问题,索性那么我们就把这个当个长期坑来看吧,先学习一下如何开发 IntelliJ 的插件,然后再看如何改这个 Flutter Plugin for IntelliJ 吧。


刚好再重看了一遍这个 repo 中的 README 中的 Getting Started with IntelliJ 文档,刚好看到了其中关于 Running and debugging 的内容,如下图:

所以,我把我刚刚说的无法快速在某台指定设备上执行 Flutter Hot Reload 行为的胡话收回,说明我原来用的姿势不对,确实没有发现这么简单和常用的功能(当然实际上我肯定是用过的,只是这个操作区域跟我时常需要关注的 IDE 底部的 Console 离得太远了,所以慢慢地就忽略了这个入口了,更多的时候是直接使用了 Tool Window Run/Debug 中顶部的 Flutter Hot Reload 和 Flutter Hot Restart 按钮来实现的,对了 Flutter Hot Restart 只在底部的 Tool Window Run/Debug 中才有哦),由于平时经常需要关注 Console 中输出的日志信息,所以养成了关注底部窗口的相关,操作也一贯是在这个窗口上执行的,但是这个 Window 中只显示了一个 main.dart 的标题,如下图:

如果能在这个标题上加上一个当前执行的设备的名称,是否会更直观一些呢,例如显示 main.dart on V2057A 或者 main.dart@V2057A 这样是不是就更好了呢。嗯哼,这就是我想做的事情了。

坑已开,等着填。我要开始学习了,IntelliJ Platform SDK 会是我们的第一站,我会一点点记录学习过程中所得的。

macOS 下 Unity 执行子进程调用时无法读取到系统环境变量问题修复备忘

发现问题

由于我们的游戏工程中集成了多个渠道的 Android 版本的 SDK,而不同的 SDK 依赖的一些库多少有些区别,而且不同的 SDK 通常都有着各种不同的需求,最终我们的处理方式是针对不同的 SDK 做不同的打包预处理,这样就可以在打不同的渠道包的时候,按照不同的 SDK 的需求做相应的处理,确保不同的 SDK 的渠道包里头包含的库文件和资源文件不同。

这样我们就在打包执行先执行了一个 Gradle 的 build 脚本,通过 Gradle 的脚本中不同的任务来完成针对不同 SDK 的 Android 工程的编译以及资源文件的删除和拷贝等等。

但是在我们的 Jenkins 打包服务器上打包出来的 APK 文件安装到手机上,启动游戏之后总是提示丢失了类文件,直接就闪退了,这说明打包的过程中出现了问题。一步步排查,最后发现在打包预处理的 Gradle 脚本执行的时候抛出了异常,异常的提示信息也非常的明确,就是 Gradle 在执行 Android 工程的 build 时缺少 ANDROID_HOME 系统环境变量或者在 Gradle 工程的 local.properties 中缺少了 sdk.dir 的设置项,导致 Gradle 在编译 Android 工程时无法正确地引用 Android SDK 进行编译,最终导致 Unity 打包出来的 APK 中缺少了类最终造成了启动就闪退的问题。

解决方案

那么最简单的方法当然是通过添加 local.properties 文件来解决这个问题,可是我看了一眼 Android Studio 生成的 local.properties 文件的内容:

## This file is automatically generated by Android Studio.
# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
#
# This file must *NOT* be checked into Version Control Systems,
# as it contains information specific to your local configuration.
#
# Location of the SDK. This is only used by Gradle.
# For customization when using a Version Control System, please read the
# header note.
#Thu Mar 22 17:12:39 CST 2018
ndk.dir=/Users/helihua/Library/Android/sdk/ndk-bundle
sdk.dir=/Users/helihua/Library/Android/sdk

这货原本就是 Android Studio 创建 Android 工程时或者说是打开工程时根据当前的系统变量自行生成或者设置的,而且明确告知别把这个文件放到 git 等版本管理控制中去,也就是说别把你本地的一个配置给推送到服务器上去,防止别人从服务器上拉取下来你这个本地配置,这样就可能导致团队中其他的小伙伴在编译的过程中出错。

那么我们自然也不能选择这种方式了,而且 Android Studio 这货非常省心地帮你把这个文件都给放到了工程的 .gitignore 中去了,如下:

*.iml
.gradle
/local.properties
/.idea/workspace.xml
/.idea/libraries
.DS_Store
/build
/captures
.externalNativeBuild

看,人家都不给你犯错误的机会呢,所以说我们还是找别的方法吧。

由于我们的项目中执行 Gradle 脚本是通过 C# 提供的执行子进程的方式来完成的,那么找不到 ANDROID_HOME 这个环境变量的进程实际上就是我们执行预处理的那个 C# 进程了。那么有没有可能单独针对这个进程设置系统变量呢,一查文档还真有,那么就可以简单的这么处理了:

gradleProcess.StartInfo.EnvironmentVariables["ANDROID_HOME"] = EditorSetup.AndroidSdkRoot;

这样一来问题自然就得到解决了,EditorSetup.AndroidSdkRoot 这个变量返回的是我们在 Unity Editor 中设置的 Android SDK 的路径,当然具体要不要用这个变量就看自己的选择了,通常来说如果我们想要正常打包 Android 的包的话,这个变量肯定是正确的 Android SDK 的路径了。

延伸思考

这个问题越想越让人觉得奇怪,你说我作为一个前 Android 开发者,怎么可能会没有配置 macOS 的系统环境变量呢,电脑上 ~/.profile 文件中早都配置好了 ANDROID_HOME 等等环境变量的啊。那么为什么现在才暴露出来呢?

  1. Android Studio 等 IDE 在 macOS 上不只是会读取 macOS 的环境变量,还会尝试去读取 ~/.profile 和 ~/.bash_rc 等等文件中设置的环境变量,所以 Android Studio 等 IDE 在启动的时候就能正确检测到我们已经在 ~/.profile 等文件中配置好的 ANDROID_HOME 变量了;
  2. Unity Editor 并不会尝试去读取 ~/.profile 等文件中设置的环境变量,而只是从其父进程 launchd 中继承了已设置的环境变量,而这些环境变量中并没有 ~/.profile 中我们通过 export 设置的各种环境变量,平时我们执行很多 Gradle 命令的时候,通常都是直接从终端中执行的,而终端程序除了会从 launchd 进程中继承已有的环境变量还会加载 ~/.profile 等文件中的环境变量的,所以导致了实际上这个问题是一直存在的只是没有暴露出来。

最终问题的根源找到了,是因为 Unity Editor 这个进程中的环境变量中缺失了 ANDROID_HOME 这个变量,那么最终需要做的就是让 Unity Editor 这样的程序的环境变量中有 ANDROID_HOME 这个变量,在杨威同学和 Google 的帮助下,最终确定的方法就是通过创建一个交给 launchctl 加载的 plist 文件,在 plist 文件中声明我们需要执行的命令 launchctl setenv 和对应的参数,让系统在启动的时候自行通过 launchctl setenv 命令将 ANDROID_HOME 等环境变量设置到 launchd 这个牛逼的父进程中去。

我们可以先看一下 macOS 系统上进程的层级关系:

我们可以看到所有进程实际上都是由 kernel_task -> launchd 这样一路 fork 出来的,看看进程 ID 也能明白得差不多了。废话少说,怎么做呢?

好吧,我参考了这篇文章

大体的流程就是这样的:

  1. 创建一个用于 launchctl 在系统启动的时候自动加载的 plist 文件,在该 plist 文件中调用 launchctl setenv 命令设置参数;
  2. 将 plist 文件放到 ~/Library/LaunchAgents 目录下去,然后重启系统即可,当然如果你现在不想重启系统,可以执行 launchctl load -w ~/Library/LaunchAgents/[com.laputa.SetAndroidEnviroment.plist] 命令(替换方括号中的 plist 文件名)来主动加载该 plist 文件,加载成功后,重新启动 Unity Editor,在 plist 文件中设置的环境变量在 Unity Editor 进程中就生效了。

把我的 plist 文件贴上来:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
  <plist version="1.0">
  <dict>
    <key>Label</key>
    <string>setenv.Android</string>
    <key>ProgramArguments</key>
    <array>
      <string>/bin/launchctl</string>
      <string>setenv</string>
      <string>ANDROID_HOME</string>
      <string>/Users/helihua/Library/Android/sdk</string>
      <string>ANDROID_NDK_HOME</string>
      <string>/Users/helihua/Library/Android/sdk/ndk-bundle</string>
    </array>
    <key>RunAtLoad</key>
    <true/>
  </dict>
</plist>

注意事项:plist 文件如果向上文提到的那个链接中的文章中放在 /Library/LaunchDaemons 目录下的话,重启之后貌似就不会生效,而放到 ~/Library/LaunchAgents 目录下在重启之后是可以生效的,我是在 macOS High Sierra 10.13.3 上测试的。

如何解决Unity-Editor中使用Gradle打包出现Heap-Space不足的错误

如何解决 Unity Editor 中使用 Gradle 打包出现 Heap Space 不足的错误

最近在自己的开发机上和公司内部用的打包的机器上都出现了通过 Unity Editor 提供的 Gradle 打包流程打包 APK 文件时提示 Java Heap Space Error 的问题。

输出的错误日志内容如下:

Running dex as a separate process.

To run dex in process, the Gradle daemon needs a larger heap.
It currently has 1024 MB.
For faster builds, increase the maximum heap size for the Gradle daemon to at least 2560 MB (based on the dexOptions.javaMaxHeapSize = 2g).
To do this set org.gradle.jvmargs=-Xmx2560M in the project gradle.properties.
For more information see https://docs.gradle.org/current/userguide/build_environment.html

:mergeDebugJniLibFolders UP-TO-DATE
:transformNativeLibsWithMergeJniLibsForDebug
:processDebugJavaRes NO-SOURCE
:transformResourcesWithMergeJavaResForDebug
:validateSigningDebug
:packageDebug
Expiring Daemon because JVM Tenured space is exhausted
Daemon will be stopped at the end of the build after running out of JVM memory
:packageDebug FAILED

BUILD FAILED

Total time: 1 mins 26.532 secs
Expiring Daemon because JVM Tenured space is exhausted
]
exit code: 1

那就按照它的提示把 gradle 的配置修改一下吧,由于我使用的开发机和公司的打包机器都是 macOS 系统,所以只需要在 ~/.gradle 目录下的 gradle.properties 文件(如果该目录下没有这个问题件,可以通过 touch gradle.properties 命令创建)中新增下面的这行配置即可:

org.gradle.jvmargs=-Xmx2048m