「QQ 沉默者」白皮书

序 「QQ 沉默者」是一款针对 QQ 的插件,实际上经历了数个产品形态,是一个典型的极端思想的产物。从2020年2月有了初步想法,至今(2020年9月)虽然从未公开发布,但我自己也是断断续续地使用改进了大半年。偶然得知列表里竟然还有人真的在寻求这样一个功能,所以我决定把这半年来的收获分享一下,希望不要有人步我的后尘,也当做是不愿意公开发布的一个解释。 这里不会涉及到很多的技术问题,相反,我更想从初衷与结果的角度谈一谈。 TL;DR 对经常不回消息的人,我们也不回他们的消息。以暴制暴的「QQ 沉默者」没能像想象中构建轻松的聊天氛围,相反,强化了独裁者的报复心态。它不是什么正义的神器,而是一个潘多拉盒子,打开之后容易让人为之着魔、疯狂。 同样对经常不回消息很气愤的朋友,希望你们能先了解聊天的目的与本质,沟通是为了解决问题,交流是为了增进感情。如果不能,删除或者再也不见或许是对大家最好的办法。不要尝试以暴制暴,如果成功了,你会陷入暴力的沼泽;如果失败了你要承受长久的内疚。 既然不合适,就天各一方,没必要指责或强求。更没必要给自己找不悦。 起 QQ 沉默者初衷是解决某些人屡次无故不回消息,或沟通过程中常常突然消失而后了无音讯的问题。相信每个人都会有类似的体验,甚至某些自己就喜欢不回消息却刷朋友圈的人同样讨厌这种经历。他们总是有意无意地不回消息,并将其调侃为「意念回复」,试图娱乐化给另一方带来的困扰,而我们始终没有一个良好的反制策略。我大致分析了一下,这通常与性格和长期的成长环境有较大关系,粗俗地讲就是「狗改不了吃屎」(当然我这里意思不是把这类人比喻为 :dog: 你们懂就好)。 很多次气急败坏地想删掉好友,但又觉得很不妥;无数次下定决心再也不要找他,却又按捺不住躁动的心以及有消息必回的强迫症。终于,有一个折中的办法——以其人之道还治其人之身。 既然你回复慢,那我就回复得和你一样慢,甚至更慢 ╯^╰ 我也许不是第一个有这种想法的人,但可能真的是第一个付诸行动的人。 变 最狠的第一版 既然愿意投入精力去做这样一个有点匪夷所思的插件,大家也应该就能感受到我对于这种行为的憎恶是多么强烈。一开始我只是想惩罚一下那些穷凶极恶的人,但在开发过程中,由于技术的限制以及…思想的强化?这种愤怒逐渐转嫁到了更广泛的好友身上,以至于最终干脆砍掉了白名单,强制性地对所有好友生效——不论你是客户、老师、同学、朋友,没有什么好商量的。相应的,「屡次」「无故」也不再是遭到反制的门槛,毕竟通过现有技术难以自动化界定这些标准。 于是第一个雏形就这么出来了:它粗暴地统计每个人的平均回复时间,不分对象不论缘由地对所有人套用规则。不得不承认,这很爽。**关键在于这个「爽」似乎不是来自解决了问题的兴奋,而是报复后仇恨的迸发——就像被人扇了一巴掌,你把他打倒在地连扇20巴掌一样。**结果自然也不言而喻。 人性的第二版 成人们都知道世界不是非黑即白的,那就也不应当对延时回复一概而论。法律上讲究疑罪从无,显然一开始的做法于情于理都说不过去,于是我决定给自己一个机会。现在每一次发送都可以指定为立即发送,当然,默认情况下还是根据平均回复时间来延时。 说它人性是因为没有机械地套用算法,有了人类的参与,对事情的处理或多或少不会再那么僵硬。 然而我错了。**这次改进其实使整个项目的方向从「技术向善地解决问题」转向了「独裁思维的控制与报复」。**表面上我可以根据实际情况选择暂时容忍他的小调皮,实际上相当于把自己推上了独裁者的宝座,因为我掌握着“生杀大权”,原本为了避免误判才点击的立即发送,不知不觉间好像成了一种恩惠。 既然我如此善良地放过你了这一次,难道你不应该感激我吗? 这种想法从局外人的角度看很可笑,要想感同身受,大家不妨类比一下溥仪在新中国成立后逛故宫也要买票。 后来逐步又改了许多,包括区分开首次建立对话的回复时间与后续聊天的回复时间;定期清零平均时间;低于阈值不触发延时等等。但这些措施没能把项目拉回正轨。 灭 最后我意识到,自己做的是不是太过火了,但这并不是一开始的初衷啊。**聊天氛围完全没有因此变得轻松,相反,更加紧张了。**于是干脆去掉了延时功能,只保留回复时间统计作为参考,试图让一切回到最初的样子。 正是这次更改,我才发现了问题的根源所在。原本不回消息只是少数人的行为,我们应该关注的焦点是不回消息背后的原因与态度,而不是回复时间这一冷冰冰的数字。前几天有一个掀起波澜的文章《外卖骑手,困在系统里》,当中有一端话: 而在整个系统中,最无解的部分在于,在让骑手们越跑越快的推手中,也包括骑手自己。 孙萍说,系统要求骑手越跑越快,而骑手们在超时的惩戒面前,也会尽力去满足系统的要求,「外卖员的劳动越来越快,也变相帮助系统增加了越来越多的『短时长数据』,数据是算法的基础,它会去训练算法,当算法发现原来大家都越来越快,它也会再次加速。」 相比较2月份,现在的我对回复时间的容忍度已经大幅下降,并且不再满足于曾经那些可以理解的合理理由——毕竟有多么紧急的事能让人忙到连10秒钟回个「一会再说」都来不及呢?**这一变化的推手正是 QQ 沉默者插件本身。**它给我罗列了统计数据,把选择权交给了我,慢慢的我眼中只剩下数据。哪怕现在没有了延时功能,光是盯着这些数据甚至都觉得怒火中烧——竟然半小时都没有回复。 最后 对于不回消息的执拗,我已经被自己开发的插件强化了难以形容的极端,并在痛苦中挣扎。理想中和谐的聊天氛围遥遥无期,原本较为和谐的氛围也毁于一旦,剩下的只有不安、猜疑和报复。**「QQ 沉默者」不是什么正义的神器,它是一个潘多拉盒子,打开之后容易让人为之着魔、疯狂。**假设大部分的人都安装了这样一个插件,QQ 也将变成黑暗森林,所有人总体的平均回复时间终究会越来越长,而 QQ 也将趋于冷寂。 但是以上不代表我对初衷的否定。 现在我已经关闭了这个插件,尝试着不去关注那些数据。而那些经常性不回消息转头刷个空间的人已经删掉了。我同样看过一篇文章《我为什么有时候不回你消息?》,可能真的是习惯想法不同,看完后没有丝毫理解,只有厌恶,没想到竟然有人能把不回消息讲的那么理所当然,甚至还把自己塑造成一个受害者的形象。 和我有一样感觉甚至希望得到这个插件的朋友,希望我们都能先了解聊天的目的与本质,沟通是为了解决问题,交流是为了增进感情。如果不能,删除或者再也不见或许是对大家最好的办法。不要尝试以暴制暴,如果成功了,你会陷入暴力的沼泽;如果失败了你要承受长久的内疚。 与自己合拍的朋友在一起,感受下美好;对不合拍的同事宽容,展现出大度。不要成为第二个我,这个项目就此终结,这也算是最后的技术向善吧。

