AlphaGao's Blog

恐惧源于火力不足,紧张源于准备不足!

0%

1. 背景

在 go 的项目中同时引用较新版本的 etcd 和 protoc-gen-go 会产生一些问题,具体表现为:

1
2
3
4
5
6
7
8
9
10
11
go: finding module for package google.golang.org/grpc/naming
go: finding module for package github.com/coreos/bbolt
go: found github.com/coreos/bbolt in github.com/coreos/bbolt v1.3.6
go: node/watch imports
go.etcd.io/etcd/clientv3 tested by
go.etcd.io/etcd/clientv3.test imports
github.com/coreos/etcd/auth imports
github.com/coreos/etcd/mvcc/backend imports
github.com/coreos/bbolt: github.com/coreos/[email protected]: parsing go.mod:
module declares its path as: go.etcd.io/bbolt
but was required as: github.com/coreos/bbolt

或者
1
2
3
4
5
6
uapm-agent/watch imports
go.etcd.io/etcd/clientv3 tested by
go.etcd.io/etcd/clientv3.test imports
github.com/coreos/etcd/integration imports
github.com/coreos/etcd/proxy/grpcproxy imports
google.golang.org/grpc/naming: module google.golang.org/grpc@latest found (v1.45.0), but does not contain package google.golang.org/grpc/naming

这都是由于 etcdv3 依赖了较低版本的 grpc(v1.26.0) 导致的。

想立刻知道解决办法的可以直接拉到第三节查看。

Read more »

前言

刚开始接触 Go 的时候,被 Go 这种可以直接返回多个变量的设计惊呆了,而且错误也是作为变量与函数结果一并返回。在此之前接触的都是 Java 或者 Kotlin 这种只有一个返回值的语言,而这两种语言本身的错误处理机制都是在函数签名上抛出一个异常,由方法调用方决定是否要处理该异常。Java 中所有的异常都继承自 java.lang.Throwable 接口,然后又分为 Error 和 Exception 两个分支,其中 Error 表示不该由程序处理的错误,通常由虚拟机抛出;而 Exception 又继续分为检查异常和不受检查异常。检查异常必须显式处理,否则编译器无法编译通过,不受检查异常就可以选择性是否进行处理。

Read more »

刚开始写 go,因为用习惯了 JetBrains 系列的 IDE,所以还是选择用 GoLand 来做 Go 开发。 GoLand 本身相当强大,不过有一点确实比不了 VS code,就是默认没有 golint 的支持,需要用户手动配置和开启。

Read more »

前言

如果说我的 2019 年要用一个词来形如,我给自己的是:废柴。是的,这一年我几乎一事无成,做什么什么半途而废,就连 2018 年的总结也懒得没写。其实本来我也没打算写 2019 年总结的,不过昨晚看了某个关注的博主写的年度总结,写的真好啊,这一年里发生的大事小事,开心的不开心都写了出来,分门别类的也很清晰,同时还贴了数百张图来记录,这样的总结就非常有意义啊。于是也动了继续写年度总结的想法,所以还得感谢这位博主 提拉拉拉就是技术宅

为了能总结得更有条理,我会把过去这一年分成几个方面来分别回顾(其实就是模仿那位博主),不知道我在一个个回顾的时候会不会内牛满面,悔恨不已啊 😂。

Read more »

fragment 的碎碎念

GraphQL 大家都不再陌生了,很多技术前瞻(作死)的公司都在用了,其中 fragment 作为一个 feature 我觉得很有必要单独拿出来说道说道。GraphQL 作为一种查询语言,从 OOP 的思想来看,fragment 可以看作是这个语言的 class ,也就是对一个具有相同属性的对象的定义。然后就可以多处引用,而无需多次编写。一个基本的 fragment 用起来形如下:

Read more »

作为一名程序员,肯定对极客这个词不会陌生。很多人标榜自己具有极客精神,但是观察他们的说话方式,行事风格,其实是与极客精神相去甚远的。但这可能也不能怪他们,也许很多人并不真的了解极客一词的内涵。

