知乎 · 2016 上半年

2016 上半年 · 43 条
回答2016-06-22

作为一名程序员,最大的成就感来自哪里?

写了个 Game Engine。顺便用自己的 Game Engine 写了一系列的游戏。

是的,为了方便编写游戏脚本,我还拓展了下 notepad++,当做个简易的 IDE 来使用。

引擎里有哪些看起来比较复杂的东西?自己定义了一套脚本(叫做 fscript),写了个简单的脚本解析器。写了个用在立绘切换的 Shader。搞了搞 FreeType。当初写引擎的时候,链表和 hash 表的实现都是自己手写的,那个酸爽~

做了个弹幕游戏。

游戏视频:

UP主自制游戏_实况解说

图形渲染、粒子系统、碰撞检测都是自己写的,还写了个光效叠加的 Shader。当然最屌的还是音频信息可视化,游戏所有对象和状态都和背景音乐有互动。

想想,这都是大二大三时候的事了。如果路子不走野的话,现在大概在做游戏开发或者搞图形、可视化相关的东西吧~ 哎,可惜了 ╮(╯▽╰)╭

♥ 16💬 27
文章2016-06-10

#Kotlin# 一年の使用报告 - 类型设计

前言

大约有一年的 Kotlin 使用历史,总结下 Kotlin 带来的一些体验。这是该系列文章的第一篇。

正文

昨天,著名 PL 人王垠写了篇文章 Java 有 value type 吗? 是的十分有意思,文中假设将 Java 中的原子类型设计为 reference type,你会发现它依然和 value type 表现一致。一个很重要的原因是 Java 中并不具备 C 语言中的 deref 操作符,无法修改 reference 指向中真实的 value,你在对它进行再赋值时,仅仅是改变了它的指向。

然而实际的情况更复杂一点。拿 int 类型举例,在 Java 中除了 int 还提供对其 wrap 过的 Integer 类型。Integer 明显是引用类型但是和值类型表现一致,因为它并不提供 setValue() 这样改变内值的函数。可以看出 Java 是在刻意保持 Integer 和 int 表现的一致性(自动装箱也是一方面)。

好吧,至少在 Kotlin 中你的顾虑可以少了一点,因为在 Kotlin 中只有 Int 而没有 int。它可以像 Integer 一样提供一系列额外的操作函数(可以看下面的代码例子),然而实际上在 JVM 上却储存的是 int 类型!(int 效率会更高)也就是说 Kotlin 中 Int 类型编译成字节码后实际上是 int 类型,它利用编译器隐藏了一些实现,把 int 等原子类型看起来更像 reference type,并提供了额外的函数:

10.ushr(10)

看着是不是很屌。这样看来,Kotlin 中也就实现了语法上一切皆 reference 的设计。

顺便讲一个 trick 吧。你思考下 Kotlin 中的 Int 和 Int?(也就是 nullable 的 Int) 类型。有没有发现什么不对劲的地方。是的,如果如上文所说 Kotlin 中的 Int 类型实际上是用 value type 的 int 实现的话,怎么可能会存在 nullable 的情况(只有 reference type 才有 null 这个概念)。好吧告诉你!Kotlin 中 Int? 的实现居然变成了 Java 中的 Integer 了。。我次,是不是有种被骗的感觉。

然而仔细想想,Int? 的出现不就更符合一切皆 reference 的设计的(都能设置为 null 了,它当然是 reference type 啊 =_=)。可以看出这一系列的暗箱操作都是为了实现基本类型的归一化处理呀。

Kotlin 中使用 var 和 val 来界定 reference 的可变性,这其实是对 final 修饰符的泛化。带来的结果是,它让我更关注 reference 应该是 mutable 还是 immutable 的。我践行的一种思想是 [ 尽量保持 reference 是 immutable 的 ],一个适合的场景:

data class Test(val a: Int, val b: String)

我使用 val 来标记 Test 类中的所有成员变量。它在构造后就无法对 a、b 成员进行改写操作。

此外,Kotlin 为解决 NullPointerException 做了很多类型安全的工作,这在之前的文章中就有提及。对我的影响是有好有坏,它强制我更正确对待所有可能为空的引用,但是很多地方我为了避免充斥 check null 运算,我反而更多地使用了 lateinit 关键字来描述变量为 notnull。例如:

lateinit var textView: TextView

fun onCreate() {
    textView = find(R.id.textView)
}

然而这并不是一种好的习惯。除非你十分确保 textView = find(R.id.textView) 这句语句执行时 textView 尚未被赋值,否则将会抛出错误拒绝对 textView 进行二次赋值。特别是在 Activity 或者 Fragment 这种生命周期由系统托管的类中你尤其需要小心。例如上面的代码看上去很正确,但是假设是它在 Retained Fragment 中进行的话就会发生意外(在屏幕发生旋转后程序就会崩溃退出)。原因在于 textView 的实例并未被释放,于是在第二次调用 onCreate() 生命周期时产生了二次赋值。

延伸一下,lateinit 修饰符的原理究竟是怎样的?查看下字节码的实现的话,你会发现上面代码的等价 Java 实现为:

public TextView textView = null;

void onCreate() {
    if (textView != null) {
        // 抛出错误
    } else {
        textView = (TextView)findViewById(R.id.textView);
    }
}

好吧,知道为什么 lateinit 不能修饰 Int、Long 等基础类型了么?因为 Int、Long 在 JVM 中的实现是 int、long 啊,上面已经说了它们是不能赋值为 null 的。