September 26, 2020 · Chenhe

Lean OpenWrt 编译使用小记

Lean OpenWrt 是 Lean 大佬修改的 OpenWrt 开源版本,和 KoolLede 是国内两个比较有名的分支。Lean OpenWrt 不提供预编译文件,所以想使用的话需要自己编译一遍。 编译需要使用 Ubuntu 系统,不要使用 WSL 会出问题。 编译没什么好说的,README 里面写的很详细一步步来就行了,推荐使用这个脚本白嫖下 Github 的服务器来云编译。 编译选项 如果没什么特别要求,保持默认就行,但来刷 Lean 的基本上都有一些奇奇怪怪的需求😁 这里有插件清单,包括 xls 格式的:https://www.right.com.cn/forum/thread-344825-1-1.html 酸乳饮料 Lean 内置的酸乳饮料插件很好用,而且没发布到官方源,也是很多同学来的目的。可惜现在默认给取消了,找回来分三步: 取消 feeds 注释。修改一下 feeds.conf.default 文件,去掉最后一行 helloword 的注释。重新执行 ./script/feeds update -a && ./script/feeds install -a. 如果是云编译,修改一下 diy-part1.sh 取消带 helloword 那一行的注释。 make menuconfig 生成配置文件的时候,在 LuCI - Applications 里选中 luci-app-***-plus. 系统安装完毕后如果找不到菜单,那命令行执行一下 0xDEADBEEF > /etc/config/google_fu_mode 就可以了。 IPv6 Lean OpenWrt 默认不支持 IPv6,启用的话在 Extra packages 里选中 ipv6helper 就好啦,所需的依赖会自动勾选。...

