Sim is a fool

Hello Stranger


  • Home

  • Archives

创业码农的一些建议

Posted on 12-23-2015 | Edited on 05-05-2019

开篇先友情提示一下,此篇文章所谈论的技术点与微信无关,如有描述不准确的地方,也欢迎大家支出与讨论。
作者去年离开微信,变成一个创业码农,期间也踩过一些坑,这里与大家分享一些我个人的经验。

微信整体的氛围还是很像创业公司的,快速、高效。但微信团队对技术的挖掘还是很深的。这一点是在创业公司很难做到的。创业公司更追求快速、稳定的做出功能,完成迭代。

下面给大家介绍一点我个人觉得很大的提高了我的开发效率的工具。

App架构

RxJava

首先给大家安利ReactiveX,其中Android的核心实现为RxJava。

为了App不卡顿,我们会把所有耗时的操作(比如:网络访问、文件访问)放到Worker Thread中。但是Android本身的AsyncTask的设计个人觉得设计的十分糟糕,不但写出来的代码冗长,而且稍微复杂一些的多流操作就会写的完全无法维护(这里可以用Java本身的线程模式来实现)。而且肆意的开线程也会造成App的卡顿。这里本身最初的想法就是需要一个线程池,以Promise的方式对外提供接口。原先试用过facebook的开源方案Bolts-Android,这个库是parse的开源方案。后来有iOS的同事推荐Reactive的方案,于是就走上了Rx脑残粉的不归路。

首先Rx会大大减少你的代码量,这一点对“懒惰”的我们十分重要。
下面举2个平时开发都会遇到的问题来举例:

  1. 搜索界面

    我们需要在用户输入完毕后第一时间显示搜索结果,由于这个需要请求后台,我们又不想用户每次输入的时候都去后台请求。并且总需要显示当前最新输入内容的结果,不能因为网络的原因产生乱序的结果。

    1
    2
    3
    4
    5
    6
    7
    8
    RxTextView.textChanges(searchEditText)
    .compose(this.bindToLifecycle())
    .debounce(300, TimeUnit.MILLISECONDS)
    .switchMap(SearchService::searchFeed)
    .subscribe(
    feeds -> updateUI(),
    throwable -> RxUtil.handleError(throwable, activity)
    );

    几句简单明了的代码,满足上述的需求,而且看起来十分明了简单。其中.compose.(this.bindToLifecycle())是为了防止内存泄露,.debounce(300, TimeUnit.MILLISECONDS)是表示间隔为300毫秒,使用switchMap是会停止之前发出的请求,防止脏数据重入。
    由于Android并不支持Java 8,所以我们需要Retrolambda,来支持lambda表达式。

  2. 防止多次点击重入

    1
    2
    3
    RxView.clickEvents(button)
    .throttleFirst(300, TimeUnit.MILLISECONDS)
    .subscribe(this::onButtonClick);

MVP & MVVM

image

关于MVP&MVVM我一直是拒绝的,因为一开始的几个Screen我是用硬套MVP&MVVM的模式来做的,虽然activity的代码十分简单,但是View和ViewModel都会写一些晦涩、重复的逻辑来保证数据绑定,这不符合D.R.Y.。后来发现google官方有一个data-binding的实现,感觉实现和prism十分类似,已经在最新的迭代中开始使用data-binding来实现MVVM,具体可以参考一个第三方例子。

如何优雅的偷懒

REST Client

关于REST API是一件几乎纯体力活,这里应当使用代码生成工具来帮助我们完成繁琐的工作。如果你的App追求极致的性能和流量,这里可以使用protocal buffer。这个有一个坑,就是PB原生的生成器生成的方法数非常多,会造成Android方法数64K的问题。可以使用Square开源的wire来降低方法数。

不过笔者的APP并没有使用二进制协议,使用了更容易调试的JSON。其中我们可以定义JSON Schema来描述协议,后台与客户端都可以拿这个schema来生成自己的Model和验证协议数据。Android中可以jsonschema2pojo来生成自己的Model代码,并且可以生成Parcelable代码(PS:这一部分可能还存在隐藏BUG,如果你在使用过程中有什么问题可以提issue或者直接联系笔者)。

关于REST API还有一个杀手级的库Retrofit。Retrofit可以完美配合jackson+Rxjava来实现一个基于ReactiveX的REST Client。

