RTFSC 是程序员打怪升级路上避不开的功课,那营造一个舒适的环境来提升上课的体验就很有必要了。 比如阅读 AOSP 这种大型源码,用什么姿势来阅读才能丝般顺滑,让 F**king Source Code 也变得不那么可恶呢? 工具的选择 阅读源码的工具我尝试过以下几类: IDE 在看特定类型项目时这是我的首选。比如它原本就是一个 Visual Studio 工程,那当然用 Visual Studio 来打开阅读,看 Android App 或者 Library 源码当然用 Android Studio 体验更好。 编辑器配合插件 比如 Vim + Ctags + Cscope,再配合文件模糊查找插件 LeaderF 和神器 YouCompleteMe,在源码规模不大时很方便,打开也轻快,阅读一些小项目时我还是乐意使用它们。 专门的源码阅读工具 在针对特大型源码时,比如 AOSP 和 Chromium,使用上述两种方案可能会感觉乏力,这时候就需要祭出专门的源码阅读工具了。 一类是商业软件,比如 Windows 下有著名的 Source Insight,跨平台的有 Understand,功能都很强大,都是不错的选择。当然它们都价格不菲。 而我这里要讲的主角 OpenGrok 属于另一类,免费,开源,运行流畅,功能也毫不逊色。 如果你还在寻觅适合你自己的解决方案,大可以花一点时间将以上几种都尝试一遍,哪个称手用哪个,也可以像我一样,针对不同的项目使用不同的工具。想直观了解 OpenGrok 的同学可以直接先看看一些使用 OpenGrok 的在线源码查看网站,看看它能否满足你的需求,其中的一些列在 OpenGrok installations。 OpenGrok 特性 译自官方 Wiki。 OpenGrok 提供如下特性: 快速搜索代码的引擎 搜索全文、定义、符号、文件路径和修改历史 搜索任意指定子目录(分层搜索) 增量更新索引文件 支持类似 Google 的查询语法,比如 path:Makefile defs:target 搜索日期范围内修改的文件 支持使用通配符搜索,如 * 表示多个字符,? 表示单个字符 在搜索结果中展示匹配行 一个 Web 只读版的版本历史查看界面 文件的修改日志 文件在两个版本间的 diff 文件夹的历史记录 带语法高亮的交叉引用显示,可以使用 CSS 自定义样式 可以开发插件支持新的语言和版本控制系统 已经支持的语言: Supported Languages and Formats 已经支持的版本控制系统:Supported Revision Control Systems 配置 OpenGrok 截屏 按惯例先上图吧,万一你一眼就发现不是你的菜呢(截图来自官网)。 搜索功能和源码树: 代码导航和版本历史记录: 安装和配置 如下以 Windows 下为例,Mac OS X 与 Linux 下与此类似,很多步骤能使用 brew 或者 apt-get 会更方便。 安装 JDK,并配置 JAVA_HOME 或者 JRE_HOME 环境变量为安装目录。 下载 Tomcat,解压到一个目录,如 D:\Programs\apache-tomcat-8.5.8,并将此目录添加为 CATALINA_HOME 环境变量。 下载 Universal Ctags for Windows,将 ctags.exe 文件所在目录添加到 PATH 环境变量。 下载 OpenGrok 的最新包,比如 opengrok-0.13-rc4.zip,解压到一个目录,如 D:\Programs\opengrok-0.13-rc4。 配置 data root。 data root 用于放置生成的索引文件和配置信息,比如我在 OpenGrok 目录下创建了一个 data 目录用作 data root,即 D:\Programs\opengrok-0.13-rc4\data。 将 OpenGrok 的 lib 目录里的 source.war 解压到 D:\Programs\apache-tomcat-8.5.8\webapps\source,配置 WEB-INF\web.xml 文件的 CONFIGURATION 为上一步生成的 data 目录下的 configureation.xml,比如我的配置: <display-name>OpenGrok</display-name> <description>A wicked fast source browser</description> <context-param> <description>Full path to the configuration file where OpenGrok can read its configuration</description> <param-name>CONFIGURATION</param-name> <param-value>D:/Programs/opengrok-0.13-rc4/data/configuration.xml</param-value> </context-param> 注:这里只是配置一个文件路径,具体的 configuration.xml 会在第 8 步时自动生成。 配置 source root。 可以让 OpenGrok 认为 source root 下的每个子文件夹是一个项目,所以我们利用这个特性来配置和阅读多个项目源码就好了。 我的做法是在 OpenGrok 下创建了一个子目录 D:\Programs\opengrok-0.13-rc4\projects,然后将需要阅读的源码使用符号链接的方式链接到这个目录里: cd /d D:\Programs\opengrok-0.13-rc4\projects mklink /J android D:\sources\android_5.1 mklink /J openjdk7 D:\sources\openjdk7 这样就有一个叫 android 的工程,它实际对应 D:\sources\android_5.1 下的源码,一个叫 openjdk7 的工程,它实际对应 D:\sources\openjdk7 下的源码。 建立索引。 使用 opengrok.jar 调用 ctags 来为源码建立索引。命令行: java -jar /path/to/opengrok.jar -P -S -v -s /path/to/source/root -d /path/to/data/root -W /path/to/configuration.xml -P 表示为 source root 目录下的每个一级子目录生成一个工程。 -S 表示搜索并添加 “external” source repositories。 -v 表示打印操作的进度信息。 -s 表示指定 source root。 -d 表示指定 data root。 -W 表示指定将配置写到该文件。 还有更多配置选项可以使用 java -jar /path/to/opengrok.jar 查看。 比如我使用的完整命令行: java -jar D:\Programs\opengrok-0.13-rc4\lib\opengrok.jar -P -S -v -s D:\Programs\opengrok-0.13-rc4\projects -d D:\Programs\opengrok-0.13-rc4\data -W D:\Programs\opengrok-0.13-rc4\data\configuration.xml 每次需要建立或更新索引的时候敲这么长一个命令当然很不爽,使用 doskey 或者 Cmder 里的 alias 命令将其 alias 为 opengrok-index 命令会省力不少,再不济把这命令存成个 bat 文件也行啊。 为大型源码建立索引可能需要漫长的时间,这时候可以去干点别的事了。 启动 Tomcat,愉快地 RTFSC。 D:\Programs\apache-tomcat-8.5.8\bin\catalina.bat start 用你最爱的浏览器打开 http://localhost:8080/source/,然后就能愉快地跟 OpenGrok 玩耍了。 当新添加了项目,或者现有项目有源码更新时,再次执行上一步的命令,就能增量更新索引了。 配置多项目 我曾经为如何在 OpenGrok 里配置多项目苦恼了好久——一开始我是把 Android 源码的根目录当作 source root 的,可想而知 OpenGrok 把 Android 分成了好多个子项目,而这时我也没法再添加新的工程了。 后来才发现建一个专用的 source root,然后把各种项目源码根目录软链接过来,让 OpenGrok 为 source root 下的每个 symbol 一级子目录建立一个项目才是正确的使用方法。 Windows 下建立软链接的方法是使用 mklink /J android D:\sources\android_5.1,Mac OS X 和 Linux 下可以使用 ln -s /path/to/source project_name。 折腾狂魔 在 Vim 里使用 没错,还有人做了支持在 Vim 里使用 OpenGrok 的插件,如果你是 Vim 控+折腾狂魔,可以一试,这里仅给出插件地址: asenac/vim-opengrok jdevera/vim-opengrok-search 反正像我这种智商是折腾不动了,就安心在浏览器里用了。 在源码里做笔记 配合 Chrome 插件 Diigo,还能给源码加标签,写注释等等。 参考:https://www.zhihu.com/question/33505693/answer/132224974 常见问题 打开网页后报错 There was an error! CONFIGURATION parameter has not been configured in web.xml! Please configure your webapp. Unable to determine source root path. Missing configuration? java.io.FileNotFoundException: Unable to determine source root path. Missing configuration? at org.opensolaris.opengrok.web.PageConfig.checkSourceRootExistence(PageConfig.java:1562) at org.apache.jsp.index_jsp._jspService(index_jsp.java:222) at org.apache.jasper.runtime.HttpJspBase.service(HttpJspBase.java:70) at javax.servlet.http.HttpServlet.service(HttpServlet.java:742) at org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:443) at org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:385) at org.apache.jasper.servlet.JspServlet.service(JspServlet.java:329) at javax.servlet.http.HttpServlet.service(HttpServlet.java:742) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) at org.opensolaris.opengrok.web.StatisticsFilter.doFilter(StatisticsFilter.java:55) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) at org.opensolaris.opengrok.web.AuthorizationFilter.doFilter(AuthorizationFilter.java:83) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:198) at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96) at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:478) at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140) at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:80) at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:624) at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87) at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:342) at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:799) at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66) at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:868) at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1455) at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) at java.lang.Thread.run(Thread.java:745) 这一般是由于更新或切换了 OpenGrok 版本,但却没有将 tomcat 的 webapps 目录下的 source 文件夹替换为对应版本。 后话 古人教会了我们工欲善其事,必先利其器的智慧,但我们也不能沉迷和徘徊于各种利器之间,选择一样自己感觉最称手的工具,把它用熟练,少再在这上面花时间折腾,毕竟把有限的生命投入到无限的 RTFSC 才是正道不是么。
前几天,第一款由我独立开发的 Vim 插件 vim-markdown-toc 升级了功能,发布了 v1.0 版本。 它的主要功能是为 Markdown 文件生成 toc(Table of Contents)、更新已经存在的 toc 和在保存时自动更新 toc。 说它是当前使用 Vim 编辑 Markdown 文件时维护 Table of Contents 的最佳解决方案应该不为过。 下载地址 vim-markdown-toc 主要更新 支持使用 :UpdateToc 命令更新已经存在的 toc。 支持保存时自动更新 toc。 功能演示 一些体会 相比于 awesome-adb 的一千多个 Star,vim-markdown-toc 的三十多个 Star 在我心目中含金量更高。 做能挣钱的东西,或者对自己有用的东西。 有天和我们组去年来的小朋友聊天,他说了一个观点我觉得很有道理:做能给自己产生经济效益的东西,你才有动力持续下去。虽然听起来功利,但不无道理。 这么一个插件显然不能挣钱,它属于第二种。本次更新的功能其实在半年前就有网友提过需求,见 #6,但我却迟迟没有动手。半年后的某个夜里,突然就决定要把这些做出来,并且花了几个小时就做完了。究其原因,还是因为自己现在有了需求,我现在要维护几份比较长的 Markdown 文档,如果每次手动去删除已有 toc 然后再次生成,虽然比纯手工写 toc 要不知道方便和准确到哪里去了,但归根结底还是不够完美。果然最后用着自己做的功能替自己省下不少重复劳动时,那种感觉也是很爽的。 酒香也怕巷子深。 发布了个人的项目后,适当地在相关的社区进行推广,让更多的人享受到你的劳动成果,帮助验证和反馈,也能给自己带来更多的成就感和关注度。
这是一个源自知乎的话题,原贴链接:一道百度的面试题,有大神会嘛? 虽然我不是大神,但我也点进去看了一下,思考了一会之后有了一些思路,然后去看其它人的答案的时候果然全都已经被各路大神们先想到并贴出来了,所以我就不去凑热闹写重复答案了,整理一下网友们的智慧在这里自娱自乐好了。 题目 思路一 作为一个多年前也见过不少笔试题的少年,看到这个题目的第一想法是脑筋急转弯——注入一段逻辑直接改变原 if 结构。 解法一 填入内容 true){System.out.print("a");}if(false。 public void print() { if (true) { System.out.print("a"); } if (false) { System.out.print("a"); } else { System.out.print("b"); } } 类似地也可以填入 true){System.out.print("ab");return;}if(false 等。 当初大学时单纯的少年可是很难想出这样的套路的,时间改变了我们啊。 思路二 如果正经遵从题目的原代码结构,那就得想办法构造一段代码,既能输出 a,又能返回 false。 解法二 我也想到能否使用 System.out.print 的返回值来做文章,但奈何并不记得它返回什么,首先让我们复习一下 PrintStream 的 print、println 和 printf 方法的区别: 方法 功能 返回值 print 打印一个值或者对象 void println 打印并换行 void printf 格式化打印 PrintStream 所以适用的是 printf,它的返回值是 PrintStream 类型的 System.out,判它是否为空即可。 填入内容 System.out.printf("a") == null。 public void print() { if (System.out.printf("a") == null) { System.out.print("a"); } else { System.out.print("b"); } } 经测试填入 System.out.append("a") == null 也是可以达到效果的。 解法三 仍然是思路二,但从匿名内部类来作文章。 实现代码: public void print() { if (new Object() { boolean print() { System.out.print("a"); return false; } }.print()) { System.out.print("a"); } else { System.out.print("b"); } } 这里利用的知识点是匿名内部类可以声明基类没有的新方法并且马上调用。 解法四 使用 Java 8 里的 lambda 来实现思路二。 public void print() { if (((BooleanSupplier)(() -> {System.out.print("a");return false;})).getAsBoolean()) { System.out.print("a"); } else { System.out.print("b"); } } 严格来讲这个不一定能算作正确答案,因为要增加 import java.util.function.BooleanSupplier;。 脑洞大开 讲完严肃的解法,来看看网友 穷小子 开脑洞的思路: public void print() { // if ( ) { System.out.print("a"); // } else { System.out.print("b"); // } } 如果没有特意说明只能在括号里加东西,倒真是个妙计! 同样看得我一愣一愣的还有 caiwei 同学的答案,他和朋友们发现题目里少写了个大括号(真的),于是我们看到他的朋友老方的解决方案: 真是防不胜防啊~不过我喜欢!:+1: 参考 RednaxelaFX 的回答 仓鼠君 的回答 放开那女孩 的回答 穷小子 的回答 caiwei 的回答
这是一个 C++ 程序员自己总结的 Java 学习中应该注意的点。 缘起 因工作原因从 Windows 客户端开发转为 Android 客户端开发,所以主要的开发语言也由 C++ 变为了 Java,在学习 Java 的过程中,即享受到 Java 的自带程序库的丰富带来的便捷,也遇到一些与 C++ 里的习惯不符需要注意的地方。 初学时的计划是看完一本 Java 教材,过程中整理 C++ 程序员学习 Java 需要注意的点,然后对照写一篇《C++ 程序员的 Java 指南》,但最后懒癌犯了,只整理了一部分不同点,要形成一份「指南」还有很长的路要走,暂且把这个坑挖在这里,如果哪天心血来潮就填上。 一个知乎问答下有我的答案,与本篇文章内容同步:习惯写C++的人突然转去写Java,会有什么样的坑? 注意点 char 是两个字节(字符及字符串默认都是 utf-16)。 浮点数默认是 double,所以要写成 float f = (float)5.0 或 5.0f,不然会报错。 整数除整数可能导致除零异常,而浮点数不会。 break 和 continue 能够使用 flag 来跳出和继续指定循环。 boolean 值只能是 true 和 false,不能从整形等其它值转换而来。用于字符串连接的时候会自动转换成「true」和「false」。 if 里只能接受 boolean 值,所以 C++ 里的好习惯 if (5 == var) 在 Java 里不再必要,少写了一个 = 的时候 IDE 和编译器都会提示你。 new Person(); 必须有 (),否则编译不过。 数组的声明方式推荐 int[] nArray = new int[4]; 或者 int[] nArray = {1,2,3};,第一种称为动态初始化,第二种称为静态初始化。动态初始化时,系统按如下规则分配初始值:整形为 0,浮点型为 0.0,字符型为’\u0000’,布尔型为 false,引用类型为 null。 当系统加载类或创建该类的实例时,系统自动为成员变量分配内存空间,并在分配内存空间后,自动为成员变量指定初始值。 局部变量定义后,必须经过显式初始化后才能使用,系统不会为局部变量执行初始化。 访问控制符有 private、default、protected、public。private 只能在同一个类中访问,default 能在同一个类、同一个包中访问,protected 能在同一个类、同一个包、子类中访问,public 能在全局范围内访问。 在构造器中可以使用 this(params) 来调用本类的其它构造器,使用 super(params) 来调用父类构造器,只能书写在本构造器第一行,所以它们不能同时出现。 使用 super 调用父类中的实例方法,使用父类类名调用父类中的类方法。 如果父类方法具有 private 访问权限,则该方法对其子类是隐藏的,因此其子类无法访问和重写该方法。 java 中有 instanceof 运算符,c++ 中对应的 RTTI 方式是(typeid)?instanceof 运算符的前一个操作数通常是一个引用类型的变量,后一个操作数通常是一个类(也可以是接口,可以把接口理解成一种特殊的类),它用于判断前面的对象是否是后面的类,或者其子类、实现类的实例。如果是,则返回 true,否则返回 false。判断是否是同一个类的实例时应使用 obj1.getClass()==obj2.getClass()。 初始化块和声明属性时指定初始值,这些按源程序中排列顺序执行。 java 中只有值传递。 对 private 方法,即使它使用 final 修饰,在子类中也可以定义一个相同的,因为这是子类定义了一个新方法,并非重写。 final 和 abstract 永远不能同时使用;static 和 abstrace 不能同时修饰某个方法;private 和 abstrace 不能同时修饰某个方法。 java 中的 abstract 方法不能有方法体,c++ 中的 pure virtual 函数可以有实现。 interface 的方法只能是 public abstract 的,属性只能是 public static final 的,使用 private 等修饰编译会报错。 从内部类里引用外部类的属性或者方法时,可以用命 OuterClass.this.。 非静态内部类里不能有静态成员。 内部类可以使用 static 修饰,外部类不行。 从外部类外创建内部非静态类的语法:OuterClass.InnerClass varName = OuterInstance.new InnerConstructor(); 从外部类外创建内部静态类的语法:OuterClass.InnerClass varName = new OuterClass.InnerConstructor(); 内部类不可能被外部类的子类中重写,因为命名空间不同。 纠误一处:《疯狂 Java 讲义》P214 讲道「如果匿名内部类需要访问外部类的局部变量,则必须使用 final 修饰符来修饰外部类的局部变量,否则系统将报错。经验证,只要这个局部变量在后续不改变其值,即使它不以 final 修饰,但实际表现是有效的 final 时,在 Java 8 环境下编译后会自动为它加上 final,不报错。 switch 表达式可以使用整形或者枚举类实例(从 Java 7 开始,可以使用 String 对象了,参考:Strings in switch Statements)。 Set 判断两个对象是否相同不是使用 == 运算符,而是根据 equals 方法。 HashSet 判断两个元素相等的标准是两个对象通过 equals 方法比较相等,并且两个对象的 hashCode() 方法返回值也相等。 foreach 循环仅适用于实现了 Iterable 接口的 Java array 和 Collection 类。 遍历任何 Collection(例如 Map、Set 或 List)时要删除元素只能使用 Iterator 的 remove 方法。 包名不能使用关键字,比如 switch 和 return 等,参见 7.4.1. Named Packages 和 3.8. Identifiers。
假设我们正在 Vim 里编辑一个很长的 Markdown 文档,这时想跳转到另一个章节去查看或编辑内容,可以怎么做? 查找章节标题。 上下翻页。 记得行号,精确跳转(请收下我的膝盖)。 … 不知你此时感受如何,反正我是无比想念 Word 的导航窗格,各种编程 IDE 的 Outline。 但等等,我们正在用编辑器之神 Vim 诶!无论想弄啥折腾折腾就有了。 最终效果 我的完整 Vim 配置托管在 GitHub,可供参考:config-files 实现步骤 安装 tagbar 这里我们借助于 tagbar 插件实现导航窗格。当然 tagbar 的功能远不限于此,可以为 C、C++、Python 和 Java 等很多语言提供类和方法列表视图等等,详见 tagbar。 推荐使用 Vundle 管理 Vim 插件,这样就可以简单几步安装插件了。 在 vimrc 文件里添加如下内容: Plugin 'majutsushi/tagbar' 执行 :so $MYVIMRC 执行 :PluginInstall 安装 Exuberant ctags tagbar 正常工作依赖于 Vim 7.0+ 和 Exuberant ctags。 下载地址:http://ctags.sourceforge.net/。 下载后将 ctags 可执行文件放置到一个在 PATH 环境变量的文件夹里,或者 Vim 安装目录的 Vim74 文件夹下,比如我是将 ctags.exe 放在 D:\Program Files (x86)\Vim\vim74 文件夹下。 配置 tagbar 显示 Markdown 导航窗格 tagbar 默认并不支持 Markdown 文件,但配置一下就好了。 创建 ~/.ctags.d/markdown.ctags 文件(Windows 下是 C:\Users\<username>\.ctags.d\markdown.ctags),将如下内容贴到文件里: --langdef=markdown --langmap=markdown:.md --regex-markdown=/^#{1}[ \t]*([^#]+.*)/. \1/h,headings/ --regex-markdown=/^#{2}[ \t]*([^#]+.*)/. \1/h,headings/ --regex-markdown=/^#{3}[ \t]*([^#]+.*)/. \1/h,headings/ --regex-markdown=/^#{4}[ \t]*([^#]+.*)/. \1/h,headings/ --regex-markdown=/^#{5}[ \t]*([^#]+.*)/. \1/h,headings/ --regex-markdown=/^#{6}[ \t]*([^#]+.*)/. \1/h,headings/ 这表示提取 Markdown 文件里的一到六级标题,并使用空格缩进表示层次。 给你的 vimrc 文件里增加如下内容: let g:tagbar_type_markdown = { \ 'ctagstype' : 'markdown', \ 'kinds' : [ \ 'h:headings', \ ], \ 'sort' : 0 \ } 配置 tagbar 支持 Markdown。 更多自定义配置 现在你可以使用 :TagbarToggle<CR> 来打开导航窗格了,但每次开关导航窗格都要敲这么长一串命令毕竟不够方便,配置快捷键来操作更顺手,在你的 vimrc 文件里增加一个映射: nnoremap <leader>tb :TagbarToggle<CR> 现在你可以使用 <leader>tb 来随时开/关导航窗格了。 导航窗格默认是在右边,如果你也像我一样喜欢它在左边,也想指定它的宽度,可以在你的 vimrc 文件里配置: let g:tagbar_width = 30 let g:tagbar_left = 1 至此,大功告成了! 根据我的这个文章没有配置成功的同学,可以参考下我的配置 https://github.com/mzlogin/config-files 。 参考链接 majutsushi/tagbar#70 Support for additional filetypes Extending ctags with Regex parser (optlib) - Option files
Update 2018/05/01: GitHub 官方已经支持自定义域名启用 HTTPS 了,见 https://blog.github.com/2018-05-01-github-pages-custom-domains-https/ 前天收到来自 Kloudsec 的邮件,说 Kloudsec 将于 2016/08/01 停止服务。Bad news,请容许我做一个悲伤的表情。 邮件原文 Hi all, It is with great sadness that I have to inform you that Kloudsec is shutting down. Why is Kloudsec shutting down? We have been funding Kloudsec out of our own pocket Kloudsec is very expensive to maintain (upwards to $10000 / month) We are unable to make money from Kloudsec, nor raise any funds for it We will shut Kloudsec down on 1st August From now till then, we will not be maintaining the service. Migrating out of Kloudsec Migrating out of Kloudsec is extremely simple. All you have to do is to point your domain back at its origin server. You will lose the HTTPS cert. But you can fix that by either issuing your own LetsEncrypt certificate or using Cloudflare. Lessons learnt From the start, we are extremely lucky to have a small revenue stream that let us to experiment with cool products. And Kloudsec is one of our biggest experiment. There are a couple of things we did right, and a couple we did wrong. We did right by having the right team come together, building a seriously sophiscated product. (Thank you Ivan and Bach) We did wrong by building a sophiscated product that made it hard for a small team to maintain, let alone scale. We did wrong by building a product that was not immediately useful enough so much so that people will pay for. We did wrong by building a product that was too expensive to maintain. We did wrong by assuming that traction solves all ailments. Not in Singapore, you don’t. There is no good venture money for real hard-tech software startups in Singapore. We did right with pulling the plug so we can learn from these mistakes and work on the next product. What’s next for us From Kloudsec, we identified a few niche problems that we will be looking to solve. In other words, we will continually be building. And you can be sure from our next product onwards, we will charge right from day 1 so we can sustain the product financially. Lastly, thank you! Thank you. Most of you have spoken to me, or read the posts I’ve written on Github, on Hacker News, or Producthunt about Kloudsec. You guys took a leap of faith in trusting this small unknown team and product, and used us. I’m sorry to disappoint you with this piece of news, but I’ll try better next time. If you like, you can follow me on Twitter at @nubela. You can also contact me at anytime at steven@nubela.co Steven Goh. 我的决定 Sigh! 想起五月份的时候我还欢天喜地地给博客 HTTPS 化了,并为此写了一篇博客 为绑定域名的 GitHub Pages 启用 HTTPS,还将它推荐到了 掘金,收获了 64 次收藏,并在那篇文章下创下了我个人博客单篇评论数最多的记录。 没想到还没等到 Kloudsec 第一次为我的域名证书自动续期,它家的服务就要关停了。 但也没有办法,使用第三方的服务,特别是初创公司的服务本就有这种风险。 本来 GitHub Pages + Custom Domain + HTTPS 也还有其它解决方案,但我已不想再折腾了,多引入一层中间服务,就多一层出状况的风险,我只是想作一名安静地写博客的美男子啊。 所以决定去掉自定义域名的 HTTPS,恢复 HTTP,现在 GitHub Pages 已经支持 *.github.io 域名的 HTTPS,除非哪天它原生支持 Custom Domain 的 HTTPS,否则我不再折腾这个事情。 采取的措施 当初之所以想开启 HTTPS 的一个重要原因就是 Google 收录了博客的 HTTPS 链接,但是证书不对导致用户打不开或者有警告,既然使用自定义域名没有办法让证书对,那就让 Google 不收录 HTTPS 的链接吧。 告诉 Google 不收录 HTTPS 链接 如果一个页面使用 HTTPS 和 HTTP 都能访问,那如果想 Google 只收录 HTTP 版,而不收录 HTTPS 版,那可以在页面的 head 里添加 canonical 给爬虫以建议,具体方法如下: <link rel="canonical" href="http 开头的 URL">
kramdown 的列表嵌套内容的缩进规则很「奇葩」,不是使用自然的 Tab 缩进。 问题 kramdown 的作者对列表嵌套内容的缩进规则的 描述 是: kramdown does not allow 4 space indent, …… Indentation for list items is always calculated based on the first non-space character after the list item marker. 在年初 GitHub 宣布 GitHub Pages 服务将只支持 kramdown 这个 Markdown 解析引擎时,我曾经总结过从 Redcarpet 迁移到 kramdown 需要做的一些更改,将 GitHub Pages 从 Redcarpet 切换到 kramdown 里有说到,嵌套在列表项里的代码块,如果不按如上规则做缩进的话,将会解析不正常。 比如: 1. list item one ```python print 'hello, world' ``` 2. list item two 解析后的结果是: list item one print 'hello, world' list item two 这当然不是我们想要的,我们应该如何写呢? 1. list item one ```python print 'hello, world' list item two ``` 解析后的结果是: list item one print 'hello, world' list item two 看出来区别了吗?没错,这种情况下代码块必须缩进三个空格,因为除开列表记号后的第一个非空字符的缩进是三。 那么问题来了,有时候是有序列表,序号是个位数时需要缩进三个空格,序号是两位数时需要缩进四个空格,序号是三位数时(弄这么大的列表是闹哪样?)需要缩进五个空格……有时候是无序列表,只需要缩进两个空格。 当然这是最简单的一级嵌套的情况,如果是多级列表嵌套,那情况就更复杂了,每一次都去手打空格缩进吗?作为一名 Vimer,当然 say no! 所以为此我做了一个简单的小 Vim 插件专门用于解决此问题。 下载地址 vim-kramdown-tab 使用方法 安装完此插件后,在你需要对列表嵌套内容进行缩进时,不用掰着手指头去数要打多少个空格了,只用按 LeaderTab 就好了。 安装方法 推荐使用 Vundle 来管理你的 Vim 插件,这样你就可以简单三步完成安装: 在你的 vimrc 文件中添加如下内容: Plugin 'mzlogin/vim-kramdown-tab' :so $MYVIMRC :PluginInstall 屏幕截图
Update 2018/05/01: GitHub 官方已经支持自定义域名启用 HTTPS 了,见 https://blog.github.com/2018-05-01-github-pages-custom-domains-https/ Update 2016/07/11: 收到 Kloudsec 的邮件,说将在 2016/08/01 停止服务,所以如下方法在那之后会无法使用,寻找 GitHub Pages + Custom Domain + HTTPS 的方法的童鞋请不要再尝试 Kloudsec 了,去找一找别的方法吧。 虽然现在各种网站都在 HTTPS 化,甚至有的个人网站在添加链接的说明里明确声明只与启用 HTTPS 的网站交换链接,但一直以来我启用 HTTPS 的需求并不强烈,又加上有懒癌在身,实在是没有动力去折腾,直到最近发生了几件事情。 缘起 最近一段时间也不知道是 GitHub Pages 做了调整还是 Google 的收录策略有了变化,Google 收录我的博客页面都是同时收录了 HTTP 和 HTTPS 两种版本,而很遗憾这个博客并没有配置有效的 SSL 证书,所以点开的是一个 HTTPS 的链接就会被浏览器提示连接不可信,有安全风险云云,虽然访问量不大,但这样体验太差,也是蛮闹心的。 恰逢知乎上有人邀我答题 Github Pages 绑定了个人域名,怎么使用 HTTPS 访问而证书不报错呢?。 三月份的时候 @nubela 发邮件邀请我试用他为绑定域名的 GitHub Pages 制作的一键启用 HTTPS 的工具,而我当时答复的是实在太忙,后来有空再折腾。 几个理由加在一起,终于战胜了懒癌,让我动了起来。 步骤 @nubela 提供的工具非常友好且方便,第一次使用的我只简单做了一些配置,没有对 GitHub Pages 仓库做任何更改就实现了全站 HTTPS 化,自动使用了 Let’s Encrypt 提供的免费 SSL 证书,有效期 90 天,Kloudsec 会在它过期前自动续期,换句话说,只要 Kloudsec 还活着并且免费提供这项服务的话,后面就不用管这个了。 记录操作步骤如下: 打开 Kloudsec 为 GitHub Pages 提供的工具 Kloudsec for GitHub Pages。 按工具里的要求填好三个部分的内容,依次是 用于注册 Kloudsec 的邮箱和为 Kloudsec 账户设置的密码。 GitHub Pages 项目的 URL 和绑定的域名。 到你的域名解析控制面板里添加工具要求的 A 记录。 注意: 每个 A 记录应只保留一个 IP,比如之前将 @ 做了 A 记录到 GitHub Pages 的 IP 上了,那现在将其删除,并添加工具提供的 IP。 去上一步填写的邮箱里收邮件,激活 Kloudsec 账号并登录。 进入到 Dashboard 的 SETTINGS,在 Web Server Origin IP / Hostname 一栏填上 GitHub Pages 的可用 IP,比如我填写的是 103.245.222.133。 开启 HTTP 自动跳转到 HTTPS。(非必须,按自己需求来。) 进入到 Dashboard 的 PROTECTION,点击 SSL Encryption 里的按钮,选择你的网站: 将 Automatically redirect to HTTPS site? 下面的开关切换到 ON: 进入到 Dashboard 的 PLUGIN STORE,启用 Offline Protection、Page Optimizer 和 One-Click Encryption,一般来讲免费的计划就够用了,如果你想要使用付费计划提供更多更好的服务,那按需选择吧。 恭喜你已完成所有步骤!等待几分钟生效即可。 后话 实现原理 看 Kloudsec 的文档里描述的 HOW DOES IT WORK?,它提供的服务处于我们的网站服务器和我们的网站访问者之间,其原理是缓存了我们服务器上的页面,所以实际用户建立的 HTTPS 连接是用户的浏览器与 Kloudsec 之间的。 使用 Kloudsec 的好处 摆脱了证书不可信存在安全风险的不友好提示。 配置方便,一劳永逸。 访问速度并未受影响,因为缓存里优化了图片大小,合并了 CSS/JS,甚至可能更快了。 小绿锁看着舒心。 使用 Kloudsec 的风险和影响 貌似是个小公司,这样的免费服务能提供多长时间只有天知道。 因为用户看到是在 Kloudsec 上的缓存页面,所以我们更新 GitHub Pages 内容后,刷新线上页面效果的时间变久了,以前上传完基本马上就能看到,现在有时候部分页面会延迟两三分钟,我勉强能接受。 Update: 2016/6/16 现在基本上传完马上就能看到了。 很偶尔会出现样式加载不完整的情况,刷新就好了。 Update: 2016/6/16 最近基本没出现过了。 缓存页面合并了 CSS 和 JS 文件,使用开发工具在线调试时要找到样式源文件变困难了——可能看到全都在一个 CSS 文件里。不过我大部分情况下都是用 Jekyll 在本地调试好再上传,这点对我影响不大。 Update: 2016/6/16 现在貌似不会合并了。 如果引用了其它域名下的非 HTTPS 的 CSS 和图片资源等,开发者控制台下会有 error,显示不受影响。 Google 索引状态会受影响,目前本博客来自 Google 的流量完全没有了,估计需要一段时间才能恢复。 Update: 2016/6/16 应该影响不大,只是 Google Search Console 将 http 与 https 的未当成一个网站来统计,所以给我造成错觉了。 接下来,能做的就是祈祷 Kloudsec 不要挂了。:laughing: 其它做法 如果使用 GitLab 提供的 Pages 服务,那它直接支持添加自定义域名的 SSL 证书,可以配合免费申请的 SSL 证书一起使用。详情可见 零成本打造安全博客的简单办法。
本文记录的是一种判断当前激活的 Activity 是否属于本进程的方法。 约定:文中表述说一个 Activity 处于激活状态是指它是屏幕上当前展示的 Activity,且没有被 Dialog 覆盖。 原理 Activity 的生命周期由 Android 系统维护,所以使用一个 Activity 的生命周期方法 onResume 与 onPause 来记录它的激活状态是可靠的。 同理,因为前台 Activity 只有一个,所以判断前台 Activity 是否属于本进程只需要判断本进程是否有 Activity 处于激活状态即可。 实现 顺着这个思路,直接想到的方案就是去给代码里所有的 Activity 实现一个共同基类,然后在基类里的 onResume 和 onPause 方法去更新一个全局计数器了。 这固然可行,但遇到有些不能继承共同基类的情况,比如继承自 ListActivity 和 ExpandableListActivity 等 Activity 的子类的,就得重复去在具体 Activity 里的对应方法里添加代码了,一旦有一个 Activity 忘了添加,这个机制就失效了,所以并不优雅。 幸好 Android 在 API Level 14 的时候新加入了 android.app.Application.ActivityLifecycleCallbacks 接口,它会在 Activity 生命周期事件发生时产生回调: public interface ActivityLifecycleCallbacks { void onActivityCreated(Activity activity, Bundle savedInstanceState); void onActivityStarted(Activity activity); void onActivityResumed(Activity activity); void onActivityPaused(Activity activity); void onActivityStopped(Activity activity); void onActivitySaveInstanceState(Activity activity, Bundle outState); void onActivityDestroyed(Activity activity); } 这真是救星。 那么优雅的实现方案: public class MyApplication extends Application { private static boolean hasActivityActivate = false; @Override public void onCreate() { super.onCreate(); registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() { @Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) { } @Override public void onActivityStarted(Activity activity) { } @Override public void onActivityResumed(Activity activity) { hasActivityActivate = true; } @Override public void onActivityPaused(Activity activity) { hasActivityActivate = false; } @Override public void onActivityStopped(Activity activity) { } @Override public void onActivitySaveInstanceState(Activity activity, Bundle outState) { } @Override public void onActivityDestroyed(Activity activity) { } }); } public static boolean hasActivityActivate() { return hasActivityActivate; } } 然后在需要的时候调用 MyApplication.hasActivityActivate() 就行了。 当然别忘了在 AndroidManifest.xml 里声明指定你的 Application 类名: ... <application android:name=".MyApplication" .... 后话 本文记录的只是判断当前进程是否有 Activity 处于激活状态的方法,判断当前应用、其它应用的前后台情况有多种方法,它们的优缺点、适用场景在以下 GitHub 仓库有详细列举,有需求的同学可以参考: https://github.com/wenmingvs/AndroidProcess 参考 wenmingvs/AndroidProcess Checking if an Android application is running in the background
Node.js 的开发环境选择很多,比如 WebStrom/Visual Studio Code/Atom/HBuilder,还有万能的 Vim/Emacs 等等。 根据我个人的试用,WebStorm 应该是配置起来最省心,用起来最顺手的选择,而且有 Android Studio 和 PyCharm 的使用经验上手毫无障碍。但一来 WebStorm 价格不菲,二来最近刚刚被 Atom 圈粉,而 Visual Studio Code 和 Atom 又是这些编辑器里面与 Node.js 渊源最深的,所以最终决定最近在学习 Node.js 以及写一些练手项目时以使用 Atom 为主,Vim 为辅。 安装 Atom 插件 主要是它们: atom-ternjs 用于 Javascript 和 Node.js 的自动补全 script 用于一键运行程序 自动补全插件 Atom 上的 Javascript 自动补全主要依赖 atom-ternjs 插件,貌似没有什么其它更好的选择。(配合 autocomplete-plus 插件使用,Atom 默认已经安装。) 安装方法与安装其它插件无异,主要有三种选择: 图形界面。 在 Atom 的 Settings > Install 里搜索找到 atom-ternjs 并安装。 这种方法在国内需要科学上网。 命令行。 apm install atom-ternjs 这种方法在国内也需要科学上网。 本地安装。 cd ~/.atom/packages git clone https://github.com/tststs/atom-ternjs.git cd atom-ternjs npm install 一键运行插件 安装 script 插件,然后有两种方法可以一键运行/结束程序了: Packages > Script > Run Script/Stop Script。 快捷键。 Mac Windows 运行 cmd+i ctrl+shift+b 结束 ctrl+c ctrl+q 配置项目 atom-ternjs 插件对项目配置做了可视化,可以通过菜单来操作。 File > Open 打开 Node.js 项目文件夹。 Package > Atom Ternjs > Configure project Save & Restart server 之后会在项目根目录生成 .tern-project 文件,该配置文件里常用字段: 字段名 含义 ecmaVersion 选择 ECMAScript 版本 libs browser 表示原生 js 补全,jquery 代表 jQuery 补全 loadEagerly 指定加载解析的 js 文件 dontLoad 排除加载的文件 plugins ternjs 使用的插件,配置的扩展补全的库等 目前插件的配置页面暂不支持 plugins 部分配置,需要手动配置。 比如一份最简单的 .tern-project 文件的示例: { "ecmaVersion": 6, "libs": [], "loadEagerly": [ "**/*.js" ], "plugins": { "node": {}, "node-express": {} } } 它代表使用 ECMAScript 6,递归加载项目文件夹下所有的 js 文件(包括 node_modules),使用 ternjs 的 node 插件用于 Node.js 核心库补全,node-express 插件用于 express 补全。 这部分推荐详细阅读一下 atom-ternjs 的 README,会更清楚怎么回事。 创建/修改 .tern-project 文件后,执行 Packages > Atom Ternjs > Restart server。 进行完这一步以后,顺利的话你应该已经能愉快地看到原生 js 和 Node.js 的自动补全了;不顺利的话,看看下面的 Q & A 一节,有我遇到的问题的记录。 最终效果: Q & A 在 Mac 下按步骤官方的 README 操作后自动提示出不来? 我在 Windows 下按官方指南配置 atom-ternjs 倒是很顺利,按默认步骤操作完,然后在 plugins 一节添加 node 就一切 OK 了,但在 Mac 下貌似不配置 loadEagerly 为 **/*.js 智能提示出不来。 另外就是 .tern-project 文件放置的位置,最好与 package.json 放在同级目录。 安装 atom-ternjs 总是失败。 科学上网。 Vim 也有 tern_for_vim,作为主力编辑器,为何不使用它来写 Node.js 呢? 别提了,如果没有把 .tern-project 文件配置好,打一个 . 之后能卡五秒,严重拉低 Vim 编辑速度。 不过 tern_for_vim 也有一个好处,那就是不用像 atom-ternjs 这样每次改完配置后都要手动 Restart server。 后话 ternjs 功能强大,包括: 自动补全方法和变量 查找引用/定义 显示方法详情,包括方法签名和文档等 简单的重构 把它用好了还是能不错地提升开发效率的。