August 12, 2020 · Chenhe

中文移动开发所想—火山平台为例

此文写于偶然间发现火山安卓平台发布了 libGDX 类库的即兴思考,组织较为混乱,也可能包含技术或事实错误,还请指正。 部分观点较为主观,无引战意思,还望海涵,请勿撕逼。 引言 说起中文编程,易语言绝对是领导者。尽管其有着数不清的槽点,也因此被专业人士嘲讽,但事实胜于雄辩——易语言至今还拥有相对活跃的社区,众多非科班的业余开发者照样使用易语言做出了优秀的软件,也在不少接单平台拿到了足以维系生活甚至不菲的收入。易语言也算是我入开发坑的引路人,最初小学四年级时接触过 VB,本来英语就很差的我差点昏厥 🤪。别看区区几个关键字,光是 int, float, bool 之流就已经很头疼了。后来五年级试用易语言像是打开了新世界的大门,一直持续到初三才转的 JAVA。 转 JAVA 的契机就是 PC 流量逐步迁移至移动设备,再加上中学生使用电脑本来就不多,上课玩手机到是不少 😜。在迁移的过程中,一匹黑马打乱的这一进程——E4A。这是一个第三方仿照易语言开发的面向 Android 的中文开发工具,最初是基于已经停止维护的 Google Simple 语言开发而来。开发 Android 应用是许多易语言用户的心愿,当然也包括我在内。幸好当时的 E4A 还是萌芽时期,各种支持库非常不完善,甚至达不到「可用」的标准,否则或许我就错失了转向正规军的最后机会。(此处正规军指的是对应平台官方支持的原生开发,无歧视含义) 现在是2020年了,吴涛先生憋了多年的大招虽然姗姗来迟,但也在稳步发展——火山软件开发平台。显然这与 E4A 不是一个量级的产品,也体现了吴涛先生宏大的理想:做一款中间语言,能够编译到各个平台从而实现真正的大一统。这个想法并不罕见,但打出中文的口号倒是首家,吴涛先生将其称为语言之上的语言。尽管早已转向更大众的技术栈,但仍情不自禁地关注中文开发的动向,近期在研究 libGDX 相关东西无意中又搜到了火山安卓开发平台的相关内容,或许这就叫羁绊吧。 偶遇&现状 libGDX 是一款采用 JAVA 的跨平台游戏框架,近年来由于国内环境问题,个人独立游戏渐渐没落,而大型游戏通常需要更专业的游戏引擎,因此 libGDX 国内资料奇缺。而在搜索引擎给出的结果中,几个近期发布的页面格外醒目:LibGDX引擎里面有没有封装box2d库? - 火山安卓俱乐部 - 火山软件…。没想到火山官方团队也瞄准了这一开源框架,在 Volcano3D 游戏引擎较为专业、入门困难的背景下,或许引入这一小巧的框架能大幅提升火山安卓开发平台在游戏方面的表现。 老友邂逅定当叙叙旧,我花了近2天时间大致了解了下 E4A 与火山安卓的现状,也重点关注了 libGDX 支持库发布后的市场反应。结论有点遗憾,目前看来整体不尽如人意。 火山安卓官方论坛日均帖子数在50左右,其中还包括很多官方的模板式回复,与易语言难以相提并论,相当于小众中的小众。 火山安卓曾也有引以为傲的作品— Pandownload 安卓版,随着不久前 PC 版开发者被逮捕,安卓版相关帖子也被迅速撤掉,尴尬的是,在此之前,火山甚至使用官方账号对 Pandownload 大肆赞扬,并赠送其一套正版火山密钥以资鼓励(原帖已被删除,当时截图不完整没有体现发帖账号)。除此之外,官方账号还为一个疑似炒共享经济概念的产品做推广,原帖备份在此。当然,作为平台的开发方对自家平台开发出的著名软件鼓励推广是无可厚非的,但类似 Pandownload 之类处于灰色地段的程序,竟然毫不避讳地用官方账户表扬着实值得商榷。这也侧面表明火山开发平台遇到了和易语言类似的情况,即用户多是非专业人士,所开发的产品往往也是非专业产品。常规的小工具基本上没有发展空间,唯独灰色产品(例如采集、破解、外挂)或者打一枪换一炮类产品有增长的可能。毫无疑问这种生态对平台的发展是非常不利的,火山官方不清楚是真的不知道,还是没有办法,竟然将这一不利因素视为救命稻草,妄想借此来壮大生态,其后果难以想象。 libGDX 类库或许因为发布较晚,目前社区还没有太多的讨论,同样没有成功案例出现。论坛里仅有的帖子则全部是封装 bug 的反馈。这不禁让我想起 Cocos2d-js 引擎的困难处境。Cocos2d 一开始是 IOS 端游戏引擎,后来发展为跨平台,进而引入了 Lua 脚本后期又引入了 js 脚本,并实现 js 绑定。Cocos2d 曾希望将开发的重点转移到 js 来实现对 html5 的支持,对于 Native 设备则是将 js 绑定到原生 API。不巧,原生 API 经过多轮改变已经非常混乱,于是又引入了绑定生成框架在自动化这一繁琐的任务。折腾了许久,Cocos2dX 原生引擎,以及衍生引擎基本上都停滞了,重点转向了 Cocos Creator 这一对标 Unity3d 的全新平台。我最后接触 Cocos2d-js 是在2018年,那时 js 绑定非常不完善,众多 API 在 web 端与本地端行为不一致,甚至函数参数都不一致,对于一个一跨平台为卖点的产品,这是致命的打击。...