极客,又译为技客、奇客,是英文单词 geek 的音译兼义译。原本的俚语是指反常的人。
这个词在“美国俚语”中意指智力超群,善于钻研但不爱社交的学者或知识分子,含有贬义,因为极客常常醉心于自己感兴趣的领域,可以牺牲个人卫生,社交技巧或社会地位(但并不是所有的geek都会这么做)。但近年来,随着互联网文化兴起,其贬义的成分正慢慢减少。
但这个词仍保留拥有超群的智力和努力的本意,又通常被用于形容对计算机和网络技术有狂热兴趣并投入大量时间钻研的人。所以俗称发烧友或怪杰。如计算机怪杰(Computer Geek),技术/科技怪杰(Techno-geek),玩家怪杰(gamer geek)等。
———— 源自维基百科

我对极客的理解非常简单:追求极致。

Read more »

我是一名软件开发人员,工作一年多,技术还处于初级水平吧,虽然完成日常开发任务问题不大,收入勉强过得去,在上海这个城市倒也还能养活自己,衣食也算无忧,但总觉得少了点什么。

Read more »

接触 MVP 已经很久了,但我觉得我还没有真正掌握它。它总是在不经意间给我带来意想不到的。。惊,没有喜。最早接触 MVP 是在哪里早就已经忘记了 ,只是依稀还记得对 MVP 的理解与使用经过了好几个时期。

Read more »

很多 App 都会要求输入的字符种类、长度有所限制,在此之前我其实已经遇到了这样的需求,只能输入中文和英文,并且不同的语言所限制的长度不同。那个时候会觉得这样的限制比较麻烦,因为总认为涉及到中文的判断就比较麻烦,所以推脱说实现比较麻烦没有太多的时间,就给暂时压了下来。直到第二次遇到这样的需求我直到没办法再退了,结果 google 了下发现判断中文也没那么麻烦,使用正则判断中文字符范围即可。

Read more »

为什么这么做:公司的 jenkins 是搭建在阿里云上的,处于外网,但是 jenkins 服务器本身配置较低,最开始只是为了服务端自动化构建搭建起来,后来配置 Android 打包后就一直处于性能不够的情况,频繁打包失败,甚至会导致服务器直接卡死,影响其他的项目构建,就这还是在服务器从 1 核 2G 升到了 1 核 4G 的条件下了。但是为了 Android 打包继续增加服务器的配置,明显是不划算的。正好公司的文件共享主机性能不错,4 核 16G,用来打包绰绰有余。一开始计划在内网机器上也配置 jenkins ,但这种方式 gitlab 的 webhook 貌似没办法调到位于内网的 jenkins ,即使做了内网穿透也无济于事,具体原因不明。后来在搜索中看到了这篇博文:远端GitLab+Jenkins(CentOS)+本地Mac 做CI自动打包iOS上传到蒲公英, 受到启发,也决定通过配置内网节点的方式来做。

Read more »

很惭愧这个年终总结一直拖到年后,2018年已经过去了快两个月才开始写,一是因为工作还比较忙,二是总感觉一个阶段还没有结束,所以无法做一个阶段性的描述。在过完年后有了一段时间的空白期,即时回到了工作岗位也还是对具体的工作内容有点模糊,趁此机会就写个年终总结吧,当然既然到了二月份那么二月份之前的时间也会写进去。

Read more »

关于 graphql 和 RestFul 的差别我就不说了,很多人都说过,如果自己没有体会我写在这里也不过是复制粘贴而已。这片文章的内容是我三个月使用过程中的经验,同时也是我在公司内部的技术分享,从 ppt 改成博客,勉强能够凑成一篇吧。

Read more »

已经一个多月没有更新了,因为公司开了新项目,并且由于业务的特殊性,上线期限比较紧,又碰上国庆长假,所以时间非常紧张。这段时间一直都是加班加点,偶尔周末也会加一天班,所以整个人都比较累,回家了也没什么精神写博客了。正好到这周终于没有什么杂事来打扰,有空总结下这段时间的经验和收获。

这次就主要说一下项目中用到的新的 API 请求框架 GrqphQL 。GrqphQL 是由 facebook 开源的新一代 API 请求框架,具体的介绍和与 REST 的差别大家还是 Google 吧,我也不想 copy and paste,不过我会把具体使用过程中遇到的问题和体会写在这里,供大家参考。

