一个人住是种怎样的体验?
带上钥匙去倒垃圾的感受。。
带上钥匙去倒垃圾的感受。。
去年开源了一个项目(当初花了很大心血),结果今天在 Github 上就搜到有国人去掉了版权说明并进行二次开发,真尼玛啊……
之前逆向过一些应用,一些比较典型的逆向过程放在了我的 Blog 里:nekocode.cn 有兴趣的朋友自取。
这次的目标是知乎 Beta 版,大概因为是 Beta 版的原因,Anti-Decompiling 做得比较差,而且混淆后还保留了 Source file attribute 和 Line number tables,应该为了方便调试吧。
手机上访问 知乎客户端 β 下载 Beta 版 Apk 到手机。执行 Adb Pull 拉取 Apk 文件到电脑上,直接 Jadx 大法就是干。

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

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

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 发送的一条私信哦。
可以简单地理解为 Bitmap 储存的是 像素信息,Drawable 储存的是 对 Canvas 的一系列操作。
而 BitmapDrawable 储存的是「把 Bitmap 渲染到 Canvas 上」这个操作。
早些时候,依赖 Activity 生命周期的操作以及业务逻辑都集中在 Activity 中,Activity 变成了单个页面的上帝类,大多数的代码都写在 Activity 中。而 Fragment 的出现分担 Activity 的重任,它和 Activity 有着同步的生命周期,它可以装载一系列 View 并管理这些 View 的生命周期,而一个 Activity 中可以包含多个 Fragment,这就意味着可以将不同功能布局的 View 用 Fragment 分开管理出来,这大大地减轻了 Activity 的负担,还从另一方面提供了适配屏幕大小以及横竖屏的方法。
但拥有与 Activity 同步生命周期的 Fragment 不单单可以用来装载 View,你也能将原先 Activity 中依赖生命周期的逻辑代码迁移到 Fragment 下,它比起 Activity 更加小巧灵活,而且被设计为完全可以不包含任何 View。
我在知乎上一个回答上提到使用 Fragment(碎片)来进行重构,所以补充一下:
它能在重构中能做些什么?
无限细化是什么意思?就是你任何视图或者逻辑都可以用 Fragment 一直细化到你喜欢的粒度为止。一个 Fragment 内可以只有一个 View,也可以只用来实现一个后台下载任务。你应该懂为什么 Google cheng它为碎片了吧?
假设进行如下尝试:用没有 UI 的 Fragment 来构建 MVP 架构中的 Presenter,用来存放程序中的控制逻辑(调用业务逻辑代码,DO 到 VO 的转换等操作),它能带来以下一些明显好处:
这个 Presenter 看起来应该是这样的:
class MeiziPresenter(): BasePresenter<Contract.View>(), Contract.Presenter {
override fun onViewCreated(view: Contract.View?, savedInstanceState: Bundle?) {
view?.showToast("View created.")
}
}
无疑这是一种新的尝试,我有看过类似的观点,但是并没有具体的实现。于是我在 Kotgo 的新版本中将原有 Presenter 改为用 Fragment 实现。它一下子解决了我很多的问题。例如:
关于如何恢复视图以及数据现场网络上的文章很多有坑,下面是引用自我的 Git Blog ,比较靠谱的一些笔记:
最近工作比较忙,没时间更新专栏。但是在空余时间我有在为 Kotgo 的下一版本进行准备,我们团队已经在基于 Kotgo 0.7 的架构进行产品开发将近一个月,大概在下个月也将会迁移到 Kotgo 最新版本上。事实证明,使用 Kotlin & MVP 这个组合在生产环境上十分愉快,期待我们团队未来能有更多的探索以及发现。
PHP 是世界上最好的语言。
在 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 来纪录它们切换页面的用时:(单位:毫秒)