May 11, 2020 · Chenhe

Android 磁盘最近最少缓存实现 - DiskLruCache 源码分析

 DiskLruCache 是一个 Android 端使用广泛的磁盘 LRU (最近最少使用)缓存算法的实现库,甚至在 AOSP 中都有使用。 基础使用 为了对整体架构有个印象,方便找分析入口,先看看基本的使用。 实例化: val cache = DiskLruCache.open(cacheFile, version, valueCount, maxSize) 写入: val editor = cache.edit(key.md5()) val os = editor.newOutputStream(0) 读取: val snapshot = cache.get(key.md5()) val is = snapshot?.getInputStream(0) 关于它的具体使用可以看这篇文章。 整体来看并不复杂,实际上代码量也确实不多。通过读取时的 snapshot 封装猜测后续修改不会影响已经读取到的值。 日志文件 DiskLruCache 的一个显著特征就是在缓存目录有一个 journal 文件,用于记录所有操作日志,大概长这个样子: libcore.io.DiskLruCache 1 19 1 DIRTY c915a1313f3e413252fae2957469dcd2 CLEAN c915a1313f3e413252fae2957469dcd2 50196 READ c915a1313f3e413252fae2957469dcd2 DIRTY 8b33d00d5f9d443095e20829a1367e05 CLEAN 8b33d00d5f9d443095e20829a1367e05 159594 前五行是元数据: 一个魔法数,固定字符串,用于标识这是一个 DiskLruCache 的日志文件。 DiskLruCache 版本号,从发布以来就没变过,预留一下便于后续格式更新。 开发者声明的应用版本号。 每个 key 对应几个 value,一般都是1。一个 key 支持写入多个值,每个值都是一个文件,文件名是 key....