1
2
3
4
5
6
@GET("/v2/feeds/search")
Observable<List<FeedDetail>> searchFeeds(
@Query("query") String query,
@Query("tag") String tag,
@Query("page") int page
);

声明十分简单明了,具体可以去retrofit的官网了解更多。

Image Loader

整体APP的架构完成后,图片库也是对于APP十分重要的。笔者刚入职的时候,就是在照着google tutorial上的图片加载的例子写过一个ImageLoader,深深感到做一个高效的图片库还是有很大难度的。还好现在各路大神给了我们很多选择,下面3款笔者认为是可以选择的option:

  1. Picasso
  2. Glide
  3. Fresco

其中Picasso和Glide的接口十分接近,但是benckmark下来Glide的性能更好一些,并且支持更多格式的图片,我们现在使用的的是Glide,而Fresco的功能是这3个库中最强大的,且支持PJPG。但是他需要替换你的View,并且接口设计的不如上述2个库。笔者在3个多月以前用Fresco的时候,他在加载多张图片的时候偶尔会有显示不出的情况,不确定现在是否修复。

ButterKnife

Jake Wharton是个非常高产的大神,诸多开源库都是他主导的(RxAndroid也是他主要在主导)。ButterKnife可以帮助你少写很多重复的code。配合IDEA的插件可以不用写很多繁琐的findviewByid的搬砖代码。

质量保证

监控数据对于App来讲也十分重要,Growth Hacker和开发都需要经常关注。笔者现在在用的有一下几款产品:

  1. fabric
  2. umeng
  3. splunk

fabric和umeng的功能有很大的重叠,fabric是twitter旗下的数据上报和分析系统,笔者这里使用了他的crash报上,做的十分强大,给App的质量提供了保证。splunk是一款服务端的log分析系统,有了他的支持客户端可以减少需要无谓的事件上报。

另外,DebugDrawer也值得推荐,可以帮你继承很多debug时的小工具,快速的在debug版本分析、诊断问题。

最佳实践

关于最佳实践当然见仁见智,不过笔者还是推荐一些比较成熟的方案android-best-practices,这个建议精读一下,里面的每一条都是别人踩过的坑总结来的,十分有价值。

虽然笔者一直是一个人默默开发,但是还是会遵守git-flow。每次多花一点点实践换来的分支的规整还是值得的。另外TJ开发的git-extras会有很多对github友好的命令,也值得推荐。

另外关于代码格式,也没有官方统一的方案,笔者这里推荐使用Square的java-code-styles,也可以自己fork做相应的修改。

另外强烈push设计的同学使用Sketch,这样不仅可以解放设计的同学在无尽的切图中,也可以让自己节约更多的时间。

总结

笔者离职一年,感觉创业和做freelancer有很多相似的地方,有大量灵活的时间,你需要学习如何去掌握你的时间,毕竟工作只是生活的一部分,你需要合理的分配时间。所以你的代码要尽可能的少些,即能自动生成的就用脚本来做,能抽象的就不重复去写,可以给自己节约更多的时间去玩耍。。。

参考文章

ReactiveX

RxJava

RxJava入门教程

android-application-architecture

android-application-architecture

Improving UX with RxJava

给 Android 开发者的 RxJava 详解

Mysql 事务

Posted on 12-01-2015 | Edited on 05-05-2019

上一次的抢购出现了库存是 -1 的问题,说明我们lock ticket的逻辑是非排他的,临界区没有被锁住。今天仔细研究了下DB的事务,发现以前的理解有一些偏差。

tl;dr

在查询库存的语句加入 for update 来让这一行加入悲观锁,其余同事在查询transcation会被hold住,有效fix读到脏库存的问题。

概念

事务的特征:原子性(Atomiocity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability),这四个特性简称ACID特性。

  • 原子性:事务是数据库的逻辑工作单位,事务中包括的所有操作要么都做,要么都不做。

  • 一致性:事务执行的结果必须是使数据库从一个一致性的状态变到另外一个一致性状态。

  • 隔离性:一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对其他事务是隔离的,并发执行的各个事务之间互相不干扰。

  • 持久性:一个事务一旦成功提交,对数据库中数据的修改就是持久性的。接下来其他的其他操作或故障不应该对其执行结果有任何影响。

原子性

首先被begin和commit(或rollback)包含的逻辑是原子的,即要不一起成功,要不一起失败。这一点我们的理解没有错误。

隔离性(Ioslation Level)