Read more »

转眼入职两个星期了。这两个星期我从最开始的担心,到燃起熊熊斗志,再到焦躁不安,再到现在的归于平静,算是经历了一个比较大的回转。能够进入这家公司,不能不说是运气使然,大概恰好是看上眼了,然后进来了。因为在面试这家公司之前,已经拿到了一个 Offer ,因此心里其实没什么压力,不过自己也觉得手里的 Offer 要价太低,抱着保一争二的心态,眼前这家的要价差不多是手里这家的 2 倍,但是能要到这个价钱还是眼前这家本来的薪资范围就比较高,因此尽管我自己降了一点还是比较高的水平,对于我一个应届生已经足够激动了。面试的过程比较愉快和轻松,尽管细节技术问题几乎没怎么问,也没有看我的作品案例,但就是没觉得有不靠谱。尽管如此,对于能够进入这家公司我还是没有太大的自信,但最终还是进去了,可能不仅是技术和经验因素,还有其他方面特质的因素,因此还是感谢面试的几位大哥大姐对我的认可。

Read more »

0. 闪屏流程分析

要说到闪屏,几乎已经是所有 App 的标配了。但是各家对于闪屏的理解和应用方式各不相同。比如 Google 自己并不建议在闪屏页中设置过长的停留时间,而只是作为冷启动过程的一种变相缓冲,以就是仅在背景上设置一个 APP Logo 来避免屏幕留白。但在中国,几乎所有的流量还不错的 App 都会在闪屏页中加入广告,或者自己应用中某个主题活动的宣传,在增强品牌效应。这其中又有不同的使用流派,但基本都可以归为一个流程:

部分 App 会直接省略 Logo 这一环,直接加载闪屏页。观察了几个常用 App 后,我发现网易云音乐的闪屏逻辑是最好的,这不是说网易云音乐的闪屏流程与上面的图片有什么不同,而是网易优化了冷启动过程。一般来说,App 在点击 back 退出以后,再次打开会重新进入冷启动流程,也就是会再次进入闪屏显示流程,但是网易云音乐就不会:它只会在你第一次打开该 App 的时候加载 Loge 和 Splash,然后即使你按了 back 退出到屏幕,再次打开就不会再走闪屏流程了。这样即使用户多次退出打开也只会显示一次 Splash 页面,那么就算网易云的 Splash 时间稍微长一点你也不会在乎的吧(斜眼笑)。

1. 闪屏流程优化

那么这种模式是如何实现的呢?开始我以为是因为是通过检测后台服务是否运行栏判断是否跳过 splash 逻辑,但是我实际实验后发现,这个方法虽然可行,但是在此打开时的页面动画就不是那么舒服了,有点生硬的感觉,而且不是所有的 App 都有后台服务的。不管简单的搜索就找到了我觉得正确的方式:

Activity 类中有一个方法:moveTaskToBack,接受一个 boolean 类型参数,传入 true 就会让 App 退出进入后台,但是不会停止该进程。这不就是我们想要的方式么?试了下果然跟网易云一毛一样,这样也是变相的避免了 App 冷启动,减少了用户的等待时间,提升了那么一点用户体验吧。

除了这个方法还有另一种办法:

1
2
3
4
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.addCategory(Intent.CATEGORY_HOME);
startActivity(intent);

效果与 moveTaskToBack 是相同的。

但是这两个方法都有一个共同的弊端,如果 App 处于后台超过一定时间没有任务执行也会被杀死的。而且这个时间非常短。我在自己手机上测试是 7 秒左右。不过这大概是因为我手机打开了某些清理工具的原因,毕竟写在 onDestroy 里的 log 都没打出来,可见是被强制杀死的。所以我又在虚拟机上测试了下,点击返回以后等了三分钟发现还是处于存活状态我就没有继续测试了,基本已经证明如果没有第三方干涉,应用应该会在后台存活较长时间,直到内存不足被系统杀死。当然作为一个有尊严的 App 应该在用户退出后开个定时器,十分钟用户还没有回来就自毁吧,毕竟占着内存不工作也不太好是吧。