March 9, 2020 · Chenhe

Git HTTP+SSH 代理配置

本来是没有什么技术含量的东西。奇怪的是网上答案千篇一律而且大部分都是错的,不知道要坑坏多少人:smiling_imp:。 HTTP 代理 HTTP 代理相对简单 # HTTP 代理 git config --global http.proxy http://127.0.0.1:1080 git config --global https.proxy http://127.0.0.1:1080 # Socks5 代理 git config --global http.proxy socks5://127.0.0.1:1080 git config --global https.proxy socks5://127.0.0.1:1080 注意这里的 socks5 仅仅是代理使用的协议,它依然是针对 http 设置的,所以仅对 http 协议的仓库有效。使用 git@xxx 这种 ssh 连接的不会使用代理。 也可以分域名设置代理: git config --global http.https://github.com.proxy http://127.0.0.1:1080 git config --global https.https://github.com.proxy https://127.0.0.1:1080 SSH 代理 SSH 代理需要在密钥目录 (~/.ssh) (Windows 下是 C:\Users\{UserName}\.ssh) 新建一个 config 文件,没有后缀名。 Linux 系统写入以下配置(未验证): # 需要 netcat ProxyCommand nc -v -x 127....

March 3, 2020 · Chenhe

Android 自定义 View 的默认属性值(四个构造函数的作用)