这一点是我们以前理解错的,对于我们使用的RDS的InnoDB默R认的Ioslation Level为READ COMMITED.

  1. READ UNCOMMITED
    SELECT的时候允许脏读,即SELECT会读取其他事务修改而还没有提交的数据。

  2. READ COMMITED
    SELECT的时候无法重复读,即同一个事务中两次执行同样的查询语句,若在第一次与第二次查询之间时间段,其他事务又刚好修改了其查询的数据且提交了,则两次读到的数据不一致。

  3. REPEATABLE READ
    SELECT的时候可以重复读,即同一个事务中两次执行同样的查询语句,得到的数据始终都是一致的。实现的原理是,在一个事务对数据行执行读取或写入操作时锁定了这些数据行。
    但是这种方式又引发了幻想读的问题。因为只能锁定读取或写入的行,不能阻止另一个事务插入数据,后期执行同样的查询会产生更多的结果。

  4. SERIALIZABLE
    与可重复读的唯一区别是,默认把普通的SELECT语句改成SELECT …. LOCK IN SHARE MODE。即为查询语句涉及到的数据加上共享琐,阻塞其他事务修改真实数据。serializable模式中,事务被强制为依次执行。这是SQL标准建议的默认行为。

我们原先一直认为应为SERIALIZABLE, 导致我们读到的数据是不正确的。

锁机制

  • 共享锁(乐观锁):由读表操作加上的锁,加锁后其他用户只能获取该表或行的共享锁,不能获取排它锁,也就是说只能读不能写
  • 排他锁(悲观锁):由写表操作加上的锁,加锁后其他用户不能获取该表或行的任何锁,典型是mysql事务中的

其命令分别为:共享锁(share mode), 排他锁(for update)

这里我们需要的是排他锁,即当一个操作在lock的时候,其他事务应当被hold住,而不是读到以前的旧数据。

排他锁例子

我们做一个小实现,开2个sql的terminal(分别为T1和T2).

T1 RUN:

1
2
3
4
5
mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from t1 where id='3' for update;
[result]

T2 RUN:

1
mysql> select * from t1;

这一句查询是OK的

T2 查询非锁定记录

1
select * from t1 where id=2 for update;

T2 查询锁定记录

1
select * from t1 for update;

这样会被hold住

表加锁解锁

  • LOCK TABLES tablename WRITE;
  • LOCK TABLES tablename READ;
  • UNLOCK TABLE;S

###事务所用到的表
information_schema

1
2
3
select * from innodb_trx;
select * from innodb_lock_waits;
select * from innodb_locks;

Use Promise Pattern in Android

Posted on 04-22-2014 | Edited on 05-05-2019

I’ve read deep in NodeJS today. Although I haven’t written nodeJS for a long time, but I learn a lot for the book.
Recommanded for you.

deep in NodeJS

Promise/Defered

Promise pattern is wilderly used in JQuery framework, it solved Pyramid of Doom issue. And it quit useful in Android development.
In our company project, we always wrapped a asynchronously framework for network request. some complex requirement will be implemented by request server several times, and the code will be ugly.

Promise pattern will help us to solve this proplem.I found a java framework to implement promise pattern.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Deferred deferred = new DeferredObject();
Promise promise = deferred.promise();
promise.done(new DoneCallback() {
public void onDone(Object result) {
...
}
}).fail(new FailCallback() {
public void onFail(Object rejection) {
...
}
}).progress(new ProgressCallback() {
public void onProgress(Object progress) {
...
}
}).always(new AlwaysCallback() {
public void onAlways(State state, Object result, Object rejection) {
...
}
});
1
2
3
4
5
6
DeferredManager dm = new DefaultDeferredManager();
Promise p1, p2, p3;
// initialize p1, p2, p3
dm.when(p1, p2, p3)
.done(…)
.fail(…)

It makes you source code more readable.

How does Dalvik implement Thread Priority

Posted on 01-27-2014 | Edited on 05-05-2019

Java Thread Implementation

On linux platform, jvm implement thread in native thread method, every jvm thread will map a native thread. And so on, native thread under the linux platform are implemented as processes that shared resources. The scheduler does not differentiate between a thread and a process.
All threads of all apps belong on the same thread group on the system, so every thread competes with all threads of all apps. Therefore, Thread.setPriority should actually do the same as Process.setThreadPriority.

Currently, CFS is the default scheduler for process or thread on linux platform. And also there is other schedule for real time task.