不过相比于很多 App 用 再按一次返回退出 来挽留用户,我觉得这种方式可能更加舒服一点。

2. 闪屏页需求分析

首先要说的是,我这里所说的闪屏页并不包括引导页,这相对是另一个功能了。一般对闪屏而言,无外乎就这么几个功能:

  • 显示 Logo,增强品牌效应
  • 打广告,挣点零花钱
  • 内部活动推广,会带有内链

首先说显示 Logo,如果 App 的 Logo 与开始的图片上的流程是一样的,那么 Logo 是可以有两种方式来显示的。

  1. 通过 windowBackground 来设置:可以在屏幕背景里放一个非全屏显示的图片,作为打开的留白的间隙的替代,当正常内容加载完就会被覆盖掉。Logo 的显示时间取决于正常内容的加载时间,如果加载很快,Logo 就会一闪而过。但为什么是非全屏显示呢,对于 windowBackgroung,很难设置图片的拉伸模式,那么屏幕适配就非常麻烦。所以放一个非全屏图片,设置个背景色或许是个不错的选择。这种方式也是 Google Photos 在用的。至于一闪而过对于用户的体验是否良好就仁者见仁了。另外值得一提的是网易云应该也是使用这种方式,因为 Logo 的显示时间并不总是相同而且没有丝毫的留白间隙,但是它的 Logo 是全屏的,这就得佩服下网易的技术水平了(微笑脸)。

  2. 除了这种方式就是常规的用一个单独的页面来显示 Logo 了,这种没什么好说的,全屏往里面放图片就行了。

2.2 显示广告

然后说打广告,这里的广告是指接的外部广告,比如网易的闪屏经常会放某某车的广告,或者天猫什么节的,还是有很酷炫的动画和声音的那种,简单的图片动态加载比较简单,但是带有多媒体效果的广告就略复杂了,这里就只说动态下拉图片广告吧。

动态下拉图片广告需要后台 API 的配合。比如检测是不是要下载新的广告,广告的过期时间,紧急停用某个广告等。因为我不是后台,所以自己写了个简单的后台广告接口,勉强够用:

  • 请求
    • 地址:
    • 方法:GET
    • 格式:<K,V>
    • 参数:
      • Long : splashId //当前 splash id,为空则返回最新 splash 信息
  • 返回
    • 数据:闪屏信息
    • 格式:json
    • 例子:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      {
      "splash":
      {
      "id":1, //闪屏识别 id
      "imgUrl":"", //闪屏图片下载地址
      "eventUrl":"", //闪屏活动地址
      "eventTitle":"", //闪屏活动标题
      "deadDate":0, //有效期至(Long 毫秒值)
      "disable":true //是否停用,用于紧急下线闪屏
      }
      }
      • 服务器行为:
      • 根据 id 检查是否存在新的 splash 信息,存在则返回 splash 对象,不存在返回空,停用该 splash 则返回 id 相同、 disable=true 的 splash 对象

如果 App 某段时间内只维持一个 Splash,也就是显示同一个广告,那么对象信息可以存在 SP 里,如果要维持多个 Splash,那还是单独建个数据表来存储比较方便。我这个接口只适合维持一个 Splash,因此也不存在考虑显示频次的问题,如果要适配多 Splash 的需求,我想你们肯定能做到的(斜眼笑)。

对移动端来说,每次打开 App 都要请求该接口,以保证 Splash 的最新。当发送 id 与接收到的 id 不同,那么就要开启子线程与下载新的 Splash 图片了。如果相同,但 disable=true,下次冷启动就不会再显示这个 Splash 了。

2.3 显示活动推广

本来我以为只有 App 自己的活动推广才会带有内链,不过网易显然又刷新了我的认知,它竟然在网易云里面加载天猫某某节的页面!不愧是网易,果然有态度。其实看上一小节的 API 文档,就知道 Splash 里还带有两个 url 属性的,imgUrl 是广告图片的下载地址,eventUrl 是活动页面地址,至于 eventTitle 就是活动页面的标题了。