常见的套路 自定义 View 同学们一定或多或少都接触过,毕竟设计给出的样式往往从来不是系统默认的🙂。 自定义 View 时往往遵守下面这样的套路: 在 attrs.xml 中声明自定义属性(或使用系统预定义属性),经常用 declare-styleable 包裹起来。 继承一个 View 并写两个构造函数:constructor(context: Context), constructor(context: Context, attrs: AttributeSet?). 前者用于代码创建,后者用于 xml 创建。 context.theme.obtainStyledAttributes() 来读取属性值,并在 getXXX() 函数中给出一个默认值。 一切似乎理所应当行云流水并且工作良好,但这真的合理吗?(废话 要是合理这篇文章是在自嗨嘛😅 考虑以下问题: 用户(本文指使用 View 的开发者)如何统一设置默认样式? 默认值硬编码在代码文件中是否耦合过重? 当然用户可以定义一个 style 每次使用都设置一下,你不觉得太麻烦了吗? 当然也可以直接写在主题的样式中,但如果有冲突属性怎么办?例如自定义 View 使用了系统定义的 android:textColor 但又不希望其他控件例如 TextView 受影响。 为了后边便于说明,我们定义个 CircleCheckBox,并定义如下属性: <!--lib: attrs.xml--> <resources> <declare-styleable name="CircleCheckBox"> <attr name="normalColor" format="color" /> <attr name="selectColor" format="color" /> <attr name="outlineColor" format="color" /> <attr name="outlineStrokeWidth" format="dimension" /> <attr name="android:checked" /> </declare-styleable> </resources> 被忽略的两兄弟 除了常用的两个构造函数之外,还有一个三个参数的构造函数,从 Android 5....

February 16, 2020 · Chenhe

一起动才够嗨!Android CoordinatorLayout 自定义 Behavior

CoordinatorLayout 的今生前世 联动效果 现代化的 Android 开发一定对 CoordinatorLayout 不陌生,CoordinatorLayout + AppBarLayout + CollapsingToolbarLayout + Toolbar 的全家桶更是信手拈来,无需一行代码光靠 xml 就能实现下面这种折叠导航栏的炫酷效果: 这种搭配的教程已经非常多了,不是本文的重点。在使用 xml 时候肯定不少同学掉过一个坑:界面主要内容与头部元素重叠了!粗略了解一下因为 CoordinatorLayout 的布局方式类似 FrameLayout 默认情况下所有元素都会叠加在一起,解决方案也非常玄学,就是给内容元素添加一个 app:layout_behavior="@string/appbar_scrolling_view_behavior" 属性就好了,简直像黑魔法! Unfortunately,代码并没有魔法,我们能偷懒是因为有人封装好了。跟踪进这个字符串是 com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior 显然这是个类!事实上这就是今天的重头戏 —— Behavior. 这个效果太复杂了,所以 Google 才会帮我们包装好,下面换一个简单的例子便于学习: 这是仿三星 One UI 的界面。上面是一个头布局,下面是一个 RecyclerView,向上滑动时首先头布局收缩渐隐并有个视差效果,头部彻底隐藏后 RecyclerView 无缝衔接。向下滑动时同理。 事件拦截实现 在继续探索之前,先思考一下如果没有 CoordinatorLayout 这种现代化东西怎么办?因为这牵扯到滑动手势与 View 效果的糅合,毫无疑问应该从触摸事件上入手。简单起见暂时只考虑手指向上滑动(列表向下展示更多内容),大概需要进行以下操作: 在父布局 onInterceptTouchEvent 中拦截事件。 父布局 onTouchEvent 处理事件,对 HeaderView 进行操作(移动、改变透明度等)。 HeaderView 完全折叠后父布局不再拦截事件,RecyclerView 正常处理滑动。 现在已经遇到问题了。因为一开始父布局拦截了事件,因此根据 Android 事件分发机制,哪怕后续不再拦截其子控件也无法收到事件,除非重新触摸,这就造成了两者的滑动不能无缝衔接。 接着还有一个问题,反过来当 RecyclerView 向下滑动至顶部时,如何通知 HeaderView 展开? 哪怕解决了上述主要问题,肯定还有其他小毛病,例如子控件无法触发点击事件等等等非常恼人💢。假设你是大佬完美解决了所有问题,肯定耦合特别严重,又是自定义 View 又是互相引用的乱七八糟😵 所以现在就不往下深究了,有闲情雅致有能力的同学可以尝试实现。...

February 15, 2020 · Chenhe

Android 事件分发机制

Android 事件分发机制一直让人头痛,之前也是面向 GitHub 编程得过且过。今天下定决心了解一下,以便后面自己定制 View 效果。Android 触摸事件有三个基本类型:ACTION_DOWN, ACTION_MOVE, ACTION_UP,后两者的传递顺序取决于 DOWN 的传递结果,所以就从 ACTION_DOWN 开始分析。 ACTION_DOWN 全景 借用一张下面参考文章里的全景图片,注意这里指的仅仅是 ACTION_DOWN 事件的传递。先解释一下: 白色箭头表示事件传递(函数调用) 箭头上的标注表示调用前提。(supper 表示上一级直接调用,false 表示若上级返回 false 则系统继续向下调用) 白色方块内的消费箭头表示若此函数返回对应值,则事件终止传递(也称作被消费了) 以左上角事件入口为例,首先 Activity 收到事件触发 dispatchTouchEvent,不论返回 true 还是 false 事件均终止,任何组件的任何函数均不会再被调用(包括 activity 自己的 onTouchEvent),只有 return super.dispatchTouchEvent() 也就是调用了 super 才会继续传递到下一级。 对于下一级 ViewGroup 的 dispatchTouchEvent 来讲,返回 true 同样消费事件立即终止传递。返回 false 则会回溯到上一层的 onTouchEvent。调用 super 则继续向下传递。 全程传递 我们假设事件没有被拦截、消费,那么整个传输流程类似 U 型: 不难看出,整个流程分为左右两部分,我们暂且叫做分派与回溯。分派是自顶到底的,主要用于事件的传递。回溯是从底到顶的,主要用于事件的处理。所有方法的默认实现就是 return super.xxx() 因此事件默认情况下可以走完整个流程。 拦截器 ViewGroup 细心的同学应该注意到了,在分派过程中除了整齐的 dispatchTouchEvent 方法外,乱入了一个 onInterceptEvent 方法,可以称之为拦截器。顾名思义拦截器的作用就是拦截此事件供自己使用(就像大理 gov 一样对待口罩那样)。不难看出 dispatchTouchEvent 方法调用后根据内部处理的不同有三个后果,分别是 ①终止传递 ②向下传递 ③向上回溯,而前面提到过,一般来讲处理具体的处理会放在 onTouchEvent 中。那么问题来了,终止传递后我居然自己无法处理事件?(参考 U 型图中的 ViewGroup 层,无论 dispatchTouchEvent 作何响应都无法调用自己的 onTouchEvent)...

February 13, 2020 · Chenhe

Jenkins+Github 持续测试

说明下环境: Windows 10 (因为还需要跑其他一些东西) Jenkins 2.204.1 LTS 内置 Jenkins war 2.217 配置 GitHub 鉴权 Jenkins 的安装过程很简单就不写了。首先需要安装 GitHub Plugin 插件,如果安装 Jenkins 时选择了安装常用插件那么应该已经装好了,否则就去手动安装一下。 为了和 GitHub 账户连接,当然要配置一下鉴权。进入 Manage Jenkins - Configure System,找到 GitHub,添加一个 GitHub 服务器,API URL 保持默认 https://api.github.com 就行,名称随便填。点击凭据右边的问号可以看到帮助。 去 GitHub 生成一下 AccessToken,Note 就是一个备注随便填,下面的授权范围要勾选 repo 和 admin:repo_hook. 复制一下生成的 Token 回到 Jenkins 添加凭据,类型选 Secret text,将 Token 粘贴到 Secret 编辑框内,ID 留空,描述 自己填一下其他保持默认。 最后在下拉框中选选中刚刚添加的,点一下连接测试,通过就行啦😁 Webhooks Webhooks 用于当发生特定的事件(例如一个新的 push)时 GitHub 主动去通知外部服务。打开需要集成的项目,进入 Settings - Webhooks,点击 Add webhook....

January 28, 2020 · Chenhe

Moshi with Kotlin Json 库—现代化的最佳损友

Moshi 与 Kotlin Android(Java) 平台已经有许多 Json 库了,包括 Google 推荐的 Gson,广受欢迎的 Jackson,阿里的 FastJson 等,但今天要说的是大名鼎鼎 Square 公司的 Moshi. (什么?没听说过 Square?OkHttp, Retrofit 可都是他的著作) 轮子已经有很多了,不过自从 Google 将 Kotlin 定为 Android 亲儿子开发语言后,竟然包括 Gson 在内的几乎所有 Json 库均不支持,这当然可以理解,因为他们大多数采用了 Java 反射机制,注定难以适配 Kotlin 独有的特性。因此今天要介绍 Moshi —— 一个与 Kotlin 兼容极好的现代化解析库。 传统 Java Json 库用于 Kotlin 主要产生两个问题: 不支持空安全。即使 Kotlin 变量定义为非空,若 Json 为空则解析出 null 而不会抛出异常,直到数据使用时才抛出诡异的 NPE. 为啥说诡异?因为定义非空的变量默认不需要进行空判断,但实际上他是空的,有点挂羊头买狗肉的意思。 不支持默认参数。Kotlin 的 data class 极大地方便了开发,默认参数的语法更是直呼太爽。可惜这种现代化写法或遇到两个问题:①默认参数无效。②解析失败因为没有无参构造函数。而解决办法更是令人崩溃:①不要使用默认参数。②给所有形参全部加上默认参数。 因此,如果你已经使用 Kotlin 作为主要语言,Moshi 将会是绝佳的选择。(KT 自带的解析库也可以考虑) 另外 Moshi 的贡献者也是 Gson 的主要贡献者,因为 Gson 的先天不足且他已经离开了 Google,故开发了 Moshi,具体可以参考他在 Reddit 的回答:Why use Moshi over Gson?...

January 25, 2020 · Chenhe