CFS Priority Scheduling

SCHED_NORMAL, SCHED_BATCH, SCHED_IDEL will be scheduled by CFS scheduler.

SCHED_RR and SCHED_FIFO will be scheduled by real time scheduler.

In Complete Fair Scheduler every task has the virtual time parameter to save the time that it had token cup time. And the system record the fair clock which is running in RealTime div TheNumberOfTask. Every task’s vtime will trbby to catch the fair clock. All task will save in RB tree with its vtime. In every sechuldeing happened, the sechudler will choose the left of RB tree which is has the most small vtime.

On the other hand, every task has seperated priority. If one task has two times priority than the other, the high priority task will get two times cup time than the other.

Android Thread Pirority Implementation

Priority

http://androidxref.com/4.2_r1/xref/dalvik/vm/os/android.cpp#48

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void os_changeThreadPriority(Thread* thread, int newPriority)	{
if (newPriority < 1 || newPriority > 10) {
ALOGW("bad priority %d", newPriority);
newPriority = 5;
}

int newNice = kNiceValues[newPriority-1];
pid_t pid = thread->systemTid;

if (newNice >= ANDROID_PRIORITY_BACKGROUND) {
set_sched_policy(dvmGetSysThreadId(), SP_BACKGROUND);
} else if (getpriority(PRIO_PROCESS, pid) >= ANDROID_PRIORITY_BACKGROUND) {
set_sched_policy(dvmGetSysThreadId(), SP_FOREGROUND);
}

if (setpriority(PRIO_PROCESS, pid, newNice) != 0) {
std::string threadName(dvmGetThreadName(thread));
ALOGI("setPriority(%d) '%s' to prio=%d(n=%d) failed: %s",
pid, threadName.c_str(), newPriority, newNice, strerror(errno));
} else {
ALOGV("setPriority(%d) to prio=%d(n=%d)", pid, newPriority, newNice);
}
}

http://androidxref.com/4.2_r1/xref/system/core/libcutils/sched_policy.c#312

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 if (sys_supports_schedgroups) {
if (add_tid_to_cgroup(tid, policy)) {
if (errno != ESRCH && errno != ENOENT)
return -errno;
}
} else {
struct sched_param param;

param.sched_priority = 0;
sched_setscheduler(tid,
(policy == SP_BACKGROUND) ?
SCHED_BATCH : SCHED_NORMAL,
&param);
}

http://lxr.linux.no/#linux+v3.13.5/kernel/sched/core.c#L3015

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/* Actually do priority change: must hold rq lock. */
static void
__setscheduler(struct rq *rq, struct task_struct *p, int policy, int prio)
{
p->policy = policy;
p->rt_priority = prio;
p->normal_prio = normal_prio(p);
/* we are holding p->pi_lock already */
p->prio = rt_mutex_getprio(p);
if (rt_prio(p->prio))
p->sched_class = &rt_sched_class;
else
p->sched_class = &fair_sched_class;
set_load_weight(p);
}

http://lxr.linux.no/#linux+v3.13.5/kernel/sched/core.c#L2462

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
static inline struct task_struct *
pick_next_task(struct rq *rq)
{
const struct sched_class *class;
struct task_struct *p;

/*
* Optimization: we know that if all tasks are in
* the fair class we can call that function directly:
*/
if (likely(rq->nr_running == rq->cfs.h_nr_running)) {
p = fair_sched_class.pick_next_task(rq);
if (likely(p))
return p;
}

for_each_class(class) {
p = class->pick_next_task(rq);
if (p)
return p;
}

BUG(); /* the idle class will always have a runnable task */
}

Demo Time

https://github.com/simpleton/android_misc/tree/master/ThreadBenchmark

References

  1. http://lwn.net/Articles/3866/
  2. http://androidxref.com/source/xref/dalvik/vm/Thread.cpp
  3. http://stackoverflow.com/questions/1662185/do-linux-jvms-actually-implement-thread-priorities
  4. http://blog.csdn.net/innost/article/details/6940136
  5. https://www.ibm.com/developerworks/cn/linux/l-cfs/
  6. http://hi.baidu.com/_kouu/item/055bd19af9f6b9dc1f4271d1
  7. http://ck.wikia.com/wiki/SchedulingPolicies

Sim Sun

4 posts
7 tags
© 2019 Sim Sun
Powered by Hexo v3.8.0
|
Theme – NexT.Mist v7.1.1