那 Lazy Delegate 呢(by lazy { //… })?这和 lateinit 完全是不同的东西,Delegate 的实现显然也更复杂些。它们的区别在于 lateinit 是语法级别的,而 Lazy Delegate 不是,lateinit 是主动赋值,而 Lazy Delegate 是被动赋值(第一次被访问时才赋值)。

要注意的 Lazy Delegate 和 lateinit 一样存在我上面说的生命周期问题。Lazy Delegate 并不会报错,但是会无法更新内值。JakeWharton 的 kotterknife 就是基于 Lazy Delegate 进行 bindView 的,所以我在新版本的 kotgo 框架中已经移除了所有 kotterknife 相关的代码,它们存在一定的隐患。(后话就是我使用了 kotlin 更为强大的 synthetic 来绑定试图了~)

前面说了一堆,但是Kotlin 的类型设计中,我认为最大亮点的依然是 function type,作为 first class type,你甚至可以把函数当成值进行传递。这直接赋予了 Kotlin 函数式编程的特性,实在太 cool 了:

fun a() = 1
val b = ::a
fun c(f: ()->Int) = f()
val d = c(b)

而 lambda 函数的使用场景就更加多了。在我们的最近的产品中,因为大量地使用到了基于函数式编程思想的 ReactiveX,于是 lambda 成为了这里的救星:

GankService.api.getMeizi()
        .subscribeOn(Schedulers.io())
        .map {
            Hawk.put("meizis", it.results)
            it.results
        }
        .onErrorResumeNext {
            val meiziList: List<Meizi> = Hawk.get("meizis")
                    ?: throw GankServiceException(it.message)
            Observable.just(meiziList)
        }

看起来实在是太优美了。

噢对,Jack Tools 目前也能做到。话说 Jack Tools 的诞生应该算的上是 Android 开发工具上的一个里程碑了,它对多年被困在 Java 6/7 中的 Android 开发者来说简直是个救命稻草。但只要开发者还是在 Java 这棵树上就依然没有根本性地解放生产力,编程语言界一些更好玩、更先进的思想都不知道他们啥时候才能玩得上呀~(雾)

♥ 33💬 21
文章2016-06-08

#Android# 如何组好队伍刷怪

文章转载以及所有图片 & 文字的引用需经本人同意。

前言

假设你带领着一个刚凑够人数的 Android Dev Group 准备一起打怪升级,Group 里队友们能力不等,有 Level 30 的,也有 Level 10 的。好的,那你要开始头疼一系列问题了,包括如何激发队友们的战斗热情(内驱力) ,提供一条有效的打怪练级线路(工作流水线),提高团队的整体输出(团队效率),降低团队产生的内耗等等= =

  • 基础结构

首先,这得是一个足够扁平化的团队,所以大家之间应该是队友的关系。互相之间的沟通应当没有上下级之间的障碍。

团队应该提倡 OKR 而不是 KPI 的原则,理想情况下是大家都应该在努力做自己喜欢的事,当然,努力的方向应当和公司方向不偏差太远。这种自下而上的驱动力叫内驱力。

  • 任务分配

好吧,要搞定任务分配首先你得构造一个粒度足够小的需求池,池里面有组件开发、页面开发、Bug 修复等需求,需求来自外部(PM、设计)或内部(组件抽象、代码重构),队友自行选择自己能完成的任务。

接到比较大的需求时,应当尽可能切碎再扔进需求池。粒度大的需求第一不好预估时间,第二是不可控因素太多(,深刻感悟 π__π )。粒度一到三天松紧合适。不应当按模块切分,应当先按组件切分。

  • 二进制库管理

为何需要个内网二进制仓库?一者是起到缓存作用,加快大家访问远程仓库的速度。二者是提供一个公司内部的组件仓库。

需求池有新的页面需求的时候,第一阶段应该进行的工作为[ 面向组件开发 ],由每个队友选择开发难度能接受的组件开发需求。接受需求者新建 Project 并开始开发 Library,开发完成后上传 Jar / Aar 到内网 Maven 仓库。

有必要的话,每个组件的负责人要长期维护并升级相应的组件以适应日后更广泛的需求。组件可由个人意志决定是否开源。

贡献组件的数量、质量和开发速度都会纳入绩效评价中。

  • 源码管理

开发第二阶段为 [ 面向页面开发 ],开发者选择组件库里的组件进行开发。使用 Pull Request 的模式进行代码混合,Leader 负责进行代码审查。

代码一旦混合进 Dev Branch 就会触发持续集成系统,构建失败的话 Leader 需要指派紧急修复给相应的开发者。构建成功会自动部署到相应的下载服务器进行测试分发。

代码质量和通过率会纳入绩效评价中。

  • 代码组织

界面开发中应遵循先开发 View 和 Model 层,最后开发 Presenter 层的协定(PS:上图来自公司使用的架构 KOTGO )。应当优先确保每个 Model 是可测试的。

避免 Lava Flow (熔岩流)的产生。在不规范的开发流程中,很容易会有不断往旧的类中填充适应新需求的耦合代码这种现象,最终逐渐形成难以维护的 Lava Flow。

熔岩流常见于耦合了业务逻辑的 Activity 或 Fragment 中,所以要避免熔岩流必须有明确的分层意识。而更有保障的做法是业务逻辑(Model 层)由专门的人开发,其他人只专注于页面开发。形成 [ { 大前端 <- 小后端 } <- 大后端 ]的开发模式。

  • 内驱力

赋予你的队友自主权。选择更高难度的挑战应当得到更丰厚的回报,但也要承担当中的风险。最好量化并告知所有挑战(需求)的难度和能获得的分数,分数累积到一定程度就能影响你的绩效甚至 Level。

尾言

尾言是最重要的广告时间~ (//▽//)

Come on,想要尝试 Kotlin 的小伙伴可以试试 1.x 版本的 KOTGO 了。对 我们公司 感兴趣的可以私信我拿邮箱投递简历,喜欢文章的点个赞或者打点赏呗~(下一篇的预告是 Kotlin 投入生产几个月以来的感想~)

♥ 74💬 21
回答2016-05-30

有没有一句话让人觉得夏天很美?

阳光,有点儿喧嚣

♥ 0💬 3
文章2016-05-28

数库科技 Android 开发准则

该份开发准则来源并作用于数库科技 Android 开发组,转载请标明出处。欢迎对 使用 Kotlin 进行工作有兴趣的开发者联系我并投递简历(也招 iOS、Web 前后端、设计)~文章内容首发于我的 Git Blog (知乎的排版实在太烂了,要仔细看的话还是建议到 Github 上看 =_=),并将长期在 Github 上进行维护。

总览

设计

  • 定义好调色板,所有颜色 只能 从调色板中获取,任何地方都应该避免硬编码
  • 图标可以考虑使用流行图标字体库(Fontawesome)
  • 开发前对一遍设计稿,定好所有 Dimen,尽量使用 Dimen 板
  • Multiple State(多状态) Drawable 命名规则:android-selector-chapek
  • 设计规范越完整,开发越容易工作

开发

  • 必须写注释(行内注释,函数注释以及类注释等),Doc Gen 选用 Dodoka
  • 善用 TODO,FIXME 进行标注
  • 必须写单元测试(V/M 层都需要)
  • 使用 CI(持续集成)进行远程构建
  • 使用 Lint 工具进行代码静态检查
  • 收集各种常用 Lib (log, bugly, …)
  • 可以添加多一层 Lib 层,用来对大多数第三方库进行一层包裹(Wrap),方便日后更换或拓展
  • 所有基于事件响应的场景尽量使用 Rx 来实现,包括 View 的事件响应(可参考 ReactiveAndroid
  • 所有调试用的 Log 请用使用 Debug 作为 Flag 进行输出,Release 环境下必须使用混淆去掉所有 Log 的代码
  • 上架前必须进行 混淆 和 签名
  • 使用 Redex 等工具对 Dex 文件进行优化(也可使用 (redex-plugin)[https://github.com/timmutton/redex-plugin])
  • 使用 Nimbledroid 进行应用性能分析
  • 适当使用依赖注入(常用的模块,需要单元测试的模块)
  • 使用 Fragment 来构建页面内容,使用 Activity 来管理 Fragment
  • 尽量使用 Anko DSL 来创建视图

架构

MVP,Flux/Redux。请参考 Kotgo

层次流程

  • View -> Model -> Presenter:View 和高复用性的 Model 同时开发,Presenter 最后开发。
  • 前期 View 层开发需要用到的数据全部使用 ViewObject,需要什么属性就定义什么属性,以后在 Presenter 层进行DO 到 VO 再到 View 的 Convert(转换)过程。(VO 中可以使用 data: Any 属性携带 DO)
  • DO 到 VO 的转化过程请不要在 UI 线程进行操作。(可以在 Presenter 中使用 Rx 的 Map 操作在非主线程调度器上进行转换)
  • 所有 VO,DO 只能保存在 Presenter 层内,View 层最多只能保存 VO 的引用!(另外要注意,Adapter 应当放在 Presenter 层内)
  • View 层不能接触 Model 层的任何数据和接口!
  • 页面跳转 放到 Presenter 层中。
  • View 和 Presenter 之间是双向依赖,所以通过接口解藕,便于进行 UI Mock 测试,而 Presenter 和 Model 是单向依赖,可以直接编写单元测试来测试 Model。

Flux 的一些思想

  • FP 的思想很适合前端:Rx 在 Android 领域的火爆验证了这一点(对事件或数据的流加工)
  • Pure function:Function 不影响外部变量(不产生副作用),且给定输入,输出不变。
  • Map,Reduce:对数据的流处理,任意流都可以通过 Map&Reduce 加工成任意流。

Git 协同守则

  • 拉取 dev 分支到本地 Liveneeq 文件夹
mkdir Liveneeq
cd Liveneeq
git init
git remote add -t dev -f origin git@git.thecampus.cc:onecampus/liveneeq-android.git
git checkout -b dev origin/dev
  • 每个人建立带下划线的自己全名的分支,例如 _yangfan
git checkout -b _yangfan
  • 在该分支上进行开发,定期进行 Commit(可使用 tmp 前缀来表示临时提交),确保代码在云端
git add .
git commit -m "tmp 3/18 ***"
git push origin _yangfan

git add .
git commit -m "tmp 3/19 ***"
git push origin _yangfan
  • 完成阶段性功能或页面后,使用 rebase 或者 reset 重建 Commit 历史,确保所有 tmp commit 被合并删除
git rebase -i <COMMIT_HASH>
# ...
git rebase --continue
  • 需要提交到 dev 分支时,需要针对 dev 在个人分支上进行 Rebase 操作,并处理冲突
git fetch
git rebase origin/dev
# ...
  • rebase 完成后 在本机进行构建和测试,测试通过后使用 -f 参数强制 Push 到远程分支
git push -f origin _yangfan
  • 在 Gitlab 上提交 Merge Request(/Pull Request) 到 dev 分支,等待 Master 进行 Code Review

Notice

  • 每次 Commit 要保证粒度足够细,包含的更改和描述一致,且可编译运行
  • 提交 PR 前如果确保当前分支在 dev 分支 HEAD 处的话可以不进行 Rebase
  • dev 分支将处于 protected 状态,非不得已要执行 force push 的话,要提交通知所有开发成员

编码准则

参考并修改自 Android-Best-PracticesAndroid-Guideline

Kotlin 源代码

对类文件使用 驼峰命名法 。包名使用 小写连写 ,单词较多可以使用 _ 分割符。

Property 定义与命名规范

对 Property 的定义应该放在文件的首位,另外请注意 Kotlin 可视修饰符和 Java 的不同 ,并且遵守以下规范:

  • 要注意 Kotlin 的默认可视修饰符为 public
  • Kotlin 的 internal 修饰符,可以让目标对象只在同一 Module(IDE 下的 Module) 下可访问(例如创建一个插件 Module 的时候,可以使用 internal 对外隐藏一些实现细节)
  • 静态常量命名字母全部大写,单词之间用下划线分隔,且必须使用 const val 修饰符
  • Android SDK中诸如 SharedPreferences,Bundle 和 Intent 等,都采用 key-value 的方式进行赋值,当使用这些组件的时候,key 必须被 private const val 所修饰,并以 KEY_ 作为前缀。
  • Android 下的组件以及控件尽量以 类型 的缩写小写字母作为前缀,例如以下一些可选的前缀(可依此类推):

示例:

internal class TestActivity: Activity() {
    compainion object {
        const val CONSTANT: Int = 0
        private const val KEY_ARG_TITLE = "title"
    }
    val title: String = "Title"
    var listSize: Int? = null
    private var frgHomepage: Fragment? = null
}

Kotlin 语言相关

  • 理解好 Kotlin 中的 Function 类型 ,理解 inline 和 infix 修饰符,掌握 Kotlin 中的 ExtensionsDSL 的定义 ,领悟 Function 在 Kotlin 的地位(第一公民)。
  • 看完并理解 stdlib
  • ByteArray、ShortArray、IntArray 等并不继承于 Array,它们在 Jvm 中表现为 byte[]… ,所以应该更倾向于选择它们。
  • 使用 Any 而不是 Object。(注意 Lint 的提示,也会建议使用 Any)
  • 用好 Pair 和 Triple 来避免某些情况新建类。
  • 使用好注解:@Deprecated(标注不推荐的对象)、@ReplaceWith(标注能进行替换的代码块)。
  • 注意好 Throwable、Exception 和 Error 的区别,对于可捕捉的错误应该使用 Exception 而不是 Throwable。
  • 理解好 apply()、let()、with()、to()、repeat() 的糖用法。
  • 使用 val localA = A!! // or checkNotNull(A) 将 Nullable 变量转换为 NotNull 类型的 Local Scope 变量。

Log 输出规范

使用 Log 类打印一些重要的信息对开发者而言是很重要的事情,切记不要使用 Toast 来做信息打印。

VERBOSE 和 DEBUG 类型的 Log 不应该出现在 Release 版本中,INFORMATION、WARNING 和 ERROR 类型的 Log 可以留下来,因为这些信息的输出能够帮助我们快速地定位问题所在,当然前提是,需要隐藏重要的信息输出,如,用户手机号,邮箱等。

只在 Debug 环境中输出日志的小技巧:

if (BuildConfig.DEBUG) Log.d(TAG, "The value of x is " + x)

类成员排序规范

关于这个并没有硬性要求,不过好的排序方式,能够提高可读性和易学性。这里给出一些排序建议:

  1. 常量
  2. 字段
  3. 构造函数
  4. 被重写的函数(不区分修饰符类型)
  5. 被 private 修饰的函数
  6. 被 public 修饰的函数
  7. 被定义的内部类或者接口

资源文件(Resources)

  • 资源等 .xml 文件应该采用 小写字母_下划线 的组合形式,并遵循前缀表明类型的习惯,形如 type_name.xml。
  • res/values 目录下的文件可以任意命名,但前提是该文件能够明确表达职责所属,因为起作用的并不是文件本身,而是内部的标签属性。(例如你可以定义 strings_home.xml、colors_home.xml 之类的)

Lyout 相关

  • 布局(Layout)文件命名方式:

布局文件应该与 Android 组件的命名相匹配,以组件类型作为前缀,并且能够清晰的表达意图所在。基本规则如下:

值得一提的是,一些布局文件需要通过 Adapter 填充,如 ListView,Recyclerview 等列表视图,这种场景下,布局的命名应该以 item_ 作为前缀。另外还有一种比较常见的情况,一个布局文件作为另一个布局文件的一部分而存在,或者使用了include,merge 等标签的布局,可以使用 partial_、include_ 或者 merge_ 作为前缀,这一类布局的命名同样应该清晰的表达其意图。

  • Id 命名方式:

控件 Id 的命名应该以该控件类型的缩写作为前缀,和 代码中的控件名保持一致

对于如何排版一个布局文件,请尽量遵循以下规范:

  • 每个属性独占一行,缩进四个空格
  • android:id 作为第一个属性存在
  • 如果存在 style 属性,则紧随 id 之后
  • 如果不存在 style 属性,则 android:layout_xxx 紧随 id 之后
  • 当布局中的一个元素不再包含子元素时,另起一行,使用自闭合标签 />,方便调整和添加新的属性
  • 善用 IDE 的 Reformat Code 功能,尽量在编辑完 XML 文件后进行格式化

示例如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    >

    <TextView
        android:id="@+id/tvTitle"
        style="@style/FancyText"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true"
        tools:text="This is title."
        />

    <include layout="@layout/partial_header" />

</LinearLayout>
  • 避免层级冗余的嵌套。

Layout 结构优化方面,应尽量避免深层次的布局嵌套,这不仅会引发性能瓶颈,还会带来项目维护上的麻烦。在书写布局之前应该对 ViewTree 充分的分析,善用 标签 减少层级嵌套,或者使用 Hierarchy Viewer 等 UI 优化工具对 Layout 进行分析与优化。可参考 Optimizing Your UIOptimizing Layout Hierarchies

Style、Theme 相关

Style 与 Theme 的命名统一使用 驼峰命名法 (首字母大写)。使用多个 Style 文件而不是全部写在 styles.cml 里,如:style_home.xml,style_item_details.xml,styles_forms.xml 等。

几乎每个项目都需要适当的使用 Style 文件,因为对于一个视图来说有一个重复的外观是很常见的。在应用中对于大多数文本内容,最起码你应该有一个通用的 Style文 件,例如:

<style name="ContentText">
    <item name="android:textSize">@dimen/font_normal</item>
    <item name="android:textColor">@color/basic_black</item>
</style>

应用到 TextView 中:

<TextView
    style="@style/ContentText"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@string/price"
    />

你或许需要为按钮控件做同样的事情,不要停止在那里。将一组相关的和重复的属性放到一个通用的 Style 中。

对于控件的 android:layout_xxx 等属性应该在 Layout 中定义,同时其它属性 android:xxx 应放在 style 中。核心准则是保证 Layout 属性(position, margin, size 等)和 content 属性(text, src 等)在布局文件中,同时将所有的外观细节属性(color, padding, font)放在 Style 文件中。

使用 Designtime Attributes(tools 标签)

  • 布局预览应使用 tools:xxx 相关属性,避免 android:text 等硬编码的出现,具体可参考 Designtime Attributes 。示例如下:
<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    tools:text="Home Link" 
    />

Drawable 相关

  • 常规 Drawable(图像)文件命名方式:

  • 常规 icon(图标)文件命名方式:

  • 常规 selector states(选中状态)文件命名方式:

要注意的是,Selector 的一些状态是可以叠加的,所以可以产生 btn_order_disabled_focused.9.png 这类命名。

永远使用 android-selector-chapek 这个插件来生成相应的 Selector Drawable XML 文件,而不应该手工创建。

Color 相关

colors.xml 文件就像个 “调色板”,只映射颜色的 ARGB 值,不应该存在其他类型的数值,不要使用它为不同的按钮来定义 ARGB 值。应该像下面:

<resources>
    <!-- grayscale -->
    <color name="white"     >#FFFFFF</color>
    <color name="gray_light">#DBDBDB</color>
    <color name="gray"      >#939393</color>
    <color name="gray_dark" >#5F5F5F</color>
    <color name="black"     >#323232</color>

    <!-- basic colors -->
    <color name="green"     >#27D34D</color>
    <color name="blue"      >#2A91BD</color>
    <color name="orange"    >#FF9D2F</color>
    <color name="red"       >#FF432F</color>
</resources>

对同一色调,不同色域进行定义时,像 “brand_primary”、“brand_secondary”、 “brand_negative” 这样的命名也是不错的选择。

值得一提的是,这样规范的颜色很容易修改或重构,App 一共使用了多少种不同的颜色变会得非常清晰。

Dimen 相关

我们应该像对待 colors.xml 一样对待 dimens.xml 文件,与定义颜色调色板无异,也应该定义一个规范字体大小的 “字号板”。

一个很好的建议:

<resources>

    <!-- font sizes -->
    <dimen name="font_larger">22sp</dimen>
    <dimen name="font_large">18sp</dimen>
    <dimen name="font_normal">15sp</dimen>
    <dimen name="font_small">12sp</dimen>

    <!-- typical spacing between two views -->
    <dimen name="spacing_huge">40dp</dimen>
    <dimen name="spacing_large">24dp</dimen>
    <dimen name="spacing_normal">14dp</dimen>
    <dimen name="spacing_small">10dp</dimen>
    <dimen name="spacing_tiny">4dp</dimen>

    <!-- typical sizes of views -->
    <dimen name="button_height_tall">60dp</dimen>
    <dimen name="button_height_normal">40dp</dimen>
    <dimen name="button_height_short">32dp</dimen>

</resources>

同样的,在定义 margin 和 padding 时,可以使用 spacing_xxx 作为前缀对其命名,而不是像对待 String 字符串那样直接写值。这样写的好处是,使组织结构和修改风格甚至布局变得非常容易。

String 相关

String 命名的前缀应该能够清楚地表达它的功能职责,如,registration_email_hint,registration_name_hint。如果一个 Sting 不属于任何模块,这也就意味着它是通用的,应该遵循以下规范:

♥ 142💬 16
回答2016-05-22

Java程序员该如何假造项目经验呢?

首先,题主的出发点是完全错误的。既然题主自认为技术很扎实,那你就不应该一开始就想着如何造假,而应该考虑如何弱化简历上自己短板(项目经验),突出自己的强项,体现自身的价值。其次,没有职业道德的人无论到哪(包括知乎)都是不受欢迎的。你在这提问如何造假,只会成为嘲讽 or 被攻击对象。

自身短板的形成必然是有原因的,把所有原因都归咎到其他事物身上(老东家),这是对自己极其不负责任的表现。公司项目不怎样,但是利用自己空余时间投身到写博客或开源项目中的人也大有人在啊,别人在恶补自己短板的时候题主又在干些什么,。。

让人发现你伪造履历的话,肯定是进企业黑名单的,希望你能慎重考虑下。哪怕你真的造假去面试,但是最后可能会发现其实问题不出在这,你还是只值这个价。至少我们公司是不会请这种人,更别谈值什么价了。

♥ 4💬 0
回答2016-05-19

Python 的练手项目有哪些值得推荐?

好吧,三十多个人关注我,没几个点赞的,。

---邪恶的分隔符---

你试试关注我 :) 你会收到一条来自 Python 脚本发送的私信哦~

没用到啥难的东西,很适合练手:

GitHub - nekocode/zhihuSayHi: Say Hi to your new followers in Zhihu.

不过,。。首先你得懂点反编译:

#Decompile# 搞搞知乎 Beta 版~ - 『Android 还可以这样开发』 - 知乎专栏

♥ 76💬 36
文章2016-05-13

#Decompile# 搞搞知乎 Beta 版~

前言

之前逆向过一些应用,一些比较典型的逆向过程放在了我的 Blog 里:nekocode.cn 有兴趣的朋友自取。

这次的目标是知乎 Beta 版,大概因为是 Beta 版的原因,Anti-Decompiling 做得比较差,而且混淆后还保留了 Source file attribute 和 Line number tables,应该为了方便调试吧。

过程

手机上访问 知乎客户端 β 下载 Beta 版 Apk 到手机。执行 Adb Pull 拉取 Apk 文件到电脑上,直接 Jadx 大法就是干。

没做 Anti-Decompiling,没啥大碍,正常反编译所有类。

看到 retrofitrx 字眼了没哟?(≡ω≡.) 好吧,随便打开个混淆过的类看看?

Waaaa,源文件和源码行数信息都没还保留着没混淆掉哦 (≖ ‿ ≖)✧,有人问这个有啥用?保留这些信息在字节码中,可以在应用产生错误时在 StackTraces 中提示你发生错误的源文件以及具体行数。在混淆中对应的配置是(通常在 Release 版中去掉):

-renamesourcefileattribute SourceFile
-keepattributes SourceFile,LineNumberTable

再看看有哪些值得挖掘的信息:

好吧,Model 类都没混淆,看到 Key 注解就猜到是用 Jackson 做的 Json 解析,因为要用反射拿字段,所以没混淆。

再看看:

╮(╯▽╰)╭ Errrrr

结果

没啥好说的,狗屎运啥都没做就要脱光了,逆向的成果就不在专栏发布了,有兴趣的可以关注我的 Github:nekocode · GitHub 周末可能捣鼓些东西扔上去。碎觉 ( ̄3 ̄)

拿去吧:GitHub - nekocode/zhihuSayHi: Say Hi to your new followers in Zhihu.

目前已在我的服务器上稳定跑了 7 个小时,关注我的用户会收到来自该 Bot 发送的一条私信哦。

♥ 53💬 13
回答2016-05-10

有没有比较全的rxjava操作符学习博客或者网站?

谢邀。

王の宝库,出来吧:

讲讲第一项。别说,这可真是个宝物,知道在我的收藏夹里面怎么称呼它么?

『RxJava 操作符可视化』

什么概念?

看过官方文档里的观察流示例图片吧?好的,这就是那个的可交互版本哦!能对操作符的作用有更直接的了解。

♥ 4💬 2
回答2016-05-05

程序员有哪些平时自己开发的小工具来简便工作?

搬公司之前公司办公室在一个小区里,公司员工每天上班需要在小区后门等人开门(非业主没有门卡开门),于是我破解了小区物业 App 的接口做了个能打开后门的网页。每天后门保安看到门自动开了的表情是这样的:

后来自己下班每次要等公交,就抓了下实时公交的数据加了个查公交页面。

学壹传媒工具箱 (搬了公司,快一年没维护了)

自己用的工具的话那就更多了,我喜欢用 Py 脚本来做些繁杂的东西,例如:

还有一堆更较琐碎的就不提了,想不起来了。。

♥ 1💬 12
文章2016-05-01

#Android# Single Activity Multiple Fragments Architecture

前言

在 Android 开发中,正常且推荐的 App 页面架构应该为「包含多个 Activity,Activity 即页面」,这样做的好处在于系统内存告急时可以回收处于后台的 Activity,保证更多的资源给前台的页面和任务。但是我们知道,启动新 Activity 是在 ActivityManagerService 中运行(非 UI 主线程),它需要执行一系列耗时的操作,我们能明显地意识到「页面切换」这个动作。

而知乎使用的是另外一种解决方案「单个 / 少量 Activity,多个 Fragment,Fragment 即页面」。它消耗更少的资源,能更快地响应页面间切换和交互。但是它也有些短处,在层次深的页面进行现场保存和还原会消耗更多的资源和时间。所以它适合在页面层级结构不深的应用或场合中应用。

你可以在 Kotgo Lastest Release 中下载 Sample Apk 进行尝试:sample-release.apk (可能需要 fan墙

一些数据

对比 Kotgo 的 0.8.x 和 0.9.x 两个版本(分别使用 Activity 和 Fragment 来构建页面),我使用 Log 来纪录它们切换页面的用时:(单位:毫秒)

  • 使用 Activity 切换页面:

  • 使用 fragment 切换页面:

如何设计

要使用 Fragment 来取代 Activity 的职能还需要做一系列的工作,我们需要把一些 Activity 中常用的功能搬运到 Fragment 中,并且我们还需要另外维护一份 Fragment 页面的栈队。要知道 FragmentManager 在进行 Fragment 现场还原的时候只恢复内部的事务栈队(Transaction Stack),并不会恢复每个 Fragment 的显示状态,所以会产生 Fragment 重叠的问题。

而需要从 Activity 中「移植」到 Fragment 的一些必要功能包括:

  • onBackPressed() 的处理
  • onReslut() 的处理

而且为了模仿 startActivityForResult(),我们还需要实现 pushForReslut() 这一函数,可以将一个能产生返回值的 Fragment 加入栈顶。

上面说的这一大堆功能,都在 FragmentActivity.kt 中实现了,它在内部维护了多个列表和栈来解决一系列问题以及实现 FragmentManager 未提供的一些功能。下面来看看我们的 Fragment 应该设计成怎样。

abstract class BaseFragment: WithLifecycleFragment() {
    var requestInfo: FragmentActivity.RequestInfo? = null
    val fragAct: FragmentActivity?
        get() = activity as FragmentActivity?

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        if(savedInstanceState != null) {
            requestInfo = savedInstanceState.getParcelable("__requestInfo")
        }
    }

    override fun onSaveInstanceState(outState: Bundle?) {
        if(requestInfo != null)
            outState?.putParcelable("__requestInfo", requestInfo!!)

        super.onSaveInstanceState(outState)
    }

    open fun onBackPressed(): Boolean {
        return false
    }

    open fun onResult(requestCode: Int, resultCode: Int, data: Intent?) { }

    protected fun setResult(resultCode: Int, data: Intent? = null) {
        requestInfo?.apply {
            this.resultCode = resultCode
            this.resultData = data
        }
    }
}

然后我们在 HostActivity/FragmentActivity 中提供对栈进行操作的一些函数,用来跳转到其他 Fragment 或者 Activity 上(因篇幅问题,详细实现可查阅源码):

  • push():将 Fragment 加入栈顶并显示
  • pushForResult():将 Fragment 加入栈顶并显示,并捕获返回值
  • pop():将栈顶的 Fragment 弹出并隐藏
  • get():获取栈中指定 Tag 的 Fragment
  • getFragmentTopInStack():获取栈顶中的 Fragment
  • startActivityForResult():启动另外一个 Activity,并捕获返回值给指定 Fragment

结尾

Single Activity Multiple Fragments确实是一种不错的架构页面的新方案。它提供了一种新的思路来组织页面,所有页面的 View 保存在碎片中,然后在单个 Activity 中快速切换,思考下,其实这是一种退化(或者说底层化)的思想,用 Fragment 组织 View 分页面,然后全部 View 一碌脑全在单个 Activity 上初始化/显示/隐藏/绘制。

所以说更底层化的思想带来了更快的页面切换与交互。

♥ 47💬 25
文章2016-04-26

#Android# 来谈谈 App 的 Cool-start

前言

关于在用户冷启动 App 时更友好的交互(安装后第一次运行/进程被杀掉第一次运行),有两种比较正确的做法。一种最流行的做法是使用 Splash Screen 来过渡应用初始化的时间段。

这也是比较简单的一种做法,但是很容易让 App 造成割裂感,虽然仅仅在冷启动的时候才会显示,但是对于不常驻留后台的应用,每次冷启动时,真的挺令人反感的。讲真,Splash Screen 是最令我反感的交互设计之一。

所以我推荐直接进入 App 主页面,而不是使用 Splash Screen ,但是如何处理好冷启动 UI 阻塞时的交互呢?

Google 官方推荐借助 Starting Window(默认开启)。默认配置下系统通常在点击 App Launcher Icon 后显示一个 Starting Window(Preview Window)来在应用初始化期间展示给用户(当作点击反馈)。

但是应该注意的是,默认 Starting Window 的背景是纯白色的,我们需要对 windowBackground 属性进行修改才能实现上面的效果。

正言

动画效果:

我建议你预览上面的动画效果。我最早在 MaterialColdStart 看到利用 9Patch 来设置背景,但是它有些繁杂(需要手工生成 9Patch 图)。我在 Kotgo 的 Dev 分支中使用 Layer-list 实现了效果,它是这样的:

<!-- drawable/bg_startingwindow.xml -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android"
            android:opacity="opaque" >

    <item>
        <shape android:shape="rectangle">
            <solid android:color="@color/color_primary"/>
        </shape>
    </item>

    <item android:gravity="bottom" android:top="75dp">
        <shape android:shape="rectangle">
            <solid android:color="@color/white"/>
        </shape>
    </item>

</layer-list>

然后我们可以新建一个 Style 来对 Launcher Activity 生效了!!

<style name="Launcher" parent="AppTheme">
    <item name="android:windowBackground">@drawable/bg_startingwindow</item>
</style>

尾言

很庆幸我在一些应用上看到了类似的做法(例如 新版知乎,v2ex+),也很庆幸有人和我一样不喜欢 Splash Screen,希望这篇文章能帮助到和我有一样想法的人。至于示例代码,可以在 Kotgo 的 Dev 分支上拿到: GitHub - nekocode/kotgo at dev

顺便透漏下,下一篇文章我想讲的是如何使用 Single Activity,Multiple Fragments 架构来构建一个页面快速响应的应用!知乎也是这种架构的拥护者,想知道它有哪些优劣的话,请听下回分解。想要预览,也可以点击上面 Kotgo 的 Dev 分支查阅代码,新的版本也将基于这个架构!

♥ 95💬 10
回答2016-04-21

怎么知道邻居用我家的wifi登录了哪些网站?

在安全需求较高的场景下怕受到中间人攻击的话,可以使用安全的 VPN 连接到网络上。

另外,排名第一的通过 ARP 欺骗来实现中间人攻击的套路目测成功率比较低,大多数设备上可能都会安装了防火墙软件。

更直接的中间人攻击(通过物理中间件)会成功率指数上升。

♥ 1💬 3
回答2016-04-17

作为一个程序员,你遇到过哪些来自周围人的奇葩请求?

。。

♥ 0💬 7
文章2016-04-12

#Kotlin# Murmur 开源啦~

Murmur 是一个带白噪声效果的豆瓣电台第三方客户端。鉴于之前回答中有人对该项目感兴趣,现已整合重构并开源,欢迎 Star 哦 (。・`ω´・)

Murmur 采用 Kotlin / MVP / ReactiveX 进行构建,它是 Kotgo 的一个实现案例,详细地描述了如何使用 Kotlin 来构建一个健全的 MVP 项目。如果你对使用 Kotlin 进行 Android 开发十分感兴趣,强烈推荐你对本项目进行研究。

如果你对 MVP 模式十分感兴趣,也请关注该项目,它比大多数你能看到的 MVP 架构的开源应用要正确得多,它的实现更为清晰且思路正确。它解决了一系列能考虑到的问题(生命周期/屏幕旋转),它是更能经得起考验的。

Screenshots

程式中的 OpenGL Shader 特效本人修改自 Shadertoy

程式主体

你也可以在 这里 下载到它进行试用。

操作说明

  • 您需要使用豆瓣帐号进行登录
  • 请使用左右滑手势进行歌曲切换

免责声明

该项目仅限用于学术研究,不得用于商业用途。项目中的 Launcher Icon 来自设计师 @汤未冷 ,特此声明。

♥ 47💬 15
回答2016-04-10

有data binding之后,早先的Android应用架构还有用处吗?

从 Google 官方在 IO 大会上开源的 APP:

GitHub - google/iosched: The Google I/O 2015 Android App

以及一群 Google 开发者维护的这个关于 Android 开发架构的 Collection:

GitHub - googlesamples/android-architecture: A collection of samples to discuss and showcase different architectural tools and patterns for Android apps.

可以看出来,目前更流行的架构是 MVP,iosched 中更用 Fragment 来构建 Presenter,有很多值得学习的地方。

至于 MVVM,应该会有局限性的一些问题,Flux 的话思想很好,但是我没看到比较好的具体实现,Github 上的一些 Sample 都太简单了。

在 Web 前端世界里 Redux 很火(Flux 的一种实现),Reducer 的概念设计得很好,给定 Action 和旧的 State 总能得到唯一的新的 State,它不改变外部变量(无副作用),是 Pure Function。这意味着我们只要给 View 设定好初始的状态(State),然后将用户的操作(Action)一同扔给 Reducer 总能返回新的唯一的 View State。

不过 MVVM 和 Flux 我都没尝试过,也不知道具体实现时会遇到哪些的细节问题,但如果对 MVP 架构有兴趣的话,可以关注我的专栏

http://zhuanlan.zhihu.com/kotandroid

来一起探讨一下。

♥ 0💬 0
回答2016-04-10

为什么有人GitHub排行很低,不仅不向排行高的学习,反而去黑排行高的?

把她弄成现在这样不太好吧:

AutumnsWindsGoodBye (Crying) · GitHub

虽然她技术不怎么样,但是之前她还打算建一个 Organization 来做一些国外前端文章翻译的工作,当时我也在她们 Group 里。

少点网络暴力,多点宽容吧。

===

问题的 “为什么有人GitHub排行很低,不仅不向排行高的学习,反而去黑排行高的?” 会不会给人一种 排名高就代表技术好的钦定的感觉?

说实话,

@秋风

的技术真不怎么样,用机器人 follow 别人来刷粉的行为也有不妥。不过想想,刷粉的人大有人在,大多数当初反 follow 她的人更可能是因为她是个妹子而且头像美,和她技术没多大关系(逃。。

其实她只是个很会运营(Github,知乎,甚至 QQ 群)的懂点前端的长得还可以的妹子而已。不过想想,你们 follow 她的时候还不都是因为她的(给自己挂的)程序媛标签,现在怎么反过来黑别人的技术了。

♥ 3💬 7
回答2016-04-09

哪一些大公司在使用 kotlin 开发应用?

大公司应该还没有放到生产环境中使用,大都在实验性试探阶段。创业团队的话(例如我们公司)已经在用 90% Kotlin 在开发项目了。

实际体验就是:优雅而舒适

更新: 知乎已经准备开始用了

♥ 3💬 7
回答2016-04-08

如何评价wangyin的《回应CFA传人对我的攻击》?

不管 wangyin 这个人讨不讨喜,但是看他打别人脸还是很精彩。。

说实话,年轻人对前辈还是尊重点好些。不要太 naive。

♥ 1💬 3
回答2016-04-08

如何看待「谷歌酝酿将苹果 Swift 作为安卓 APP 主要开发语言」?

Kotlin 才刚开始发力你和我说你选的是 Swift?!!

两者间个人更支持 Kotlin,感觉新闻的可靠性也值得怀疑。Swift 无法像 Kotlin 一样与原有的 Java 生态圈交融,而单纯从语法上来讲(Kotlin 和 Swift 的语法有很多相似点),Kotlin 已经足够地解放了开发者的生产力,没必要抛弃 Java/JVM 生态圈(各种 Android 类库)投奔 Swift。

再者,将 Swift 作为 First class langue 的工作量应该挺大的,大概也要花上很长一段时间了。至于其他答主说的 Swift compile to Java Bytecode 是不大可能的。一方面是 Swift 语法上并没有针对 JVM(/Dalvik/ART) 进行适配,可能会产生一些坑(例如泛型),再者就是如果真打算这样做的话,Google 应该是脑进水才不直接选择 Kotlin。。。

♥ 2💬 0
回答2016-04-08

怎么重构Android程序中一个大的类?

告诉你一个重构的神器:Fragment(碎片)

它能在重构中能做些什么?

  • 细化视图/布局
  • 细化逻辑(Activity 中的逻辑)
  • 无限细化

无限细化是什么意思?就是你任何视图或者逻辑都可以用 Fragment 一直细化到你喜欢的粒度为止。一个 Fragment 内可以只有一个 View,也可以只用来实现一个后台下载任务。你应该懂为什么 Google 叫它为碎片了吧?

更详细的一些解读:

使用 Fragment 构建 Presenter

♥ 0💬 4
文章2016-04-07

#Android# 使用 Fragment 构建 Presenter

Fragment

早些时候,依赖 Activity 生命周期的操作以及业务逻辑都集中在 Activity 中,Activity 变成了单个页面的上帝类,大多数的代码都写在 Activity 中。而 Fragment 的出现分担 Activity 的重任,它和 Activity 有着同步的生命周期,它可以装载一系列 View 并管理这些 View 的生命周期,而一个 Activity 中可以包含多个 Fragment,这就意味着可以将不同功能布局的 View 用 Fragment 分开管理出来,这大大地减轻了 Activity 的负担,还从另一方面提供了适配屏幕大小以及横竖屏的方法。

但拥有与 Activity 同步生命周期的 Fragment 不单单可以用来装载 View,你也能将原先 Activity 中依赖生命周期的逻辑代码迁移到 Fragment 下,它比起 Activity 更加小巧灵活,而且被设计为完全可以不包含任何 View。

补充

我在知乎上一个回答上提到使用 Fragment(碎片)来进行重构,所以补充一下:
它能在重构中能做些什么?

  • 细化视图/布局
  • 细化逻辑(Activity 中的逻辑)
  • 无限细化

无限细化是什么意思?就是你任何视图或者逻辑都可以用 Fragment 一直细化到你喜欢的粒度为止。一个 Fragment 内可以只有一个 View,也可以只用来实现一个后台下载任务。你应该懂为什么 Google cheng它为碎片了吧?

MVP

假设进行如下尝试:用没有 UI 的 Fragment 来构建 MVP 架构中的 Presenter,用来存放程序中的控制逻辑(调用业务逻辑代码,DO 到 VO 的转换等操作),它能带来以下一些明显好处:

  • 能够同步 Activity 的生命周期,在生命周期内进行逻辑操作
  • 能够很好地处理系统重建视图时(例如屏幕旋转)的现场恢复问题
  • 它也能够在系统重建视图时不销毁实例,保留子属性,并且不打断正在进行的任务。(setRetainInstance(true))

这个 Presenter 看起来应该是这样的:

class MeiziPresenter(): BasePresenter<Contract.View>(), Contract.Presenter {
    override fun onViewCreated(view: Contract.View?, savedInstanceState: Bundle?) {
        view?.showToast("View created.")
    }
}

无疑这是一种新的尝试,我有看过类似的观点,但是并没有具体的实现。于是我在 Kotgo 的新版本中将原有 Presenter 改为用 Fragment 实现。它一下子解决了我很多的问题。例如:

  • 如何实现在屏幕旋转时依旧保持 Presenter 的实例
  • 不保存 Presenter 实例的话,如何便捷地保存某些属性
  • 如何在 Presenter 中自动同步 View 的生命周期(例如我要在 onCreate() 时做一些操作)

Fragment 的一些知识

  • 对 NestedFragment 的 findFragmentByTag() 必需在 ParentFragment 的 onViewCreated()(视图创建后)中进行,否则将返回 null 值。

关于如何恢复视图以及数据现场网络上的文章很多有坑,下面是引用自我的 Git Blog ,比较靠谱的一些笔记:

  • 自定义 View 时,请使用 onSaveInstanceState() 和 onRestoreInstanceState() 处理视图状态的储存和恢复,以应付屏幕旋转等状况后视图的现场还原。
  • Fragment 在发生屏幕旋转等状况后,系统会持久化它的一些视图以及数据状态。旋转后 FragmentManager 会反系列化旋转前持久化的信息,新建实例,并在新实例的 onCreate() 中返回之前储存的各种 State(Fragment.onSaveInstanceState() 中插入的)。而 View State 会自动传递到各个 View 的View.onRestoreInstanceState() 函数中。
  • 如果在 Fragment 中使用了 setRetainInstance(true),则 Fragment 的实例会被保留下来,不重新创建,这意味着实例内的所有属性也会被保存下来(不会被重置),但是依然会重新触发 Fragment 的生命周期事件。所以通常这种状况仅适用于进行持续性后台任务的 Fragment(例如没有视图的单纯进行下载操作的 Fragment),在屏幕旋转后也不会打断正在进行的任务。要注意的是,这种情况下如果有视图的话,视图会被重新创建,处理不好可能产生泄露。

后话

最近工作比较忙,没时间更新专栏。但是在空余时间我有在为 Kotgo 的下一版本进行准备我们团队已经在基于 Kotgo 0.7 的架构进行产品开发将近一个月,大概在下个月也将会迁移到 Kotgo 最新版本上。事实证明,使用 Kotlin & MVP 这个组合在生产环境上十分愉快,期待我们团队未来能有更多的探索以及发现。

♥ 50💬 13
回答2016-03-10

Android Studio2.1支持Java 8语法后,还有学习Kotlin的必要吗?

这是在黑 Kotlin 么。。

Kotlin 过去可不是为了成为 Java8 的取代物而生的(更不是为了 Android 而生,而且要知道 Kotlin 比 Java8 还早出世),它和 Scala,Groovy 等 JVM 语言一样都不是基于 Java 本身语法的拓展,所以它是一门新的语言。它包含了 FP 的思想,支持创建 DSL,有着类 Swift 的安全类型,还有 Extension 等语法糖,更重要的是不用写分号了_(°ω°」 ∠),这是有历史包袱的 Java,Java8 达不到的。

当然,Java8 肯定也会解救更大一票的 Android 开发者,至少不用再为没法使用 Lambda 而伤心了(手码字,逃

♥ 6💬 6
回答2016-03-06

文本编辑器中,你正在用谁?你最喜欢谁?最看好谁?原因?

Vim 大法好:

喜欢的话是因为它被作为经典内建在系统中,任何 Unix Like 系统都可以快速打开上手开干。当然更要命的是功能实在强大,作为文本编辑器完全是战斗力爆表。

♥ 0💬 4
回答2016-03-06

想写个app,在哪里可以找到icon素材?

个人算半个美工,倾囊而出过往收藏的一些资源 and 工具:

  1. Convert Icon Fonts To PNG
  2. GitHub - JoanZapata/android-iconify
  3. Font Awesome Cheatsheet
  4. Entypo
  5. Typicons
  6. Simple Line Icons
  7. Ionicons: The premium icon font for Ionic Framework

都是些矢量图标,可以通过 #1 转换成 PNG 图片,如果是 Android 开发的话,建议使用 #2 进行便捷开发。以上。

♥ 15💬 1
文章2016-03-02

#Kotlin# 一行命令让你开始进行尝试

再不尝试你就 OUT 了

Kotlin 是匹脱缰的黑马,现在已经攀升到 1.0 正式版了!官方统计数据表明,已经有越来越多使用 Kotlin 进行开发的项目,而最流行的一些 Android 项目也都开始尝试使用 Kotlin 来进行构建了。

一句命令开始进行尝试

为了让你尽快体验一番 Kotlin 的魅力,Kotgo 诞生了!现在可以通过在终端输入下面一行命令来为你生成使用 MVP 架构的,包含各种流行库的 Kotlin 模板项目,你可以直接使用 Android Studio 打开它:

python -c "$(curl -fsSL https://raw.githubusercontent.com/nekocode/kotgo/master/project_creator.py)"

欢迎开发者们对 Kotlin 进行尝试!也欢迎朋友们 PR 以及 Star ,这对 Kotlin 的推广十分给力。

♥ 22💬 8
文章2016-02-15

#Kotlin# 神他妈 1.0-RC 版本发布了!

前言

由于一些原因,之前并不知道 Kotlin 发布了新版本,在上班第一天才得知这个振奋人心的消息。Kotlin 的这次更新有着重要意义,它意味着离官方发布 1.0 最终释放版已经很近了!

Kotlin 1.0 Release Candidate is Out! 中可以查看英文原文,伴随着这次更新,Anko,KotterKnife 等 Lib 也进行了迭代。当然,我对 base_framework 也进行了更新(是个兴奋的过程)。

这次更新,语法上并没有太大的改动,Stdlib 有略微的修改,但最主要的工作还是在 Bug Fixing 以及 IDE Plugin 的优化上,下面我将抽重点说下。

语法

语法变得更为干净:

  • 之前不赞成的语法结构现在都直接提示 Error,而非 Warning
  • 所有之前会在 Byte Code 中生成的,不赞成的声明方式都被删除掉了,例如在 Interface 中定义静态变量(这是编译阶段的,我们无需理会)

新版本还能够使用 @delegate: 对 Delegate Fields 进行注解。例如,下面这段代码将 foo 注解为 Transient:

class Example {
    @delegate:Transient
    val foo by Lazy { ... }
}

此外,该次更新还支持在编译期对 使用点变型(Use-site Variance)进行类型检测,所谓使用点变型,就是下面代码中的 MutableList。它在 list 被使用时(赋值)将 list 变型为 MutableList

val list: MutableList<out Any> = mutableListOf(1, 2, 3)

对 Use-site Variances 进行类型检测,导致在编译下面代码的时候会报错:

val ints = mutableListOf(1, 2, 3)
val strs = mutableListOf("abc", "def")
val comps: MutableList<out Comparable<*>> = ints
comps.addAll(strs) // ?! Adding strings to a list of ints

编译器会拒绝编译最后一行,并提示:

Projected type MutableList<out Comparable<*» restricts the use of addAll()

要注意的是,就算 strs 和 ints 同样是 MutableList 也会报错,编译器只要发现了某字段被定义为 Use-site Variance,则会粗暴地禁止对该变量/常量进行某些 Collection 操作。

与 Java 的协同性

  • 更好地使用成员变量代替将 Java 类中的 get/set,将 get/set 直接去掉
  • 支持 Java 中会返回值的 setter
  • 添加了 @Nullable/@NotNull 注解的支持

标准库

  • 删除掉之前所有不推荐的方法
  • Map.getOrElse() 和 Map.getOrPut() 方法在找不到 key 的时候返回 null
  • 添加了 mutableListOf, mutableSetOf,,mutableMapOf 方法来构造 MutableList
  • 使用 toMutableList 取代 toArrayList
  • 添加了 associate,associateBy 方法来构造 Map

其他

另外在 Android Extensions 和 IDE 上都有些改变,详情可查看原文。使用 IntelliJ IDEA 而非 Android Studio 的用户,需要手工下载 Plugin 并安装才能成功升级,下载地址也请看原文。

结尾

Kotlin 让人越来越看到希望,在不久的将来,Kotlin 将重拳出击!o(*≧▽≦)ツ

♥ 20💬 7
回答2016-02-03

如何在GitHub上发现好的项目?

Explore Github

偶尔看看 Trending 还是不错的。

Follow 一些知名的社区贡献者:

上面这几个都是还在 Android 开发社区前线的不错的开发者。时常能在 Timeline 中提供一些不错的 Repo。或者去查阅他们的 Star 列表,也能捡到一大堆不错的 Repo。

至于『关注度不高却又不错的项目』,这个确实有些点难捡到。只能通过 Keyword Search 去一个个挑了,所以说一个 Repo 的 Description 十分重要(Description 在关键字检索内)。我的仓库列表内就有几个我认为不错的项目,不过常年没被人检索到也然并卵。

♥ 2💬 2
回答2016-01-31

脸萌faceu的动态捕捉功能,可以实时自动识别人脸并动态叠加效果代码怎么实现?

谢邀。

关键词:人脸特征点定位与跟踪

市面上已经有很多成熟的方案了:

人脸关键点 | Face++ 最好的免费人脸识别云服务

♥ 5💬 2
文章2016-01-20

#Kotlin# 小心 Rx 的生命周期

前言

最近在使用 RxJava 的时候,遇到了一些问题。我在某个页面订阅了 Retrofit 一次异步网络请求返回的 Observable,在 Retrofit 尚未发射结果之前退出该页面的话,程式会中止。

发生中止的原因是因为 Observable 的生命周期凌驾于 Activity(/Fragment) 之上,当页面销毁后,并没有中止 Observable 流,所以在 Retrofit 中的 Observable 依然保留了 Activity 的 Subscriber 的强引用,最终导致 Activity 无法被释放,产生内存泄漏。这只是问题之一,当 Observable 开始发射数据时,如果 Subscriber 中要进行 UI 处理,会直接抛出异常并退出程式,因为 UI 对象已经被销毁了。

RxLifecyle

trello/RxLifecycle 是一个不错的解决方案,它监听了 Activity/Fragment 的销毁事件,利用 compose() 方法在流中加入自己的拦截器,在 subscribe() 操作之前拦截 Observable 并检测 UI 是否销毁,如果 UI 已经销毁的话则中断 Observable,并取消订阅。

局限

RxLifecycle 不太适用于 MVP 框架,它提供的一些组件更适合直接在 Activity/Fragment 中处理 Observable。但是在 MVP 框架中,我们更应该于在 Presenter 层处理 Observable,而 View 层应该只负责提供视图接口。所以我们的 Observable 应该和 Presenter 的生命周期挂钩,而不应该和 View 层产生耦合。

解决方法

于是,我在新版本 Presenter.kt 中实现了类似的功能。

open class Presenter {
    public enum class Event {
        // Activity Events
        CREATE,
        START,
        RESUME,
        PAUSE,
        STOP,
        DESTROY,

        // Fragment Events
        ATTACH,
        CREATE_VIEW,
        DESTROY_VIEW,
        DETACH
    }

    public val eventBehavior: BehaviorSubject<Event> = BehaviorSubject.create()

    final fun destory() {
        eventBehavior.onNext(Event.DESTROY)
    }

    // ...
}

public class NormalCheckLifeCycleTransformer<T>(val eventBehavior: BehaviorSubject<Presenter.Event>):
        Observable.Transformer<T, T> {
    override fun call(observable: Observable<T>): Observable<T> {
        return observable.takeUntil(
                eventBehavior.skipWhile {
                    it != Presenter.Event.DESTROY && it != Presenter.Event.DETACH
                }
        )
    }
}

public fun <T> rx.Observable<T>.on(presenter: Presenter):
        Observable<T> {
    return observeOn(rx.android.schedulers.AndroidSchedulers.mainThread())
            .compose(NormalCheckLifeCycleTransformer<T>(presenter.eventBehavior))
}

我自定义了一个转换器,它对源 Observable 进行布尔操作 takeUntil(),当我们用于记录 Activity 和 Fragment 生命周期事件的 eventBehavior 发射出一个 DESTROY 或者 DETACH 事件的时候则会中断 Observable。

要注意的是 takeUntil() 不是等 Observable 发射出新的数据时才进行布尔判断,它是在 takeUntil(O) 的 O 发射出任意数据时就进行中断源 Observable。这很重要,这保障了我们 UI 进行销毁的当时就能中断订阅,而不是等到 Observable 发射数据时再取消。

此外,我还实现了 Observable.on() 的拓展语法糖,这样我们就能在 WeatherPresenter.kt 中使用 on() 语法糖将其绑定到我们的 Presenter 的生命周期内:

WeatherModel.getWeather("101010100").on(this).subscribe {
    impl.setWeatherInfo(it)
}

题外话

  • Kotlin 的 Lambda 写起来真的是非常美妙,但是不要忘记了,JVM 上 Lambda 的实现和匿名(内部)类基本一致,当在 Lambda 中需要捕获外部类的 this 指针时,一样可能会引起内存泄漏,所以需要多加小心。具体例子可以查看该次 Commit 的更改:avoid rundealyed’s memory leak · GitHub
  • 我在实现该版本 Lifecycler Checking 之前,曾经试过另一版本的实现。我使用了 CompositeSubscription 在 UI 销毁时,对所有 Observable 进行统一取消订阅。这样做后确实没有产生任何错误,但是当我使用 Leakcanary 进行测试时,显示程式产生了内存泄漏。我很怀疑 Observable 并没有清除 Subscriber 的引用,当然这个有待验证。
♥ 28💬 3
回答2016-01-15

C#做游戏可行吗?效率高吗?

我很早时也想过用 VB6 写游戏(也确实有人这样做,3D GAME),后来还是滚去学 C++ 了(逃

♥ 1💬 0
回答2016-01-14

想写个 App 练手,有什么有趣的 API 接口推荐吗?

因为国内并没有什么有趣且透明免费的接口,所以只能祭出 Fiddler +dex2jar + jd-gui 大法。别说 Web Service API,连 so 库接口我都逆向过。

可以参考下:

[微票儿 APP 接口逆向](http://nekocode.github.io/Weipiao Apk Decompile/)[老司机 APP 逆向](http://nekocode.github.io/Sextube Decompile/)

所有逆向出来的接口,原则上只可用于学术研究,不可用于任何其他用途。

答主可以尝试下找几个简单有趣的内容类 APP 尝试逆向接口,并 build 个第三方客户端。

例如逆向「知乎日报」的 API:

https://github.com/izzyleung/ZhihuDailyPurify/wiki/%E7%9F%A5%E4%B9%8E%E6%97%A5%E6%8A%A5-API-%E5%88%86%E6%9E%90 m

当然,更有趣的是自己写个 backend:

http://zhuanlan.zhihu.com/kotandroid/20488077

♥ 119💬 51
文章2016-01-14

#Android# Everything is a stream

近两天开始系统地接触 RxJava。在没接触它之前,所有数据是以「多个函数顺序处理」的形式来进行加工,而且一旦某些中间步骤需要进行异步处理,代码将变成各种 Callback Hell。

RxJava 可以消灭各种 Callback Hell,它提供了异步处理数据的绝佳方式,但是它的亮点绝对不仅仅在于异步处理上,它最大的亮点在于 操作符,它提供了对数据流进行各种抽象加工的操作符。

想要在程式中很好的贯彻 RxJava,你就需要明白「一切皆流」的道理,我在 baseframework 里创建了一个简单的例子,使用 Retrofit 进行网络接口调用,并通过 RxJava 对数据流进行处理:

object WeatherModule {
    data class WeatherWrapper(@SerializedName("weatherinfo") val weather: Weather)

    fun getWeather(cityId: String): Observable<Weather> =
            Net.api.getWeather(cityId).subscribeOn(Schedulers.io())
            .map { it.weather }
            .doOnEach {
                if(it.kind == Notification.Kind.OnNext) {
                    // Cache weather to local cache
                    Local["weather"] = it.value
                }
            }
            .onErrorResumeNext {
                // Fetech weather from local cache
                val weather: Weather? = Local["weather"]
                Observable.just(weather)
            }
}

我使用 RxJava 对接口返回的数据进行了一些处理:

  • 将流中接口返回的 weatherWrapper 对象进行拆包,转换成 weather 对象
  • 在每次接口请求时,将数据缓存到本地
  • 在网络请求失败时,从本地缓存读取数据加入流

可以看出,这种链式调用配合 Kotlin 的 lambda 表达,真的是无比优雅惬意。

抽空更新

近几天的学习积累了一些琐碎的东西,与抽空更了 kotlin_android_base_framework ,我对 Kotlin 以及一些库进行了更新,并对 Model Layer 进行了一些修改,它看起来变成了这样:

流起流终

在 baseframework 里面,RxJava Stream 几乎是不同层(例如 Module Layer 和 Presenter Layer)间最好的交互方式,它能够对数据进行加工、变换,甚至传递错误。例如在 Module Layer 中,我们在 RxJava Stream 中进行各种业务逻辑、数据交并处理,最后只需变换成 DTO Stream 传回 Presenter Layer 即可。

RxJava 是我认为除去 Kotlin 外对开发最能提高效率的工具,建议读者们都能学习一番。

  1. 给 Android 开发者的 RxJava 详解
  2. ReactiveX文档中文翻译
♥ 21💬 11
文章2016-01-11

How to use Python to build a RESTful Web Service

由于知乎目前限制单人仅能开通单个专栏,所以关于文章主题的所有文字都会写在该单篇文章中(避免污染专栏),目前处于长篇连载且停滞状态,待续。。

Github Repo: nekocode/tornaREST · GitHub

Preface

我是一名 Android 开发工程师,我在用 Kotlin 和 Java 写着 Android 应用,可是我也很喜欢 Python,我用它来写一些网页应用、工具。这次,我打算使用 Python 实现一个提供 RESTful Service 的后端,它能为各种前端提供 API。

Python 是一门很强大、很容易入门的语言,但是它不是一门简单的语言,它在语法上有很多可以玩的 trick(虽然很多情况下你并不需要掌握这些 trick)。它很强大,你能通过它用更少的时间来做更多的事情,你能花更多时间关注更高层的东西(业务逻辑)。『人生苦短,我用 Python』,当然我也相信,大多数对某种语言有信仰的人,都是用着该门语言在做合适的事情。Java、Ruby、C/C++ 也是如此。

Framework

Tornado + MongoDB + Redis

Tornado

它实现了一个高效的非阻塞异步 IO 的网络模型,另外一些明星级别的工业产品(Fackbook & 知乎)也选择了它。它不像 Django 一样庞大,但是它提供了构建一个基础 Web Server 所需的大多数工具,并且得力于异步 IO 的支持,它很适合作为一个 RESTful API Server 的中间件。

MongoDB

它是新兴 NoSQL 领域的一批黑马,我对于 SQL 数据库并没有太多的经验,所以我更愿意尝试新领域的产品。我认为 MongoDB 比起 MySQL 有更吸引我的点:

  • 更高的性能。
  • 宽松的结构,更容易拓展。

NoSQL 是一场技术革命运动,已经有很多 Startup 选择使用 MongoDB。Why not have a try?

Redis

它是一个高性能的 Key-Value 类型内存数据库(也支持对数据进行持久化)。它很适合对一些使用量高,实时性要求高数据进行缓存。我将用它来储存 Token,消息队列以及 Feeds。

API Doc

一份详细的 API 文档是必须的。我使用 apidoc 来为代码自动生成 API 文档,另外我自己维护了一个分支 nekocode/apidoc ,它修复了一些问题(issue #394 ),以及把接口测试器改为使用 urlencode 进行 POST 和 PUT(默认是使用 JSON)。

详细的效果可以查看 [我跟进的一个社交后台的 Doc](http://nekocode.github.io/apidoc sample/)。你也可以使用 Slate 来生成文档,它更加漂亮,但是不像 apidoc 一样提供接口测试器。

Collections

MongoDB 使用 Collection 来储存同一类别的数据,类似于 SQL 中的 Table。现在,我们首先为我们的 Backend Service 添加一系列最基础的用户操作接口。

我使用了 MotorEngine 来作为 MongoDB 的 ORM 框架,他是针对 在 Tornado 使用 MongoEngine 的一个 Port,使其能够异步访问 MongoDB。我首先通过它来定义一些需要的数据库对象:

class BaseDocument(Document):
    def to_dict(self):
        data = super(Document, self).to_son()
        data['id'] = self._id
        return data

class User(BaseDocument):
    mobile = StringField(required=True)
    password = StringField(required=True)
    followers = ListField(ReferenceField(reference_document_type='collections.User'), default=[])
    create_time = DateTimeField(required=True, auto_now_on_insert=True, auto_now_on_update=False)

    # ...

    def to_dict(self):
        data = super(User, self).to_dict()
        del data['password']
        return data

注意,我们首先定义了一个 BaseDocument,并添加了一个 to_dict 方法,它将 Document 对象转换为一个 dict,并将隐藏的 ObjectId 添加入字典,方便向客户端返回 JSNO 对象时的序列化。

这里的获取到的 dict 不能直接用 json 的 dumps 进行序列化,可以借助 bson.json_util 进行序列化,但是它把一些 BSON Object 包裹起来序列化,这样输出的数据很丑,我们还是模仿着 bson.json_util 自己实现一个 json_util 来进行序列化吧(因为篇幅我隐藏了一些代码):

def _json_convert(obj):
    if hasattr(obj, 'iteritems') or hasattr(obj, 'items'):  # PY3 support
        return SON(((k, _json_convert(v)) for k, v in obj.iteritems()))
    elif hasattr(obj, '__iter__') and not isinstance(obj, string_types):
        return list((_json_convert(v) for v in obj))
    try:
        return default(obj)
    except TypeError:
        return obj


def default(obj):
    if isinstance(obj, ObjectId):
        return str(obj)
    if isinstance(obj, datetime.datetime):
        if obj.utcoffset() is not None:
            obj = obj - obj.utcoffset()
        millis = int(calendar.timegm(obj.timetuple()) * 1000 +
                     obj.microsecond / 1000)
        return millis
    if bson.has_uuid() and isinstance(obj, bson.uuid.UUID):
        return obj.hex
    raise TypeError("%r is not JSON serializable" % obj)


def dumps(obj, *args, **kwargs):
    return json.dumps(_json_convert(obj), *args, **kwargs)

主要对 ObjectId/ datatime/ UUID 对象进行处理。

Router & BaseHandler

我们需要一个 BaseHandler 来封装一些基础的操作(例如格式化输出)。为了保持输出的一致性,我们还需要捕获一些异常,我们需要在路由设置上使用一些小技巧:

url = [
    (r'/api/user/login', LoginHandler),
    # ...
    (r'.*', APINotFoundHandler),
]

我们需要定义一个 APINotFoundHandler 用来捕获一些未定义路径的请求,并通过 JSON 以及 Http Status Code 返回 404 错误信息给用户。

而 BaseHandler 的部分代码应该是这样的:

class BaseHandler(RequestHandler):

    def __init__(self, application, request, **kwargs):
        RequestHandler.__init__(self, application, request, **kwargs)
        # TODO: REMOVE?
        self.access_control_allow()

    def access_control_allow(self):
        self.set_header('Content-Type', 'text/json')
        # 允许 JS 跨域调用
        self.set_header("Access-Control-Allow-Methods", "GET,PUT,POST,DELETE,OPTIONS")
        self.set_header("Access-Control-Allow-Headers", "Content-Type, Depth, User-Agent, Token")
        self.set_header('Access-Control-Allow-Origin', '*')

    def get(self, *args, **kwargs):
        raise HTTPError(**errors.status_0)

    # 这里因为篇幅省略了复写 post/put/options/delete

    def options(self, *args, **kwargs):
        # TODO: REMOVE?
        self.write("")

    def write_error(self, _status_code, **kwargs):
        # TODO: REMOVE?
        self.access_control_allow()
        if self.settings.get("serve_traceback") and "exc_info" in kwargs:
            # in debug mode, try to send a traceback
            lines = []
            for line in traceback.format_exception(*kwargs["exc_info"]):
                lines.append(line)
            self.finish(dumps({
                'reason': self._reason,
                'traceback': ''.join(lines)
            }))

        else:
            self.finish(dumps({
                'reason': self._reason,
            }))

    def write_json(self, data):
        self.finish(json_util.dumps(data))

    def is_logined(self):
        if 'Token' in self.request.headers:
            token = self.request.headers['Token']
            logined, uid = validate_token(token)

            if logined:
                # 已经登陆
                return uid

        # 尚未登陆
        raise HTTPError(**errors.status_2)

    @staticmethod
    def vaildate_id(id):
        if id is None or not ObjectId.is_valid(id):
            raise HTTPError(**errors.status_3)


class APINotFoundHandler(BaseHandler):
    def data_received(self, chunk):
        pass

    def get(self, *args, **kwargs):
        raise HTTPError(**errors.status_1)

    # 这里因为篇幅省略了复写 post/put/options/delete

    def options(self, *args, **kwargs):
        # TODO: REMOVE?
        self.access_control_allow()
        self.write("")

另外我使用了一个 errors 文件来储存所有的错误码以及对于的 Reason:

status_0 = dict(status_code=405, reason='Method not allowed.')
status_1 = dict(status_code=404, reason='API not found.')
# ...

还有些细节日后再聊,晚安~

♥ 135💬 13
回答2016-01-07

使用android studio 开发android前,需要对build.gradle做哪些配置?

♥ 0💬 0
文章2016-01-07

#Android# 一些 Note

All the notes are from my blog: GitHub - nekocode/nekoblog: Nekocode’s blog

//TODO 放缩处理、显示操作层
eyeAdjustView.setVisibility(View.VISIBLE);
btnViewAdjust.setTag(true);

Matrix matrix = new Matrix();
float minY = Math.min(eyesInfo.p[0].y, eyesInfo.p[5].y);
float maxY = Math.max(eyesInfo.p[0].y, eyesInfo.p[5].y);
float w = eyesInfo.p[5].x - eyesInfo.p[0].x;
float minX = eyesInfo.p[0].x - w * 0.25f;
float maxX = eyesInfo.p[0].x + w * 1.25f;

//rect 范围空间不能为 0
if(minY == maxY) maxY++;
if(minX == maxX) maxX++;

RectF mTempSrc = new RectF(minX, minY, maxX, maxY);
RectF mTempDst = new RectF(0, 0, imageView.getWidth(), imageView.getHeight());
matrix.setRectToRect(mTempSrc, mTempDst, Matrix.ScaleToFit.CENTER);
imageView.setImageMatrix(matrix);
imageView.invalidate();

eyeAdjustView.setFeatures(matrix, eyesInfo, imageView);
  • 设置 ITALIC 需要将字体的 Typeface 设置为 MONOSPACE
  • 需要 context 的地方(非 UI)尽量使用 ApplicationContext ,而不是传 Activity:Context,因为有可能会导致 activity 无法被回收(内存泄露)
  • ViewPager 不应该使用 getScrollX() 获取当前滑动的 X 坐标,因为在 ViewPager 所在 Fragment 进行 Resume/Recreate 的时候(例如屏幕旋转),无论 currentItem 为多少,scrollX 都会被置零,所以应该通过 OnPageChangeListener 来计算出真实的 scrollX:
// ...
@Override
public void onPageScrolled(int position, float positionOffset,
        int positionOffsetPixels) {
    scrollX = position * mViewpager.getWidth() + positionOffsetPixels;
    invalidate();

    Log.e("TAG", String.format("onPageScrolled: %d, %f, %d", position, positionOffset, positionOffsetPixels));

    if (mViewPagerOnPageChangeListener != null) {
        mViewPagerOnPageChangeListener.onPageScrolled(position, positionOffset,
                positionOffsetPixels);
    }
}
// ...

Java

  • Grails:约定优于配置
    举个简单的例子。在 Django 1.3 之后引入了「Class-based view」,有「ListView」和「DetailView」。Django 的「ListView.as_view(model=Publisher,)」不需要指定去 render 哪个 template,而是自动去使用了「/path/to/project/books/templates/books/publisher_list.html」这个模板。这即是 convention over configuration 的一个典型示范。优先使用默认的约定,而不是非要明确的指定要 render 的 template。
  • kotlin:限制优于约定
    nullable 和 notnullable、var 和 val 等。语法上限制比口头约定更不易造成潜在 bug。
  • Java 线程锁:Java线程(八):锁对象Lock-同步问题更完美的处理方式
  • Java 内部类会隐式持有外部类实例的引用

泛型

  • Java 实现泛型的方法是 类型擦除。使用这种实现最主要的原因是为了向前兼容,这种实现方式有很多缺陷。与 C# 中的泛型相比,Java 的泛型可以算是 “伪泛型” 了。在 C# 中,不论是在程序源码中、在编译后的中间语言,还是在运行期泛型都是真实存在的。Java则不同,Java的泛型只在源代码存在 ,只供编辑器检查使用,编译后的字节码文件已擦除了泛型类型,同时在必要的地方插入了强制转型的代码。
//泛型代码:
public static void main(String[] args) {  
    List<String> stringList = new ArrayList<String>();  
    stringList.add("oliver");  
    System.out.println(stringList.get(0));  
}  

//将上面的代码的字节码反编译后:
public static void main(String args[])  
{  
    List stringList = new ArrayList();  
    stringList.add("oliver");  
    System.out.println((String)stringList.get(0));  
}

Kotlin 入门

Note

val a: Int = 10000
print(a === a) // Prints 'true'
val boxedA: Int? = a
val anotherBoxedA: Int? = a
print(boxedA === anotherBoxedA) // !!!Prints 'false'!!!

// ====

val a: Int = 10000
print(a == a) // Prints 'true'
val boxedA: Int? = a
val anotherBoxedA: Int? = a
print(boxedA == anotherBoxedA) // Prints 'true'

// ====

val a: Int = 10000
val boxedA: Int = a
val anotherBoxedA: Int = a
print(boxedA === anotherBoxedA) // Prints 'true'
public var heightScale: Float = 0.8f
    set(value) {
        $heightScale = value
        this.requestLayout()
    }
// backing filed syntax is deprecated, user 'field' instead
public var heightScale: Float = 0.8f
    set(value) {
        field = value
        this.requestLayout()
    }
♥ 36💬 1
回答2016-01-07

QQ是如何解析聊天艾特字符串的?

流行的有两种方案:

1. 明转义

这是很多 SNS 网站的做法(例如新浪微博、知乎):

@+用户昵称+空格(也可以为其他用户名称中禁止的字符)

这种方法的局限在于:

  • 用户昵称不能含有空格
  • 用户昵称必须唯一

2.暗转义

大部分即时聊天工具有这种(例如 QQ、Wechat),它的一个原始形式如下:

转义开始符+UID+转义结束符

经过 UI 层转义后向用户显示的是:

@+用户昵称

这种方法的好处在于:

  • 用户昵称可以不唯一
  • @ 符号去除了特殊意义(注意这里的 @ 不是转义符号了

坏处在于实现起来比明转义稍微复杂一些。

应该如何选择?

  • @ 的对象没有范围限制的话,选择明转义
  • 昵称唯一化,选择明转义
  • @ 的对象有范围限制的话(好友,群),可以选择暗转义
  • 即时聊天的话,选择暗转义(能明确地指定所 @ 的对象)
♥ 1💬 3
回答2016-01-07

手机从服务器下载多个文件,如何做效果更好?

一些区别:

Ⅰ.

  • 数据冗余量大的话,打包压缩能大幅度降低所需下载流量,单文件模式好些。

Ⅱ.

  • 如果程式无需一次加载所有文件的话,多文件模式好些。

Ⅲ.

  • 如果某些文件需要部分动态更新的话,多文件模式好些。

Ⅳ.

  • 如果没实现「断点续传」的话,多文件模式好些。

♥ 1💬 0
文章2016-01-07

#Kotlin# 一些变动

前言

前段时间抽空对框架做了些修改,主要包括下面几点:

  • 将之前用于序列化与持久化的 Kryo 换成更强大的 Hawk
  • 将之前用于测试 Retrofit(使用 Gson 进行反序列)的 Java Class 替换成 Kotlin 的 Data Class。

Local Repo

先来讲讲新添加到 Local Repo 的 Storage.kt ,它简单地封装了 Hawk 来进行本地 Key-Value 类型数据的储存。选择使用 Hawk 取代 Kryo 是因为它有以下的一些 features:

  • 使用 AES 加密
  • 使用 SharedPreferences 或者 Sqlite 进行数据储存
  • 使用 Gson 进行序列反序列
  • 储存任意类型的数据
  • Rx support

当然,付出的代价就是速度降低了,但是它在反序列化时不像 Kryo 那样需传入类型的 class 对象:

// Before
val strtest = Cache["test", String::class.java]

// After
val model: Model.ParcelableTest? = Storage["test"]

Implement

Hawk 是使用 Java 编写的。我们可以创建一个 Storage 类并重载 Kotlin 中的 [ ] 操作符,来实现以下的调用:

Storage["test"] = Model(5, 1)
var test: Model? = Storage["test"]

我在前几篇文章中已经对 companion object 做了些介绍:

public class Storage {
    companion object {
        fun init(context: Context) {
            Hawk.init(context)
                    .setEncryptionMethod(HawkBuilder.EncryptionMethod.MEDIUM)
                    .setStorage(HawkBuilder.newSqliteStorage(context))
                    .setLogLevel(LogLevel.FULL)
                    .build();
        }

        operator fun set(key: String, obj: Any?) {
            if(obj != null) {
                Hawk.put(key, obj)
            } else {
                Hawk.remove(key)
            }
        }

        operator fun <T> get(key: String): T? = Hawk.get(key)
    }
}

companion object 是 Storage 类的伴生对象,它在类加载时就生成,具体实现其实就是在该类中添加一个静态子类 Companion,所以在 companion object 中的方法是 Storage 类的静态方法。

另外我们使用 operator 关键字修饰 getset 方法来实现重载 Storage 的 [ ] 操作符,一些常见的可重载操作符以及其对应的转译方法:

更多可重载操作符请查阅 Operator overloading

Entity

在 base_framework 中我使用了 Retrofit 来与服务端进行交互,并使用了提供的 Gson Converter 来进行内部的隐藏的 Json 数据与实体类之间的转换。

之前是用 Java Class 来当作实体类,后来测试了下,Kotlin 下的 Data Class 也能在 Gson 下正常转换,内嵌也没问题,于是我把原先的 Weather.java 删除掉,用下面一个全局单例的 Model 对象来保存各种实体类:

object Model {
    data class WeatherInfo(val city: String)
    data class Weather(@SerializedName("weatherinfo") val weatherInfo: WeatherInfo)
}

注意一下 Gson 的 SerializedName 注解 可添加在 Data Class 的 Primary Constructor 中的对应属性声明前。而且注意一下,这里在 Weather 类中包含了同样为 Data Class 类型的 weatherInfo 属性,Gson 一样可以正常地转换,所以,没什么理由继续用 Java Class(又长又臭)了。

实践是检验真理的唯一标准

已经开始带着一个 Team 在实践 Kotlin 到最近一个的项目中,到目前为止,已经产出了一些挺有趣的东西,有空会继续分享出来。

♥ 6💬 2
回答2016-01-06

有哪些分享图标资源的网站?

♥ 1💬 0
回答2016-01-02

一个人住是种怎样的体验?

带上钥匙去倒垃圾的感受。。

♥ 2💬 10
文章2016-01-01

新年快乐,随便侃些事

首先很抱歉上一篇文章已经胎死腹中。。新的文章已经在酝酿中了,这几天内会发布出来,这篇就当做是给大家的一个新年祝福~顺便写写近期遇到的一些事。

Parceable generator for kotlin’s data class

这个我在上一篇(被遗弃的)板凳文里面就提到了,这是我前一段时间一直在捣鼓的一个 IntelliJ IDEA Plugin。我为什么一直在说它呢?因为,,这是非 kotlin 官方团队出的目前唯一一个 Plugin,其余几个 Plugin 都是出自 kotlin 官方团队。

为什么会出现这种情况呢?归根究底,我认为有两个原因:

  1. 有 Plugin 开发经验的大牛们,还未有对该们语言提起兴趣的;
  2. 基于 kotlin 语言开发 Plugin 必须依赖 kotlin-plugin 官方插件。IntelliJ IDEA 对 kotlin 语言的支持是以插件的形式提供的(这有利有弊),对 kotlin 语言内的各种 PSI Elements(IntelliJ Platform SDK DevGuide )的处理不同于原生对 Java 的那一套。

第二点具体是什么概念?我解释一下,Jetbrains 虽然在 Github 上开源了 kotlin 所有相关的实现,但并没有任何官方 Wiki 对 kotlin-plugin 进行任何接口解释。要写出一个第三方 Plugin 完全靠阅读 JetBrains/kotlin · GitHub 的一大堆源码啊。

当然,最后我还是把它弄出来了 :D。期间有个白俄罗斯的小伙提交了个 Issue,有人反馈的感觉真好~立马修复并更新上 Plugins Repository,目前下载量有两百多(不多,但是在缓步上升),期待能有其他人参与贡献。

Android 开发者市场水太深

近几天,无聊搜了好几个 Android 开发 QQ 群来加。人嘛,都是社群动物,总想和自己领域内的人打打交道。

然而我发现,现在的各种培训班真是无处不在,我 TM 刚进群就几个陌生妹子号开私窗和我唠起来了,先是套近乎,接着「热情」给我推荐各种 Android 在线培训全家桶,只需花个几千块就可两个月内入门 Android 应用开发,一年内月入上万有木有。。

再看看群友们平时都在聊些什么。。。。

大家都在讨论着哪个培训班更便宜、更靠谱,交流着各种培训班的训练项目,讨论的问题也基本上是一些低层次的问题。。

这些无良培训班,打着培训完后月入上万的口号,让多少非专业甚至对编程完全不感兴趣的人涌入开发者市场。本身对编程感兴趣的人就算不上多了,现在还要跟一群其他行业转过来的培训生竞争岗位,也是醉了。

前几个月公司忙着招人的时候,身为面试官,对所有培训班出来的面试者天然降 30 分(真有能力者例外)。并非歧视培训班出来的人,但是培训班出来的,多数要不就是其他专业转过来的,要不就是大学时期没好好学习,工作前还需要再培训。

要知道,并非培训几个月就能熟悉某个领域,更不是做过几个培训项目就有动手能力。问过几个培训班出来的,甚至连很多计算机基础问题都答不出来,整场面试也就只能围绕着他们做的那几个「培训项目」说说。到后来也就疲倦了,凡是看不上的都直接提前中止面试,省得浪费时间。

这年头,比较好的开发者很多都开始移民了,现在给国内这些培训班一搅和,长尾效应更严重了,花个半天时间去逛 CSDN、eoeAndroid 等社区,会发现实在是鱼塘水深。

♥ 10💬 4
回答2016-01-01

你 2016 年第一行代码是用来做什么的?

答这个问题的,看来是挺难找到女朋友了。。

♥ 0💬 1