这个其实只要给 Splash 图片设置个点击事件就行了,不过有一点还是要说下:点击图片进入了活动页面,如果 Splash 页面已经 finish 掉了,那么 App 不是就直接退出了么,如果推广是给别家推广的,有种给别人做了嫁衣裳的感觉啊。因此,对于活动页面的退出,还得加一层判断逻辑。考虑到内链不仅会是 web 页面,也可能是内部的某个常规 Activity,因此需要在所有可能由 Splash 跳转而来的页面里进行判断:

首先在开启 Activity 的传入参数:

1
2
3
4
5
6
7
public static void actionStart(Context context, String url, String title, boolean isFromSplash) {
Intent intent = new Intent(context, CommonWebActivity.class);
intent.putExtra("url", url);
intent.putExtra("title", title);
intent.putExtra("isFromSplash", isFromSplash);
context.startActivity(intent);
}

然后在需要做退出判断的地方:
1
2
3
4
5
6
7
8
9
10
@Override
public void onBackPressed() {
if (webView.canGoBack()) {//优先让内置浏览器返回至上一页
webView.goBack();
} else if (isFromSplash) {//如果从闪屏跳转而来,则关闭返回至首页
MainActivity.actionStart(this);
} else {
super.onBackPressed();
}
}

如果你有抽取 BaseActivity,那么在 BaseActivty 中判断更好:
1
2
3
4
5
6
7
8
@Override
public void onBackPressed() {
if (isFromSplash) {//如果从闪屏跳转而来,则关闭返回至首页
MainActivity.actionStart(this);
} else {
super.onBackPressed();
}
}

至此,闪屏的分析就结束了,一般来说 Splash 页面就这么些功能了,这里我没有贴上完整的实现代码,因为测试 API 是用 SpringBoot 建的,而且大家的后台并不一定是自己可以控制的,因此主要还是提供个思路,具体的实现方式还是要因公司(需求)而异。

自从知道了策略模式和状态模式这两个行为类的设计模式后,就一直觉得这两个模式有点难以区分。不仅 UML 类图看起来没什么区别,就连具体实现也是非常相似。经过一段时间研究,稍微有了一点理解。

策略模式和状态模式都是消除含有大量 if…else 或 switch…case 这类硬编码结构的良策,虽然不是所有硬编码结构都可以用这两种模式来消除。根据我的理解,可以从其应用场景方面来区分两个模式。

Read more »

今天完成一个功能开发,提交代码的时候,突然提示如下错误:

1
2
3
4
5
6
7
To C:/Users/Alpha/AppData/Local/Temp/d20170730-15308-3dbr6w/.git
! [rejected] master -> master (non-fast-forward)
error: failed to push some refs to 'C:/Users/Alpha/AppData/Local/Temp/d20170730-15308-3dbr6w/.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

Read more »

这次要说的是一个很简单但是很有用的小技巧,不知道偶然看到文章的各位是不是都已经知道了。已经知道的就可以略过了。

Read more »

又是很久没有写文章了,不写文章的这段日子里,感觉生活毫无乐趣,没有什么成就感,以后还是要多写啊,至少一周一篇吧。

需求

城市选择页面是很多 App 都有的组件,比如美团、大众点评之类的,而这个文章就是模仿美团的城市选择组件打造的,不过比起美团还是有差距的。
主要的需求有以下几点:

  1. 显示当前城市;
  2. 显示设备定位城市;
  3. 按照城市拼音进行排序和分类
  4. 城市首字母快速导航
  5. 城市搜索,关键字高亮
    Read more »

ANR 与 无限轮播

以前跟着课程做过一个无限轮播的 Demo ,原理就是 Adapter 的 getCount() 返回 Integer.MaxValue 加上子线程控制页面切换。但是这几天在用这种模式的时候发现一个隐藏的坑,有很高几率触发 ANR,这可是不得了的问题(我就不说为了找到这个 ANR 原因花了多大功夫)。在这种模式下,当更新 ViewPager 的数据源的时候,基本上百分百会导致 ANR ,原因就是 PagerAdapter 的 getCount()返回一个很大的值,再调用 setCurrentItem() 更新页面,如果引起的页面跨度超过 1,例如从第 3 页跳转到第 1 页,就会导致 ANR。

Read more »