要使用 Fragment 来取代 Activity 的职能还需要做一系列的工作,我们需要把一些 Activity 中常用的功能搬运到 Fragment 中,并且我们还需要另外维护一份 Fragment 页面的栈队。要知道 FragmentManager 在进行 Fragment 现场还原的时候只恢复内部的事务栈队(Transaction Stack),并不会恢复每个 Fragment 的显示状态,所以会产生 Fragment 重叠的问题。
而需要从 Activity 中「移植」到 Fragment 的一些必要功能包括:
而且为了模仿 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 上(因篇幅问题,详细实现可查阅源码):
Single Activity Multiple Fragments确实是一种不错的架构页面的新方案。它提供了一种新的思路来组织页面,所有页面的 View 保存在碎片中,然后在单个 Activity 中快速切换,思考下,其实这是一种退化(或者说底层化)的思想,用 Fragment 组织 View 分页面,然后全部 View 一碌脑全在单个 Activity 上初始化/显示/隐藏/绘制。
所以说更底层化的思想带来了更快的页面切换与交互。
Murmur 是一个带白噪声效果的豆瓣电台第三方客户端。鉴于之前回答中有人对该项目感兴趣,现已整合重构并开源,欢迎 Star 哦 (。・`ω´・)
Murmur 采用 Kotlin / MVP / ReactiveX 进行构建,它是 Kotgo 的一个实现案例,详细地描述了如何使用 Kotlin 来构建一个健全的 MVP 项目。如果你对使用 Kotlin 进行 Android 开发十分感兴趣,强烈推荐你对本项目进行研究。
如果你对 MVP 模式十分感兴趣,也请关注该项目,它比大多数你能看到的 MVP 架构的开源应用要正确得多,它的实现更为清晰且思路正确。它解决了一系列能考虑到的问题(生命周期/屏幕旋转),它是更能经得起考验的。


程式中的 OpenGL Shader 特效本人修改自 Shadertoy 。
你也可以在 这里 下载到它进行试用。
该项目仅限用于学术研究,不得用于商业用途。项目中的 Launcher Icon 来自设计师 @汤未冷 ,特此声明。
自荐一下我的个人网站(记得打开声音
使用 PixiJS + TiledMap 实现的小岛

灵感来自湾湾一个组织搭建的在线网站(因为涉及政治,这里就不分享更多信息了
源码在我的 GitHub 上可以找到
没深度用过 vue(作为替代我会选 svelte,狗头
但就 vue2 而言(这里不讨论 vue3),单单是对 typescript 支持不太行这一点,就注定了在大型项目上不会被优先考虑。
其他一些比较就不说了。个人感想是 react 是在引领行业,而 vue 则是实用主义,看菜下饭吧。
原文来自 被滥用的 GUI 设计模式 。
随便侃些个人对 GUI 设计模式的看法。
近些年来,随着 Fronted 技术的火热和推进,古老的(至少有几十年历史)用来解决 GUI 应用中代码组织问题的「GUI 设计模式」现在也成为了 Frontend 工程师的热门话题,MVC、MVP、MVVM 等设计模式在网路上被议论不绝。有很多工程师开始通过写博文来介绍它们、阐述自己对它们的理解,甚至在 Github 上开源了各种 GUI 设计模式的实现。
顺着这种趋势,很多 Frontend 工程师甚至把 GUI 设计模式当成一种「规范」乃至「教条」。然而糟糕的现实是,大多数人并没有正确地、细致地理解和运用 GUI 设计模式,反而因为 Tradeoff 导致它的缺点被放大。结果就是你用了大量精力、模板代码去设计它,反而让它更复杂、更难维护了。
例如,当你打开 Github 上大多数试图实现 GUI 模式的仓库时会发现,整个应用大概也就两三个页面、四五个网络接口,就可能已经创建了几十个类和接口来承载那单薄的逻辑了。举个更具体的例子,我个人曾经接触过几个用 MVP 模式设计的大型 Android 工程,在进行维护或者迭代的时候,各种带有问题的设计反而让 MVP 模式成为了累赘。
首先,工程中大多数 View 都是粒度大耦合度高的 Activity 类,而且很多 View 里为了方便,会提供 fun updateView(user: UserModel) 这样的方法,导致 View 和领域/业务模型直接耦合了。再者,View 和 Model 中还会包含了跳转页面、发送全局消息等各种带有「副作用」的命令,这也让面向接口编程成为了形式主义。
所以与其「舍本逐末」、「知其然而不知其所以然」,倒还不如理解问题的本质。于 GUI 设计模式而言,实际上最重要的思想是「分而治之」,通过把之前都写在一处的代码按照职能分到不同的类,来让它们实现「低耦合高内聚」。所以,我们更应该把 GUI 设计模式当成一思想而不是具体的手段,更也没必要用各种所谓的模板来解决问题,只要你能把热点、关键代码设计得足够低耦合高内聚,那么你完全可以无视所有 GUI 设计模式。
例如上面提到的 fun updateView(user: UserModel) 问题,实际有两种方式来让 View 和业务模型 UserModel 解耦:
// 方法一
interface ViewA {
fun updateText1(text: String)
fun updateText2(text: String)
// ...
}
class PresenterA {
fun onSomeEvent() {
val userModel = Apis.requestUser()
viewA.updateText1(userModel.name)
viewA.updateText2(userModel.age.toString())
}
}
// 方法二
interface ViewA {
data class ViewAttributes(
text1: String,
text2: String,
// ...
)
fun updateView(view: ViewAttributes)
}
class PresenterA {
fun onSomeEvent() {
val viewAttributes = Apis.requestUser().mapTo ViewAttributes()
viewA.updateView(viewAttributes)
}
}
方法一更倾向于用「指令」来描述 View,方法二则更倾向于用「数据」。而我个人更喜欢方法二,因为数据是运行时可处理、可持久化的,甚至可以跨进程、跨语言、乃至跨机器共享的。讲个题外话,Web Fronted 里 Redux 等状态管理工具捧起了一个很火的词「时间旅行」。在我看来核心思想其实也是把指令下沉,用数据(/状态)来描述上层逻辑,这样就可以在运行时实现逻辑可记录、可回放。
这里还有一点需要注意的,ViewAttributes 必须是 View 的领域模型,字段名称应当仅和 View 本身相关,而不应该和其他领域有关系。
再回到之前提到过的另外一个问题:View 和 Model 里的副作用。这个其实更容易解决,只需要把所有副作用移到外部(/调用方)就好了。例如:
// 有副作用
class ViewA {
fun onTitleClick() {
sendBroadcast("x")
}
}
// 无副作用
class ViewA {
fun onTitleClick() {
caller.onTitleClick()
}
}
class PresenterA {
fun onTitleClick() {
sendBroadcast("x")
}
}
实际上,只懂得 OOP(面向对象编程)的工程师很容易造成前面提到的问题,因为他们习惯了依赖「外部状态」来解决问题(类的实例本身也是一个状态),但是在状态数量不断增加的情况下,状态的管理反而会成为一个新的大难题。而 OOP 提倡类的「低耦合高内聚」实际上可以看成是在解决状态管理的问题。
所以在文章的最后,我强烈推荐工程师们可以学习下 FP(函数式编程)。相对于 OOP 而言,FP 的思想则是摒弃外部状态,它实现的是粒度更小的函数级别的「低耦合高内聚」,你只需要保证你的函数是无副作用的然后管理好函数内部的状态就可以了。而维持这种编程思想,能让你轻松驾驭巨型、复杂的项目,甚至能让你的代码更容易被调试,更容易被并行执行。
对于 Android 工程师们来说,Kotlin 目前的火热正是让大家有了更了解 FP 的机会。之后我也会写些和 Kotlin、FP 有关的文章。
这是「一年の使用报告」系列的第二篇,上一篇可点击 一年の使用报告 - 类型设计 查看。
关于函数式思想还可以拉上 C# 来讨论。C# 大体上和 Java 蛮相似的,它出现的动机也是为了和 Java 抗衡,但很可惜 .Net 的跨平台化并不像 Java 那样迅速而可靠,目前看来虽然微软已经在加快脚步,但明显已经错失了大多机会。
C# 在 v3.0 就已经开始支持 Lambda 语法了,而 Java 直到 Java 8 才开始支持 Lambda,可以看出虽然一开始大量借鉴了 Java 的思想,但在之后的发展上 C# 是超前于 Java 的。而作为现代编程语言的 Kotlin 对比起两位前辈来说要显得更为前卫些,在刚出生时已经融入了函数式、DSL、类型推导等特性。对比下三个语言的 Lambda 写法:
// Java 8
a.listen(context, e -> handler(e));
// C#
a.Listen(context, e => handler(e));
// Kotlin
a.listen(context) { e -> handler(e) }
个人比较喜欢 Kotlin 的写法,作为方法最后一位参数的 Lambda 表达式可以直接写在括号后,如果只有一位参数的话可以直接省略括号不写。
而 Kotlin 在高阶函数的声明上也比 Java,C# 要方便得多,支持形如 (Int)->Int 的 函数类型。
// Java 中的高阶函数(接口表达)
public interface Test {
int run(int a);
}
public void doTest(Test test, int a) {
test.run(a);
}
// C# 中的高阶函数(委托表达)
public void DoTest(Func<int, int> test, int a)
{
test(a);
}
// Kotlin 中的高阶函数(函数类型表达)
fun doTest(test: (Int)->Int, a: Int) {
test(a)
}
传统命令式语言的代码大都由 表达式 和 控制语句 组成,C# 和 Java 也在此类之中。
// Java 的条件语句
int a;
if (b > 0) {
a = b;
} else {
a = c;
}
而函数式语言的区别在于函数式语言中的 只有表达式,if else 等被纳入多元运算符。Kotlin 也吸收了这种思想,把 if else、when 等作为多元运算符。可以看出 Kotlin 比 Java 和 C# 更接近函数式思想。
// Kotlin 的 if else 表达式
val a = if (b > 0) b else c
// Kotlin 的 when 表达式
val t = when (x) {
0, 1 -> true
else -> false
}
另外,Kotlin 中的 Delegated Properties(例如 Lazy Delegate)也是函数式思想的表现,它能委托对变量的读写操作。例如下面的语句,只有在变量 a 第一次被访问时才计算结果。
// Kotlin 中的 lazy 代理
val a = lazy { 10 + 11 }
在函数式语言中,通常还提供了 对 Collection 进行操作的高阶函数集(map、filter 之类的方法)。C# 有 LINQ,而 Java 和 Kotlin 有 Streams。
// Kotlin 中的 Streams 接口
(1..10).asSequence().filter {
it > 5
}.forEach {
System.out.print(it)
}
但要知道 Java 8 才有 Streams 接口,所以目前想在 Android 开发中用上 Streams 接口的最方便的方式依然是使用 Kotlin。
Update:感谢 @林育斌 的提醒,已将最后一个例子改为和 Java 中的 Streams 更相似的 Sequence 实现(惰性计算)。关于 Iterable 和 Sequence 之间的区别可以查看这里:StackOverflow 。Sequence 和Iterable 具体的取舍需要看使用场景。
题主试试这个: droidwolf/NativeSubprocess · GitHub
创建 linux 子进程的 so 库,当初用在 service 免杀上,经测试,在大部分机子上有用。
安全软件卸载后调出浏览器苦苦哀求"主人,为什么要抛弃我…“页面是怎么做到的? service 经常莫名挂了肿么办?用 NativeSubprocess 一切都很简单。
NativeSubprocess 是一个可以让你在 android 程序中创建 linux 子进程并执行你的 java 代码的 so 库。由于市面上典型的内存清理工具值清理 apk 包关联的进程,而不会处理 linux 原生进程,所以 NativeSubprocess 可以做什么您懂滴!
可以参考下这个问题 java - Should private helper methods be static if they can be static ,总结来说有以下两点:
把 JIT 的内联优化放在第二位是因为这是个很小的优化,而Volley 源码中大量使用的 private static 主要原因是,为了清晰地表明该方法不会对所在对象造成任何更改。
该来的还是来了。虽然对于 android 来说 coroutine 应该不常用到,coroutine 更适合用在 server 这种 io 密集的应用上。
coroutine 做的其实就是在单线程里替换掉所有 thread.sleep() 函数。例如把 thread.sleep() 换成 asyncio.sleep()。当你需要延时的时候,为什么要调用 thread.sleep() 来阻塞线程呢?还不如用 cpu 来执行程序里的其他代码。所以它把所有 thread.sleep() 替换成 asyncio.sleep(),把所有包含 thread.sleep() 的其他函数(最常见的就是 io 函数)替换成非阻塞的版本。当调用到 asyncio.sleep() 时实际上并不是执行 thread.sleep(),而是保存当前栈然后去执行其他代码,等到恰当时机时再恢复栈继续执行之前的函数。
说起来 coroutine 的实现其实和 android 里的 looper 有点类似,android 是借助 looper 来在单线程里实现异步的,例如 postDelay(),它没有在 postDelay() 里调用 thread.sleep() 来延时,而是把实际要执行的代码扔到事件队列中,等到合适的时机再执行相应的代码,它保证了不会阻塞当前的 main thread。
Running on multiple platforms is a strategic direction for Kotlin. With 1.1 we can run on servers, desktops, Android devices and browsers, but in the future we are going to compile Kotlin to native code and run on many more platforms (including, for example, iOS and embedded devices).
kotlin 未来有望支持编译成 native code,这是个让人十分 excited 的消息。现在已经能将 kotlin 编译成 js 了,未来能编译成 native code 的话,以后你写的代码有望在不借助 jvm 的情况下跑在各个平台下(例如 iOS)。显然 jetbrains 在做一件野心很大的事情,它在尝试让 kotlin 变成更通用的语言。
至于 android 领域,我觉得 android 开发者们现在可以尝试下这门语言了。不用纠结于 google 是否会提供官方支持,按 kotlin 目前的成熟度(要知道 jetbrains 的 ide 技术是世界顶尖的),已经不需要 google 官方给出什么支持了(当然,推广上的支持除外)。虽然国内的开发者可能感觉不到 kotlin 的热度,但是在 reddit 的 android dev 板块上 kotlin 一直是火爆的话题,很多开发者甚至团队都已经用了很长一段时间了。
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);
// ...
@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);
}
}
// ...
//泛型代码:
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));
}
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()
}
「又流行」感觉像是开历史的倒车,但其实并不是。以前是 Back-end(或者说 Full-stack)工程师负责 SSR,但是现在是 Front-end 工程师负责 SSR 了啊。在目前这个知识爆炸的年代,前后端的职能目前已经被分割得很开,大家都不愿意去淌对方那摊混水,而 Rendering 这事从语义上出发就属于 Front-end 的范畴,让 Back-end 去做这事,其实很多人是不愿意的。
回到问题本身,SSR 的「又流行」其实是 Front-end 社区工具栈不断进化的体现,也是历史的必然啊。几年前,SPA/CSR 概念的大热,让很多 Front-end 把他们当做万金油了。其实大家都知道 CSR 有着 SEO 的问题,而且页面渲染速度肯定比不上 SSR,但苦于社区中没有解决这个问题的工具栈,所以大家都对这个问题视而不见了。
而随着 Front-end 社区造轮子大潮的兴起,出现了一个很关键的历史转折点 —— Node.js 的出现。Node.js 赋予了 Front-end 在服务端执行 Js 的能力,有了这个环境和土壤,Front-end 工程师们终于可以考虑如何用 Js 来实现 SSR 了,于是 React 和 Vue 等主流框架后面开始支持 SSR 也就成了必然。
所以之前并不是不流行,而是因为前后端职能的分离,Back-end 不愿意做这事了,Front-end 没有条件去做。现在有条件了,自然又开始流行了。
编码是一门艺术,无论是作家还是开发者,都追求优雅的作品。而优雅的代码对于开发者来说就是艺术品,它的「优雅」体现在「表现上的优雅」以及「设计上的优雅」。
kotlin 能很大程度上帮助我们实现「表现上的优雅」,它促进了语法上的极简,用先进的糖来取代 Java 臃肿的语法,使得简短的代码拥有更强的表达能力。当然「表现上的优雅」也包括代码风格上的统一,要保持良好的代码风格十分考验开发者的基本功。
而「设计上的优雅」需要考验开发者更高层次的功力,它指的是 代码组织上的优雅,需要开发者拥有大量的编码经验,以及很强的逻辑思维。早在 上个世纪七十年代 ,为了解决组织与复用代码的问题,就有开发者提出「设计模式」的概念。
这包括了早些时期的 MVC,以及现在比较流行的 MVP,MVVM 模式,它们通常指的是 GUI 领域的设计模式。它们描述了在 GUI 编程中组织与复用代码的一类方法。它们面对开发者而非机器,消耗或多或少的性能,来换得更好的可阅读性、可维护性以及可复用性。而我们今天要研究的是 MVP 设计模式。
在 Android 传统开发中,很多时候在 Activity(或 Fragment)下会产生数千行代码。按照传统的 MVC 设计模式,Activity 应该定义为 Controller的角色,它负责接收多个不同 View 的事件,并根据不同事件调用相应 Model 接口进行数据操作。
但实际上,纯的 MVC 模式在 Android 开发中并不存在,Activity 除了担任 Controller 之外还要负责一部分 View 的角色。归根究底是因为 Android 开发中被定义为 View 层的界面描述文件(layout,style 等 xml)能力十分有限,无法通过它完成所有对 View 的操作,导致很多界面操作必须 Activity 下实现(例如界面更新,复杂动画的实现等)。
Activity 中业务逻辑与界面操作耦合在一起,导致了当项目代码达到一定体量的时候,维护和升级代码变成了一件十分耗时和困难的事情。我们需要重新寻求 Android 开发中能更好组织、分层代码的设计模式,而 MVP 模式是种不错的选择。
MVP 设计模式使用 Presenter 充当中间人,负责原先 Controller 的角色,但是它完全隔离了 View 和 Model,Model 只能通过 Presenter 来更新界面(或者说通知界面更新)。
我们在 base_framework 中使用 MVP 模式来尝试设计代码,代码的层次结构如下图:

它包含的层次:
传统的 MVP 模式是单个 View 对应单个 Presenter。但是我们当将 Activity 定义为 View 之后,如果我们将原先 Activity 中所有控制逻辑仅迁移到单个 Presenter 上,会导致 Presenter 复用性低。所以我更倾向于根据逻辑拆分成不同的 Presenter,单个 Activity 对应多个 Presenter,而且每个 Presenter 可复用(n 对 n 关系)。
下面我们来实现个 Presenter 试试,它将负责从服务器获取天气信息,并通过接口通知 Activity 进行界面更新:
class WeatherPresenter(val itf: WeatherPresenter.ViewInterface) {
interface ViewInterface {
fun setWeather(weather: Weather)
}
fun fetchWeather() {
REST.api.getWeather("101010100").onUI().subscribe({
itf.setWeather(it)
})
}
}
于是,我们就可以在 Activity 中像下面这样引用 WeatherPresenter:
public class MainActivity : SingleFragmentActivity(), WeatherPresenter.ViewInterface {
// ...
val weatherPresenter = WeatherPresenter(this)
override fun afterCreate() {
weatherPresenter.fetchWeather()
}
override fun setWeather(weather: Weather) {
toolbar.title = weather.str
}
// ...
}
要注意的是,比较好的设计是:Presenter 通过接口来通知 Activity 进行 View 的更新,而不是直接操作 View,否则会造成 Presenter 和特定 View 的耦合。
使用 MVP 模式为代码进行分层,有助于我们清晰地进行代码组织,方便以后的维护:
话说回来,Android 前些时间发布的 Data Binding 库 不就为了提高 xml 在 View 层的控制能力么,不过,那已经算是 MVVM 设计模式了。
个人认为还是有蛮大机会的,主要有两个点:
以上两个点保证了 Android 开发者可以无缝从旧项目切换到 java + kotlin 甚至完全使用 kotlin 的模式下。而能够诱导用户加入 kotlin 大军的话,当然是 kotlin 各种语言 feature(详见
)安全检测空指针,更好的 Lambda 支持,更好的函数(一级公民),泛型。
更多关于 kotlin 的介绍请看官方文档:
个人已经使用 kotlin 开发新项目有几个月了,开发中代码量锐减了大概 50%,开发效率提高了 30% 以上。唯一弊端是 kotlin 依然处于高速发展中,个人维护的一个项目在开发途中就经历了 kotlin 的两次大改动,语法变动较大。所以 生产环境请下慎用。
使用 kotlin 进行 Android 的基本框架:
大约有一年的 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 这棵树上就依然没有根本性地解放生产力,编程语言界一些更好玩、更先进的思想都不知道他们啥时候才能玩得上呀~(雾)
技术上找原因的话大概很关键的一点是: 工程师还原能力低。。
其他可能是:
==== 以下是对设计上找原因的一些看法 ====


个人认为最重要的原因在于:
其中最重要的应该是 设计统一感!检验的标准就是检查 APP 是否有 统一的调色板,统一风格的 icon 库,统一的 dimen 表。以及 APP 内是否有没有令人感觉「异类」的元素。通常能做到设计感统一就不会显得太过粗糙。
而「好的 UI 设计」必然是需要设计师和工程师共同努力的(一个 具有设计感的前端工程师和 一个有基础代码能力的设计师 十分重要)
专门去翻了下两个开源项目的 git commit history。
next.js 在 10 月 25 号的「发布」实际上是将 1.0 版本开源出来,而实际该项目最早的 commit 是在 10 月 6 号:
而题主说的 nuxt.js 在 10 月 26 号的「发布」实际上只是作者创建了 GitHub 仓库而已,nuxt.js 的第一个可用的原型完成的时间是在半个月后的 11 月 7 号,而且可以看出很多代码中有参考了 next.js 的痕迹:
所以,可以推测出 nuxt.js 的作者 Sébastien Chopin 在 next.js 开源出来后没多久就萌生了给 vue.js 复刻一个 next.js 的计划,随后就立马创建了 nuxt.js 这个仓库。
2011 年,作为全球最先进 IDE 开发商之一的 JetBrains 揭露了一项正处于开发中的编程语言 —— Kotlin。它是一门跑在 JVM 上,和 Java 间具有高互操作性的全新语言。JetBrains 通过多年来和各种编程语言打交道的经验,为 Kotlin 整合了多项现代编程语言特性。
2017 年,Google 在 I/O 大会上宣布将 Kotlin 作为 Android 开发的官方支持语言。彼时,Kotlin 的开发者社区开始了爆炸性的增长,而 Netflix、Pinterest、Trello、Kickstarter 等知名公司也早已开始使用 Kotlin。
2018 年,目前 Kotlin 1.3 正式版本已经发布,更多的语言特性得到了支持。而随着官方提供了更多的编译后端,也让 Kotlin 摆脱了 JVM 的束缚,使用 Kotlin 编写的代码可以编译成机器码甚至 JavaScript 而跑在不同的运行环境中。另外,根据 Google 官方的调查,目前已有 40% 的 Android 开发者选择使用 Kotlin 进行编程工作,国内抖音、网易有道词典、大众点评、知乎等大量应用也开始引入 Kotlin。
既然 Google 已经宣布 Kotlin 成为 Android 开发的官方支持语言,也说明了至少在 Android 开发上使用 Kotlin 目前已经毫无障碍了。那么作为 Android 开发者,对比 Java 而言,使用 Kotlin 语言能获得什么好处呢?
第一点,使用 Kotlin 能够让我们的代码变得更简洁。我们都知道 Java 以它语法的严谨性而著名,它支撑起了世界上各种大型、复杂的计算机软件。然而 ,它的语法是有历史包袱而且略显啰嗦的,对比更灵活的现代语言,使用 Java 来实现同样的逻辑通常需要写更多的代码。而没有历史包袱的 Kotlin 则吸取了现代语言各种灵活简洁的语法,让开发者在 JVM 上也能写出简洁的代码:
// Java
final ArrayList<String> a = new ArrayList<>();
// Kotlin
val a = ArrayList<String>()
// Java
public String b(String c) {
return "Test: " + c.substring(2);
}
// Kotlin
fun b(c: String) = "Test: ${c.substring(2)}";
除了语法上的各种简便,Kotlin 的标准库也提供了很多实用的方法来精简你的代码,例如针对开发中最常用的「集合」,Kotlin 提供了封装让你可以轻松创建集合类、使用和 Java Stream 相似但更丰富的接口来操作集合:
// 创建包含元素 1、3、5、7 的 ArrayList
arrayListOf(1, 3, 5, 7)
// 过滤出集合中大于 3 的元素
.filter { it > 3 }
// 转换成字符串
.map { "$it, " }
// 循环输出
.forEach { println(it) }
更多 Kotlin 比 Java 语法更精简的例子可以查看 From Java to Kotlin 。
第二点,使用 Kotlin 能让你的程序更安全。Java 工程师最常见陷阱之一就是访问了空的引用而导致空指针异常,而 Java 在语法上无法描述某个引用是否可空,所以开发者要背负起更多的心智负担而不得不经常进行判空操作。虽然目前可以通过 Annotation + IDE 提示的方式来一定程度上减轻这个负担,但这并不是一个强约束,在 IDE 上的提示是可被忽略的。
而 Kotlin 在语法上对此进行了强约束。在 Kotlin 中定义某个引用时必须描述其是否为可空类型,对于可空类型引用的不安全访问会在编译期报错:
// 不可空类型,可以直接访问
val a: String = ""
a.substring(2)
// 可空类型
val b: String? = null
b.substring(2) // 不安全访问,编译报错
这有利于在运行前察觉并处理可能的空指针异常。而且 Kotlin 还提供了 ?.、?:、类型自动转换等便捷的语法来辅助处理可空类型:
val b: String? = null
b?.substring(2) // 当 b 不为空时才调用 substring()
if (b is String) {
b.substring(2) // 自动把 b 转换成不可空类型
}
val c = b ?: "" // 如果 b 为空的话,则返回 ?: 操作符右边的值
以上两点是 Kotlin 能给大多数 Java 开发者带来的直接好处。但 Kotlin 能给开发者带来的也不仅仅只有这些,它有着完美的 IDE 支持(这也正是 JetBrains 的强处),它与 Java 之间的高互操作性让你可以轻松使用 Java 生态中丰富的库,而它对各种现代语言新特性(例如协程、函数式编程)的支持,能让你在面对不同的计算机问题时有更多不同的思考和解决方式。
我们要知道,任何编程语言在设计时都需要做各种取舍。Kotlin 在提供高灵活性的背后也需要开发者付出一定的代价:
实际上这也是大多数语法灵活、语法糖多的语言的共同问题。但我们不必过于担心,通过使用强大的 IDE 和建立代码规范这些问题都能被解决。作为开发者,我们应该把精力放在用更少的代码、更快、更方便地描述出我们想要的「逻辑」上,其他的负担都交给编译器或 IDE 吧,这也正是高阶编程语言诞生的初衷。
总所周知,由于 Google 和 Oracle 之间的各种政治斗争,导致 Android 开发者一直以来只能用上阉割版的 Java。大部分开发者甚至是最近几年才开始用上、甚至开始知道 Lambda 表达式,而像 Stream 之类的工具更是无人知晓。虽然期间有传闻过要支持 Go 和 Dart 语言,但很快也都音讯全无了。
而 Kotlin 的出现正好弥补了 Android 开发生态中一块巨大的短板 —— 落后的开发语言。而且,Kotlin 和 Java 十分相似(甚至很多人把它认为是 Java 的增强版),所以从 Java 过渡到 Kotlin 的门槛比起其他语言来说相对更低。而基于 JVM 又让 Kotlin 的代码可以很轻松地运行在 Android 平台上。这么看来,Kotlin 确实比起 Google 自己的 Go 和 Dart 来说更适合作为 Android 平台的开发语言,也难怪 Google 最终敲定 Kotlin。
纵观未来,随着 Google 和 JetBrains 深度的合作,Kotlin 也肯定会成为 Android 开发生态中最先进的工具之一。目前通过 Kotlin Android Extensions 已经可以很方便地在 Activity 中直接通过 Id 名来直接访问对应的 View:
import kotlinx.android.synthetic.main.activity_main.*
// 设置 id 为 helloTextView 的 TextView 的文本
helloTextView.text = "Hello World!"
而 Google 官方推出的 KTX 库 更是让开发者能够更方便地使用 Kotlin 来开发 Android 应用:
// 使用 KTX 前
view.viewTreeObserver.addOnPreDrawListener(
object : ViewTreeObserver.OnPreDrawListener {
override fun onPreDraw(): Boolean {
viewTreeObserver.removeOnPreDrawListener(this)
actionToBeTriggered()
return true
}
}
)
// 使用 KTX 后
view.doOnPreDraw {
actionToBeTriggered()
}
另外一个好消息是,今年 11 月刚发布的 Gradle 5.0 也宣布支持了 Kotlin DSL,这意味着我们甚至可以用 Kotlin 来写我们的构建脚本了:
android {
compileSdkVersion(27)
defaultConfig {
applicationId = "com.test.app"
minSdkVersion(15)
targetSdkVersion(27)
versionCode = 1
versionName = "1.0"
}
}
所以从各种迹象来看,答案其实已经很明显了。Kotlin 的诞生以及被 Google 的钦点,对一直以来被语言限制生产力的 Android 开发者们而言意义非凡。而就 Google 和 JetBrains 的影响力来看,未来几年 Kotlin Android 开发者的数量将呈爆炸式增长,市场对于 Kotlin 工程师的需求也将会不断增加。所以,学习 Kotlin 不但能让你接触到更先进的工具、思想,也肯定能让你在人才市场上更具竞争力。
事实上,根据 Github 今年发表的 Octoverse 报告 ,Kotlin 已经成为增长速度最快的语言。
如果你已经对 Kotlin 产生兴趣,可以先通过官方的 Playground 来在线尝试下 Kotlin 的语法。它还包括一些列用于演示各种语法的实例,以及一个完整的语法课程。
如果你想在本地创建一个全新的使用 Kotlin 编写的 Android 应用项目,参照官方文档中的 Getting started with Android and Kotlin 来进行即可,目前 Android Studio 已经完全支持 Kotlin 语言。
而如果你想在一个使用 Java 的 Android 应用项目中同时使用 Kotlin,也是完全没问题的。通过上一个链接的教程引入 Kotlin Gradle Plugin,即可在你的源码目录下通过 Android Studio 菜单直接创建 Kt 源码文件。
有人可能会担心使用过程中遇到各种坑 。实际上,笔者在 2015 年就开始使用 Kotlin 了,期间在语法、IDE 支持、Kotlin 注解处理器上都遇到官方不少的坑,但由于官方的迭代速度足够快,很多问题很快就被修复了。另外,Kotlin 的社区也十分活跃,如果遇到坑或者问题也基本都能在上面找到回答。
而自从 16 年 Kotlin 1.0 发布之后的版本就更加稳定了,工具链、IDE 支持也都十分完善。所以大可不必担心会遇到无法解决的坑。
有科学家表明,使用不同的自然语言会影响人的思考方式。而编程,亦是如此。激进的 Kotlin 和保守的 Java 之间的差异,肯定会给我们带来不一样的思考问题的方式。而这些不一样,也肯定会影响未来 Android 开发的新风向。
在笔者看来,Android 开发界随着 Kotlin 的出现实际上已经到了一个新的纪元,浪潮已来,为了不被浪潮所击退,请用力拥抱 Kotlin 吧!
最近在使用 RxJava 的时候,遇到了一些问题。我在某个页面订阅了 Retrofit 一次异步网络请求返回的 Observable,在 Retrofit 尚未发射结果之前退出该页面的话,程式会中止。
发生中止的原因是因为 Observable 的生命周期凌驾于 Activity(/Fragment) 之上,当页面销毁后,并没有中止 Observable 流,所以在 Retrofit 中的 Observable 依然保留了 Activity 的 Subscriber 的强引用,最终导致 Activity 无法被释放,产生内存泄漏。这只是问题之一,当 Observable 开始发射数据时,如果 Subscriber 中要进行 UI 处理,会直接抛出异常并退出程式,因为 UI 对象已经被销毁了。
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)
}
个人算半个美工,倾囊而出过往收藏的一些资源 and 工具:
都是些矢量图标,可以通过 #1 转换成 PNG 图片,如果是 Android 开发的话,建议使用 #2 进行便捷开发。以上。
节选并重新排版自:探寻C++最快的读取文件的方案
为确保准确性,我又换到Windows平台上测试了一下。结果如下表:
从上面可以看出几个问题
- Linux 平台上运行程序普遍比 Windows 上快。
- Windows 下 VC 编译的程序一般运行比 MINGW(MINimal Gcc for Windows)快。
- VC 对 cin 取消同步与否不敏感,前后效率相同。反过来 MINGW 则非常敏感,前后效率相差8倍
- read 本是 linux 系统函数,MINGW 可能采用了某种模拟方式,read 比 fread 更慢。
- Pascal 程序运行速度实在令人不敢恭维。
好像黑了不少东西 :) 个人意见是 大文件的话使用 fread,不考虑效率以及内存占用的话可以考虑 fstream。example:高效地读取&解析文件(fread)
用 Fragment 来实现 Presenter 我之前在这篇文章就已经提过了:#Android# 使用 Fragment 构建 Presenter - 『Android 还可以这样开发』 - 知乎专栏
这篇文章算是对之前的一些补充。
看了一下目前的一些方案,包括:(以下称 屏幕旋转/意外回收时 为 销毁时)
使用 Fragment 的好处是可以同步 Activity 的生命周期方法,你可以很简单的将你老旧的代码从 Activity 迁移到 Fragment 上。
要注意在使用 Fragment 来实现 Presenter 时,setRetainInstance(true) 仅当当前 Fragment 为非 Nested 的时候才可用(只能为 Activity 的直接子 Fragment、不能为 Fragment 的子 Fagment),否则会报错。
所以说更推荐覆盖 onSaveInstanceState() 来保存数据,它在系统需要回收内存时能腾出更多的内存空间,不像其它方案一样依旧占用内存。
无法直接保存 Presenter 实例的话,需要注意在 Presenter 中进行的一些异步操作,它们在 Presenter 被回收时依然在后台运行,并且在异步操作中可能会访问 Presenter 内的一些属性,所以你可能需要在 Presenter 被回收时中止它们。
其实我是在说 ReactiveX Java。在我的项目中,基本上大部分异步操作都使用它来实现,所以我在 kotgo 中写了个 bindLifecycle() 拓展函数来将 Observable 的订阅绑定到 Presenter 的生命周期上,一旦 Presenter 被销毁时会及时终止异步流。
其它的问题暂时没有了。每个方案都各有优劣,得看具体场景。
想尝试 Kotlin 的伙伴可以看看 Kotgo 1.4 了:GitHub - nekocode/kotgo: ? An android development framework using MVP architecture on kotlin. 它已经被用在多款产品上(甚至有已上架的大型应用),也间接证明了 Kotlin 目前的稳定性。
发现现在某些所谓的「面向小白」的大 V 真是喜欢装逼。例如在某回答里说自己读大学时刷完所有 LeetCode,然后评论中被质问就改口说只刷完 Easy 和 Medium,再后来大概虚心了,直接把这段描述给全删了……
拜托,你能把 Easy 每道不落全刷完我都不太相信好么,有些标着 Easy 的题根本就可以归到 Medium 或 Hard 里去了。
搬公司之前公司办公室在一个小区里,公司员工每天上班需要在小区后门等人开门(非业主没有门卡开门),于是我破解了小区物业 App 的接口做了个能打开后门的网页。每天后门保安看到门自动开了的表情是这样的:

后来自己下班每次要等公交,就抓了下实时公交的数据加了个查公交页面。
学壹传媒工具箱 (搬了公司,快一年没维护了)

自己用的工具的话那就更多了,我喜欢用 Py 脚本来做些繁杂的东西,例如:
还有一堆更较琐碎的就不提了,想不起来了。。
更新一下,看到 @wakao 的答案,槽点有些多,吐槽下:
==== 以下为原回答 ====
谢邀。
之前一直是用 JakeWharton/butterknife · GitHub 加上 avast/android-butterknife-zelezny · GitHub ,你确定你手写 findViewById 会比我用 Plugin 一键生成代码更快?:)
使用 ButterKnife,是因为它足够轻。
Field and method binding for Android views which uses annotation processing to generate boilerplate code for you.
通过注解,把 findViewById 代码的生成丢给编译器处理,在不降低执行效率的前提下,提高代码可读性和减少代码量,有什么理由不用?至于 AndroidAnnotations,没用过,也不推荐用,太重,看着恶心。