反思

最近一直在想所谓的编程能力到底是什么。
诱因是这次16年的种子杯:辛辛苦苦一个星期,居然连初赛都没有过,真的很难受,也对自己的能力产生了怀疑。
现在再回头想想题目和自己写的东西。
题目很简单,一个最简单的词法分析器加上一个稍微复杂的语法分析器,这些东西的原理我已经在各类编译原理的书上看过很多次。实现的流程和方法基本心里都有一个数,甚至在题目出的那个晚上我就知道大概应该怎么写,写些什么了。
但是,最后完成的代码很多逻辑漏洞百出,花了一晚上时间通宵重构,最后还是有很多问题。
这次是我动手实现的能力不能匹配我的理论知识,而我之前一直觉得动手写出具体的实现是我的强项。
好好的反思了一下自己,我发现自己以前写代码一直是凭着经验来写。为什么我知道怎么做?为什么我写的比其他一般同学更快?不是因为我学习的速度快,而是因为我写过的代码更多,很容易就能从以前的东西导出一些相似的部分。
这种学习模式是有问题的。在面对一个自己没有动手做过的东西的时候,要经过大量的试错才能真正上手。
以后要锻炼自己学习完全陌生的知识的能力,编程不是简单的重复Copy&Paste,面向Google编程的习惯也要改一改了,遇到未知的问题先思考再寻找解决办法。

Glide学习记录

#Glide学习记录

###1.What
Glide是一款Google官方推出的图片加载库。它主要用来加载网络图片和处理图片大小、图片内存管理等问题。它的外部接口、使用方式和Picasso非常相似。它和Picasso相比既有优点也有缺点。Picasso加载出来的图片质量更高,但是Picasso会耗费更多的内存。Glide可以加载gif动图但是Picasso不能。

这是最基本的使用方式:

1
2
3
Glide.with(context)
    .load("http://inthecheesefactory.com/uploads/source/glidepicasso/cover.jpg")
.into(ivImg);

可以看到,Glide的和Picasso一样使用链式调用的方式,整个流程看起来很清晰。
注意,这里的context和Picasso不同,它可以接受Activity或是Fragment,并从从找到相应的Context,如果这样做,同时也可以把图片加载的过程和Activity、Fragment的生命周期绑定,可以更好的控制图片。
另外一点需要注意的是两者的磁盘缓存策略不同,当图片缓存到磁盘时,Picasso保存原图,整个图像的所有像素点,而Glide只保存加载到ImageView里的图像,图像的大小和ImageView相同。
总结:在不考虑图片质量的情况下,Glide相当于Picasso的强化版本,Picasso能做的所有事情Glide都能做。

###2.How

###3.Why
首先从Glide这个类开始分析。
Glide.class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* Get the singleton.
*
* @return the singleton
*/

public static Glide get(Context context) {
if (glide == null) {
synchronized (Glide.class) {
if (glide == null) {
Context applicationContext = context.getApplicationContext();
List<GlideModule> modules = new ManifestParser(applicationContext).parse();

GlideBuilder builder = new GlideBuilder(applicationContext);
for (GlideModule module : modules) {
module.applyOptions(applicationContext, builder);
}
glide = builder.createGlide();

for (GlideModule module : modules) {
module.registerComponents(applicationContext, glide.registry);
}
}
}
}

get方法是Glide创建实例的过程,很明显,Glide采用了单例模式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
/**
* Begin a load with Glide by passing in a context.
*
* <p> Any requests started using a context will only have the application level options applied
* and will not be started or stopped based on lifecycle events. In general, loads should be
* started at the level the result will be used in. If the resource will be used in a view in a
* child fragment, the load should be started with {@link #with(android.app.Fragment)}} using that
* child fragment. Similarly, if the resource will be used in a view in the parent fragment, the
* load should be started with {@link #with(android.app.Fragment)} using the parent fragment. In
* the same vein, if the resource will be used in a view in an activity, the load should be
* started with {@link #with(android.app.Activity)}}. </p>
*
* <p> This method is appropriate for resources that will be used outside of the normal fragment
* or activity lifecycle (For example in services, or for notification thumbnails). </p>
*
* @param context Any context, will not be retained.
* @return A RequestManager for the top level application that can be used to start a load.
* @see #with(android.app.Activity)
* @see #with(android.app.Fragment)
* @see #with(android.support.v4.app.Fragment)
* @see #with(android.support.v4.app.FragmentActivity)
*/
public static RequestManager with(Context context) {
RequestManagerRetriever retriever = RequestManagerRetriever.get();
return retriever.get(context);
}

/**
* Begin a load with Glide that will be tied to the given {@link android.app.Activity}'s lifecycle
* and that uses the given {@link Activity}'s default options.
*
* @param activity The activity to use.
* @return A RequestManager for the given activity that can be used to start a load.
*/
public static RequestManager with(Activity activity) {
RequestManagerRetriever retriever = RequestManagerRetriever.get();
return retriever.get(activity);
}

/**
* Begin a load with Glide that will tied to the give
* {@link android.support.v4.app.FragmentActivity}'s lifecycle and that uses the given
* {@link android.support.v4.app.FragmentActivity}'s default options.
*
* @param activity The activity to use.
* @return A RequestManager for the given FragmentActivity that can be used to start a load.
*/
public static RequestManager with(FragmentActivity activity) {
RequestManagerRetriever retriever = RequestManagerRetriever.get();
return retriever.get(activity);
}

/**
* Begin a load with Glide that will be tied to the given {@link android.app.Fragment}'s lifecycle
* and that uses the given {@link android.app.Fragment}'s default options.
*
* @param fragment The fragment to use.
* @return A RequestManager for the given Fragment that can be used to start a load.
*/
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public static RequestManager with(android.app.Fragment fragment) {
RequestManagerRetriever retriever = RequestManagerRetriever.get();
return retriever.get(fragment);
}

/**
* Begin a load with Glide that will be tied to the given
* {@link android.support.v4.app.Fragment}'s lifecycle and that uses the given
* {@link android.support.v4.app.Fragment}'s default options.
*
* @param fragment The fragment to use.
* @return A RequestManager for the given Fragment that can be used to start a load.
*/
public static RequestManager with(Fragment fragment) {
RequestManagerRetriever retriever = RequestManagerRetriever.get();
return retriever.get(fragment);
}

接下来,是几个重载的with方法。仔细数一下,一共有5个,这5个with方法的参数分别是:Context、Activity、FragmentActivity、Fragment、V4Fragment。可以看到,Glide对FragmentActivity进行了判断,那么显然会在之后进行特殊处理,具体接下来再说。Glide的一个细节,它同时兼容Fragment和V4Fragment。
这几个方法完全类似,首先调用

RequestManagerRetriever.get()

方法拿到一个RequestManagerRetriever的实例,深入这个类可以发现这个实例也是一个单例实例。接下来调用

retriever.get(xxx)

让我们来看看这个方法具体的内部实现,首先来看参数为Context时的情况:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public RequestManager get(Context context) {
if (context == null) {
throw new IllegalArgumentException("You cannot start a load on a null Context");
} else if (Util.isOnMainThread() && !(context instanceof Application)) {
if (context instanceof FragmentActivity) {
return get((FragmentActivity) context);
} else if (context instanceof Activity) {
return get((Activity) context);
} else if (context instanceof ContextWrapper) {
return get(((ContextWrapper) context).getBaseContext());
}
}

return getApplicationManager(context);
}

可以看到,这个方法实际上是个空的调度中间方法,它没有真正的实际逻辑,检测context的种类并由相应的方法去执行,从这里可以看出两点:

  1. 如果当前线程不是主线程,那么最后只会走进getApplicationManager,也就是说,如果不在UI线程执行Gilde,那么Activity的生命周期绑定就不能实现。
  2. 实际使用时Glide.with(Context)和Glide.with(Acitivty)的逻辑其实是一样的。

接下来让我们看看当实际参数为Activity时的逻辑

1
2
3
4
5
6
7
8
9
10
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public RequestManager get(Activity activity) {
if (Util.isOnBackgroundThread() || Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
return get(activity.getApplicationContext());
} else {
assertNotDestroyed(activity);
android.app.FragmentManager fm = activity.getFragmentManager();
return fragmentGet(activity, fm, null);
}
}

可以看到,这里和刚才一样,做了一个是否在主线程的检查。接下来调用assertNotDestroyed方法,之后调用fragmentGet并返回一个RequestManager。

1
2
3
4
5
6
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
private static void assertNotDestroyed(Activity activity) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 && activity.isDestroyed()) {
throw new IllegalArgumentException("You cannot start a load for a destroyed activity");
}
}

可以看到这个方法正如其名,只是做了一个简单的检查,判断这个Activity是否已经被销毁,这里我们可以看到Glide不能在Activity中的OnDestroy中调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
RequestManager fragmentGet(Context context, android.app.FragmentManager fm,
android.app.Fragment parentHint) {

RequestManagerFragment current = getRequestManagerFragment(fm, parentHint);
RequestManager requestManager = current.getRequestManager();
if (requestManager == null) {
// TODO(b/27524013): Factor out this Glide.get() call.
Glide glide = Glide.get(context);
requestManager =
new RequestManager(glide, current.getLifecycle(), current.getRequestManagerTreeNode());
current.setRequestManager(requestManager);
}
return requestManager;
}

这里是得到RequestManager的具体实现,可以看到要得到RequestManager首先要创建一个RequestManagerFragment,然后去调用requestManagerFragment的getRequestManager方法。那么现在我们的问题就是怎么创建RequestManagerFragment,以及这个东西是做什么的。
那么接下来深入getRequestManagerFragment方法来了解怎么样得到它。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
RequestManagerFragment getRequestManagerFragment(
final android.app.FragmentManager fm, android.app.Fragment parentHint) {

RequestManagerFragment current = (RequestManagerFragment) fm.findFragmentByTag(FRAGMENT_TAG);
if (current == null) {
current = pendingRequestManagerFragments.get(fm);
if (current == null) {
current = new RequestManagerFragment();
current.setParentFragmentHint(parentHint);
pendingRequestManagerFragments.put(fm, current);
fm.beginTransaction().add(current, FRAGMENT_TAG).commitAllowingStateLoss();
handler.obtainMessage(ID_REMOVE_FRAGMENT_MANAGER, fm).sendToTarget();
}
}
return current;
}

首先调用findFragmentByTag,因为这个时候走的是一个Acitvity,所以显然这里拿不到framgnet,那么走到current==null的情况。因为是第一次执行,因此在pendingRequestManagerFragments里也拿不到fragment,注意pendingRequestManagerFragments是一个简单的map。接下来可以看到直接new出了一个RequestManagerFragment,并把它放进了pendingRequestManagerFragments里缓存留至以后再取用。可以看到这个Fragment通过Acitivity的FragmentManager被添加进了Activity里。
那么到了这里,怎么取得RequestManagerFragment这个问题已经解决了。
下面让我们来看看这个Fragment到底有什么用,它和RequestManager有什么关系。

1
2
3
4
5
* A view-less {@link android.support.v4.app.Fragment} used to safely store an {@link
* com.bumptech.glide.RequestManager} that can be used to start, stop and manage Glide requests
* started for targets within the fragment or activity this fragment is a child of.
*
public class SupportRequestManagerFragment extends Fragment

来看一看这个类的注释。RequestManagerFragment是一个没有view的空白Fragment,Glide通过它来安全的储存RequetManager,并且可以用它来实现request和声明周期的绑定。这个Fragment作为目标Activity或者Fragment的子Fragment来实现对声明周期的绑定!
Nice,到了这里我们已经发现了Glide一个重要特性:“将图片加载和UI的生命周期绑定”的实现方法。
那么具体是如何做的呢?通过观察源码可以发现

private final ActivityFragmentLifecycle lifecycle;

这样一个成员变量,这个东西其实就是一个listener的控制器,它把所有监听生命周期的listener封装在一个set里,然后在fragment生命周期发生变化时去回调它。代码比较简单,我就不贴了。
你可能会问,为什么能通过这样一个child fragment绑定activity或fragment的生命周期呢?
让我们来看一看Google对Fragment生命周期的描述:

与 Activity 生命周期协调一致 片段所在的 Activity 的生命周期会影响片段的生命周期,其表现为,Activity 的每次生命周期回调都会引发每个片段的类似回调。 例如,当 Activity 收到 onPause() 时,Activity
中的每个片段也会收到 onPause()。

不过,片段还有几个额外的生命周期回调,用于处理与 Activity 的唯一交互,以执行构建和销毁片段 UI 等操作。这些额外的回调方法是:

onAttach() 在片段已与 Activity 关联时调用(Activity 传递到此方法内)。 onCreateView()
调用它可创建与片段关联的视图层次结构。 onActivityCreated() 在 Activity 的 onCreate()
方法已返回时调用。 onDestroyView() 在删除与片段关联的视图层次结构时调用。 onDetach() 在取消片段与
Activity 的关联时调用。 图 3 图示说明了受其宿主 Activity 影响的片段生命周期流。在该图中,您可以看到 Activity
的每个连续状态如何决定片段可以收到的回调方法。 例如,当 Activity 收到其 onCreate() 回调时,Activity
中的片段只会收到 onActivityCreated() 回调。

一旦 Activity 达到恢复状态,您就可以意向 Activity 添加片段和删除其中的片段。 因此,只有当 Activity
处于恢复状态时,片段的生命周期才能独立变化。

不过,当 Activity 离开恢复状态时,片段会在 Activity 的推动下再次经历其生命周期。

现在你明白了吧?

解决了刚才的问题,现在回头来继续看RequestManager的部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
RequestManager fragmentGet(Context context, android.app.FragmentManager fm,
android.app.Fragment parentHint) {

RequestManagerFragment current = getRequestManagerFragment(fm, parentHint);
RequestManager requestManager = current.getRequestManager();
if (requestManager == null) {
// TODO(b/27524013): Factor out this Glide.get() call.
Glide glide = Glide.get(context);
requestManager =
new RequestManager(glide, current.getLifecycle(), current.getRequestManagerTreeNode());
current.setRequestManager(requestManager);
}
return requestManager;
}

可以看到,RequestManager采用了和RequestManagerFragment相同的创建策略,第一次创建新对象,然后缓存,以后调用时直接取用。

到了这一步,RequestManager的创建过程搞清楚了。Glide.with(context)方法就没有问题了,我们仅就Activity的情况进行了分析,但是可以推测,其它情况下的执行流程也是类似的。注意,如果传过去的是一个application的实例,是不能和声明周期绑定的,这是因为Application和Activity与Fragment不同,不能创建child fragment,因此刚才说的那一套方法在Application上不能生效。

Glide:
https://github.com/bumptech/glide
Picasso:
https://github.com/square/picasso
参考:
http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0327/2650.html

2016/10/22 yyz Share

找实习
要不要实习
实习去/不能去什么公司

实习要去大公司
实习转正拿到Offer的薪水很低
实习应当避免理想公司

防范辣鸡邮件短信电话
投简历时注意使用
临时受害号
临时邮箱

拿到offer时

薪资
15k 16
18.5k
13
基本工资是13个月 多出来的要看绩效,不能稳定
绩效工资不能认准
一般大公司为14.5左右
考虑薪资时一定要想到绩效的影响
还要想到税收
税后 = 收入 - 社会保险 - 公积金 - 个人所得税
越少越好 越多越好
五险一金,社保和公积金每个地区公司系数不同,要算清楚。
公积金很重要,公司可以选择交或者不交。
收入考虑税后,可能会出现工资低但是最后到手收入更高的情况。

发展前景
城市 看个人

拿到offer后

劝说
诋毁
画饼
承诺无效

签三方协议

三方协议关系到学校的绩效,三方协议是政府考察学校的指标。
公司通过三方协议降低毁约率。
三方协议只对学生有坏处,它对学校和公司都只有好处。
公司和学校通过三方约束学生,限制学生就业。
可以以升学为理由主动毁三方,无需承担后果。
违约金不能超过月薪。

简历是面试的敲门砖和目录。
只要简历能够通过第一轮审核,那么它的主要作用就是提供给

编写ShellScript

1.执行数学运算
(1)Linux早期运算命令:expr命令
使用方法: expr 1 + 6
这种方法非常的麻烦,任何一个简单的计算都需要依赖于expr命令才能实现。
(2)bash的简化方法:使用方括号\$[opration]
使用方法: var=$[1+5]
注:以上两种方法均只支持整数运算,shell里的浮点数运算需要使用特殊的命令。
(3)计算机浮点数:bc命令
bc是bash内的一个计算器程序。浮点计算用过bc内一个叫做scale的变量控制,scale决定了浮点计算小数点后的位数,因此使用时一定要注意先对scale赋值。因为bc是一条命令,因此可以用var=`bc\ 2.2+3.3`的方法来得到bc的值并对变量赋值,同样也可以采用管道的方法。但是注意使用bc时一定要记得确定scale的值。

2.退出脚本
(1)查看退出状态码:echo \$?
与其它编程语言中的main函数的返回值类似,shell中的每一个命令在结束时都有一个退出状态码来表明它的执行状况。Linux提供\$?变量来记录上一次命令的退出状态码供使用者查看。
注:一个正常结束的命令的返回值默认是0。其它更多的默认状态码请查看Linux退出状态码表。
(2)退出脚本:exit var
执行exit命令时会退出当前脚本并返回退出状态码var。
注:退出状态码的范围为0~255。

编译原理实验

语法分析&词法分析

Task 1
识别词法错误并输出错误信息
要求:
识别八进制&十六进制 不规范报错
识别指数形式浮点数 不规范报错
识别 “//“ “/ /“注释 并过滤

Task2
识别语法错误并输出错误信息

flex使用命令:
(1)flex src.l 对给定的flex文件生成对应的c文件
注:flex对文件的space非常敏感,不要随便在代码里加空格,否则非常可能不能正常输出。
(2)gcc lex.yy.c -ll -o main 编译c文件生成可执行文件,mac下使用-ll
注:因为OSX下的gcc实际上是clang,所以命令和linux下有出入。
在mac下man gcc会提示找不到gcc,因为实际这个gcc是clang
(3)./main srcFile 把源文件传递给语法分析生成器

bison使用命令:
(1)flex lexical.l 语法分析依赖于词法分析的输出,因此首先要得到词法分析器。
(2)bison -d syntax.y 生成语法分析文件,-d参数是令其生成对应的.c文件和.h文件。
注:在bison源文件syntax.y中要在引用词法分析器文件,#include “lex.yy.c”
(3)为了能够使用在bison文件中定义的语法符号,在flex生成的词法分析器文件中也需要引用bison生成的文件,#include “syntax.tab.h”
(4)完成依赖关系的配置,编译链接词法分析器文件和语法分析文件,输入gcc -ll syntax.tab.c lex.yy.c -o scanner得到语法分析器。
(5)./scanner 根据代码输入相应的内容。

正则表达式复习:
(1)”.”表示通配符,匹配除了“\n”以外的任何一个字符
(2)”[]”表示字符集,即括号内一个字符匹配到则就算匹配到这个字符集。缩写表示法[0-9a-zA-Z]。”^”用在方括号内表示补集。
(3)”^”用在方括号外会匹配一行的开头。符号”$”用于匹配一行的结尾。“<>”匹配文件的结尾。
(4)符号”{}”多重含义。A{5}表示AAAAA。A{1,3}表示A、AA或者AAA,即1到3个A。此外,定义的名字要在花括号内,如{letter}。
(5)”*“表示闭包操作,匹配零个或多个表达式,如X*表示零个或多个X。
(6)“+”表示正闭包操作,匹配一个或多个表达式,用法同上。
(7)“?”表示零个或者一个表达式,即有或者无。
注:”?”、”*”、”+”都仅仅只对左边临近的那个表达式起效。如abc?表示ab和零个或者多个c
(8)”|”表示选择操作,如a|b|c表示a或者b或者c。
(9)”\”表示转义字符,与和C语言类似。
(10)“””表示忽略转义,如“\n”代表字符串”\n”而不是换行符
(11)”/“表示查看输入字符的上下文,如0/1匹配01串中的0而不匹配02中的0。
(12)其余剩下的所有字符都表示其本身。

正则优先级
正则在线测试工具

okhttp源码分析

在前一段时间已经学习了Retrofit和Okhttp的基本用法,今天我就要开始第三部分Why的学习。第一次认真的阅读一个第三方开源库的内部代码,深入理解okttp和Retroft的实现过程,由于Retrofit的网络通讯的实现全部基于okhttp,因此首先我将开始对okhttp源码的学习。

按照惯例,我分三部分开始对okhttp的解析。

###1.What?
okhttp是什么?它有什么用?我直接引用官方的介绍如下

Overview

HTTP is the way modern applications network. It’s how we exchange data & media. Doing HTTP efficiently makes your stuff load faster and saves bandwidth.
OkHttp is an HTTP client that’s efficient by default:

HTTP/2 support allows all requests to the same host to share a socket.
Connection pooling reduces request latency (if HTTP/2 isn’t available).
Transparent GZIP shrinks download sizes.
Response caching avoids the network completely for repeat requests.
OkHttp perseveres when the network is troublesome: it will silently recover from common connection problems. If your service has multiple IP addresses OkHttp will attempt alternate addresses if the first connect fails. This is necessary for IPv4+IPv6 and for services hosted in redundant data centers. OkHttp initiates new connections with modern TLS features (SNI, ALPN), and falls back to TLS 1.0 if the handshake fails.

Using OkHttp is easy. Its request/response API is designed with fluent builders and immutability. It supports both synchronous blocking calls and async calls with callbacks.

OkHttp supports Android 2.3 and above. For Java, the minimum requirement is 1.7.

根据官方介绍:okhttp是一个应用网络库,它可以帮助我们用更少的带宽和更快的速度去交换数据,有用的使用HTTP。
它默认具有以下功能:

  • 允许我们发向同一个服务器的请求分享socket
  • 使用链接池的设计来减少请求的延迟
  • 使用GZIP压缩方式减少从服务端下载的数据量
  • 对服务端的回应使用cache避免多次发送完全相同的请求
    okhttp使用重连机制,当网络出现问题时,它会静默的恢复,并且当你连接的服务有多个IP地址时,它会在第一个连接失败后调用第二个去尝试。
    okhttp同时支持同步调用和异步调用。

###2&3.How&Why?

首先从我们的使用过程开始一步一步深入到内部的实现。
我们先来看每次使用okhttp的最简单流程。

获取一个URL的内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//创建一个okhttpclient对象
OkHttpClient mOkHttpClient = new OkHttpClient();
//创建一个Request
final Request request = new Request.Builder()
.url("www.baidu.com")
.build();
//创建一个新的call
Call call = mOkHttpClient.newCall(request);
//令call进入队列等待执行
call.enqueue(new Callback()
{
@Override
public void onFailure(Request request, IOException e)
{
}

@Override
public void onResponse(final Response response) throws IOException
{

}
});
```
这是一个典型的最简单的使用方式。

来简单分析一下它的使用过程。
1.创建一个OkhttpClient对象,这是使用okhttp所有功能的第一步。
2.创建一个Request对象,这个过程使用了建造者模式,这个模式的优点是简化繁琐的初始化过程,使对象的创建与内部的实现分离,让客户端用户可以更精细的根据自己的需求创造对应的对象。我们可以看到,使用了建造者模式,request的创建逻辑变得非常清晰,在这里例子中我们只设置了url,但根据需求我们可以创建复杂得多的request。
3.调用client的方法将request封装成一个call,在okhttp中request并不能被直接执行,需要对request进行封装,在最后执行call
4.执行封装好的call。查看源码可以看到okhttp为我们提供了两种执行方式,execute()和enqueue(),这两种方式的区别从名字上就能看出来大概,稍后我详细分析。注意,查看源码的注释可以看到

/**

  • A call is a request that has been prepared for execution. A call can be
  • canceled. As this object represents a single request/response pair (stream),
  • it cannot be executed twice.
    */
1
2
3
4
这里明确的指出了一个call对象只能被执行一次,所以如果想要多次执行相同的request,我们还需要另想办法。

好了,下面我们开始从源码入手,分析一个request从创建到被执行的所有过程。
我们每一次使用都要创建OkhttpClient。

/**

  • Configures and creates HTTP connections. Most applications can use a single
  • OkHttpClient for all of their HTTP requests - benefiting from a shared
  • response cache, thread pool, connection re-use, etc.
    *
  • Instances of OkHttpClient are intended to be fully configured before they’re

  • shared - once shared they should be treated as immutable and can safely be used
  • to concurrently open new connections. If required, threads can call
  • {@link #clone()} to make a shallow copy of the OkHttpClient that can be
  • safely modified with further configuration changes.
    */

    1
    2
    3
    这是官方对于OkHttpClient的注释,我们可以看到OkHttpClient是用于配置我们的连接需求所设计出来的类,当OkHttpClient实例化后它应当是不可变的且可安全的同时开启多个连接,如果有修改OkHttpClient实例的需求,那么可以使用clone()来获取一份备份来进行安全的修改。

    这是被我们调用的构造函数。

    public OkHttpClient() {
    routeDatabase = new RouteDatabase();
    dispatcher = new Dispatcher();
    }

1
2

可以看到,每一个OkHttpClient实例都内嵌着一个routeDatabase实例和一个dispatcher实例,深入这两个类的内部,我们可以了解这两个对象的作用。

/**

  • A blacklist of failed routes to avoid when creating a new connection to a
  • target address. This is used so that OkHttp can learn from its mistakes: if
  • there was a failure attempting to connect to a specific IP address or proxy
  • server, that failure is remembered and alternate routes are preferred.
    */
    1
    可以看到,RouteDatabase类正如它的名字一样,是一个路线数据库,它负责OkHttpClient发起的失败的Connection,将它加入黑名单,我们在之前提到的多IP重连就是通过这样的记录的方式来实现。

public final class RouteDatabase {
private final Set failedRoutes = new LinkedHashSet<>();
}

1
2
3
4
RouteDatabase内含一个LinkedHashSet来记录所有的失败连接,这个类的实现到这里已经很清楚了。


接下来我们来看看Dispatcher类的内部实现。

/**

  • Policy on when async requests are executed.
    *
  • Each dispatcher uses an {@link ExecutorService} to run calls internally. If you

  • supply your own executor, it should be able to run {@linkplain #getMaxRequests the
  • configured maximum} number of calls concurrently.
    */
    1
    2
    顾名思义,Dispatcher是一个调度器,当我们的OkHttpClient执行异步任务时,Dispatcher负责控制各个线程间的关系。
    看到ExecutorService时就应该知道了,这个Dispatcher的内部实现应该就是线程池,并且okhttp还支持我们自定义executor,自己来控制各个异步任务。

public final class Dispatcher {
private int maxRequests = 64;
private int maxRequestsPerHost = 5;

/** Ready calls in the order they'll be run. */

private final Deque readyCalls = new ArrayDeque<>();

/* Running calls. Includes canceled calls that haven’t finished yet. /
private final Deque runningCalls = new ArrayDeque<>();

/* In-flight synchronous calls. Includes canceled calls that haven’t finished yet. /
private final Deque executedCalls = new ArrayDeque<>();
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
这是Dispatcher内部的一段代码,我们可以看到在默认的情况下okhttp最多执行的任务是64个,每个服务端是5个。
Dispatcher内部使用Deque这种双端队列管理AsyncCall,即异步Call,对Dispatcher的分析到这里结束。

简单的看了一下OkHttpClient的内部实现,下面我们进入Request,分析一下被我们创建的Request。

```
private final HttpUrl url;
private final String method;
private final Headers headers;
private final RequestBody body;
private final Object tag;

private volatile URL javaNetUrl; // Lazily initialized.
private volatile URI javaNetUri; // Lazily initialized.
private volatile CacheControl cacheControl; // Lazily initialized.

private Request(Builder builder) {
this.url = builder.url;
this.method = builder.method;
this.headers = builder.headers.build();
this.body = builder.body;
this.tag = builder.tag != null ? builder.tag : this;
}

进入Request内部,我们发现这里并没有什么实际的逻辑,Request类本身只是我们的任务的抽象集合,里面包含了大量的成员变量才是真正有效的逻辑实现体。下面我们去查看Call这个类,去观察一个任务被执行的过程。

1
2
3
4
5
/**
* A call is a request that has been prepared for execution. A call can be
* canceled. As this object represents a single request/response pair (stream),
* it cannot be executed twice.
*/

通过注释我们可以知道,一个Call就是一个可以被执行的Request,Call可以被取消执行,因为一个Call代表一个Request和Response对,所以一个call不能被执行两次。
创建Call的构造函数

1
2
3
4
5
6
protected Call(OkHttpClient client, Request originalRequest) {
// Copy the client. Otherwise changes (socket factory, redirect policy,
// etc.) may incorrectly be reflected in the request when it is executed.
this.client = client.copyWithDefaults();
this.originalRequest = originalRequest;
}

注意到这个构造函数是protected的,因此我们并不能手动new出一个call,这符合逻辑,我们每次想要创建一个call都要通过OkHttpClient的newCall()方法,这个构造函数在这里被调用。

1
2
3
4
5
6
/**
* Prepares the {@code request} to be executed at some point in the future.
*/
public Call newCall(Request request) {
return new Call(this, request);
}

接下来来观察Call的execute()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public Response execute() throws IOException {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
try {
client.getDispatcher().executed(this);
Response result = getResponseWithInterceptorChain(false);
if (result == null) throw new IOException("Canceled");
return result;
} finally {
client.getDispatcher().finished(this);
}
}

到这里Call的执行流程已经很清楚了,一个Call要被执行首先要被创建这个Call的OkHttpClient里的Dispatcher调用加入队列等待,下面我们深入Dispatcher的executed的内部阅读其实现。

1
2
3
synchronized void executed(Call call) {
executedCalls.add(call);
}

这个方法里只有一行代码,看来还要继续向下找它的真正实现,注意,这里的executedCalls就是我们前面提到的Deque,它实际上是一个队列。这个抽象接口的实现是ArrayDeque,来看看add方法。

1
2
3
4
 public boolean add(E e) {
addLast(e);
return true;
}

显然,这个add方法把我们要执行的Call加入到了队列的队尾排队,让这个request等待执行。
那么队列里的Call什么时候才会被真正执行呢?下面返回Call()的execute()方法,我看可以看到在finish和executed之间的getResponseWithInterceptorChain方法,看来这里就是实际执行部分,进入观察。

1
2
3
4
5
6
7
8
9
10
@Override public Response proceed(Request request) throws IOException {
if (index < client.interceptors().size()) {
// There's another interceptor in the chain. Call that.
Interceptor.Chain chain = new ApplicationInterceptorChain(index + 1, request, forWebSocket);
return client.interceptors().get(index).intercept(chain);
} else {
// No more interceptors. Do HTTP.
return getResponse(request, forWebSocket);
}
}

getResponse就是实际执行的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
/**
* Performs the request and returns the response. May return null if this
* call was canceled.
*/

Response getResponse(Request request, boolean forWebSocket) throws IOException {
// Copy body metadata to the appropriate request headers.
RequestBody body = request.body();
if (body != null) {
Request.Builder requestBuilder = request.newBuilder();

MediaType contentType = body.contentType();
if (contentType != null) {
requestBuilder.header("Content-Type", contentType.toString());
}

long contentLength = body.contentLength();
if (contentLength != -1) {
requestBuilder.header("Content-Length", Long.toString(contentLength));
requestBuilder.removeHeader("Transfer-Encoding");
} else {
requestBuilder.header("Transfer-Encoding", "chunked");
requestBuilder.removeHeader("Content-Length");
}

request = requestBuilder.build();
}

// Create the initial HTTP engine. Retries and redirects need new engine for each attempt.
engine = new HttpEngine(client, request, false, false, forWebSocket, null, null, null, null);

int followUpCount = 0;
while (true) {
if (canceled) {
engine.releaseConnection();
throw new IOException("Canceled");
}

try {
engine.sendRequest();
engine.readResponse();
} catch (RequestException e) {
// The attempt to interpret the request failed. Give up.
throw e.getCause();
} catch (RouteException e) {
// The attempt to connect via a route failed. The request will not have been sent.
HttpEngine retryEngine = engine.recover(e);
if (retryEngine != null) {
engine = retryEngine;
continue;
}
// Give up; recovery is not possible.
throw e.getLastConnectException();
} catch (IOException e) {
// An attempt to communicate with a server failed. The request may have been sent.
HttpEngine retryEngine = engine.recover(e, null);
if (retryEngine != null) {
engine = retryEngine;
continue;
}

// Give up; recovery is not possible.
throw e;
}

Response response = engine.getResponse();
Request followUp = engine.followUpRequest();

if (followUp == null) {
if (!forWebSocket) {
engine.releaseConnection();
}
return response;
}

if (++followUpCount > MAX_FOLLOW_UPS) {
throw new ProtocolException("Too many follow-up requests: " + followUpCount);
}

if (!engine.sameConnection(followUp.httpUrl())) {
engine.releaseConnection();
}

Connection connection = engine.close();
request = followUp;
engine = new HttpEngine(client, request, false, false, forWebSocket, connection, null, null,
response);

}
}

这一部分代码内容非常多,但是我们可以粗略的分为两部分
1.重建request,对其做一些修正,不深入探讨。
2.创建HttpEngine,在这里发送请求,取得Response。engine里是网络通讯的具体业务逻辑,GZIP压缩也在这里面发挥作用,这部分逻辑非常复杂。
到这里一个同步的request调用就分析完了,下面我们再来看看一部的request调用。

1
2
3
4
5
6
7
8

void enqueue(Callback responseCallback, boolean forWebSocket) {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
client.getDispatcher().enqueue(new AsyncCall(responseCallback, forWebSocket));
}

与同步调用相同,这里还是调用Dispatcher让Request进入队列等待执行。

1
2
3
4
5
6
7
public synchronized ExecutorService getExecutorService() {
if (executorService == null) {
executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));
}
return executorService;
}

这里发现了client里excutorService的实现类,原来它就是ThreadPoolExecutor。
我截取ThreadPoolExecutor的execute方法的注释

1
* Executes the given task sometime in the future.  The task
* may execute in a new thread or in an existing pooled thread.
*
* If the task cannot be submitted for execution, either because this
* executor has been shutdown or because its capacity has been reached,
* the task is handled by the current {@code RejectedExecutionHandler}.

public void execute(Runnable command) {
}

execute接收一个Runnable接口,它真正执行的实际上是Runnable的逻辑,而AyncCall正是一个实现了Runnable的Call的内部类,从而得知执行的就是AyncCall的逻辑。也就是AyncCall的execute方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Override protected void execute() {
boolean signalledCallback = false;
try {
Response response = getResponseWithInterceptorChain(forWebSocket);
if (canceled) {
signalledCallback = true;
responseCallback.onFailure(originalRequest, new IOException("Canceled"));
} else {
signalledCallback = true;
responseCallback.onResponse(response);
}
} catch (IOException e) {
if (signalledCallback) {
// Do not signal the callback twice!
logger.log(Level.INFO, "Callback failure for " + toLoggableString(), e);
} else {
responseCallback.onFailure(engine.getRequest(), e);
}
} finally {
client.getDispatcher().finished(this);
}
}

到了这里,又看到了熟悉的代码。

1
Response response = getResponseWithInterceptorChain(forWebSocket);

以后的逻辑和同步调用完全相同。
我们梳理整个过程。
Alt text
本次到此结束,更多细节以后再进行学习。

Retrofit学习记录

最近花了点时间了解学习了一下Retrofit库,现在记录一下。

一.What  Retrofit是什么?有什么用?

Retrofit是一个Android下非常流行的网络请求库。
按照官方的介绍就是

A type-safe REST client for Android and Java

注意,Retrofit是一个基于==REST==的客户端请求库。
在学习Retrofit之前我们需要了解REST的概念,这也是使用这个库最大的难点,我大部分时间也都花在这个部分上。

那么,现在我们来介绍REST这个概念。
REST全称为Representation State Transfer,翻译过来就是表征状态转移。
它是一种特定的互联网架构,符合这种架构的软件,我们称它为==RESTful==
光看名字非常的抽象,但是我理解REST这个概念就是通过一个博客上对这个名字的解析。下面用我的语言重新来介绍一遍上面几个名词。

Representation

这个词是最不好理解的,要弄懂这个词的意思,首先我们要弄明白Resource的概念。
Resource,即资源,我们现在使用互联网其实就是在访问互联网上的一个个资源并组织使用它们。不管是客户端还是直接通过浏览器,我们要上网需要做的事情是什么?通过网络获取特定的信息并显示为符合我们要求的形式给我们阅读。
在这个过程中,首先通过URI(Uniform Resource Identifier)即唯一定位这个资源的标识符来访问这个资源,这个资源就是在服务器中存储的信息,它可以是图片、视频、任何东西,总之就是一些被保存在硬盘里的文件,我们在访问了它们之后,服务器按照规定的格式将这个文件返回给访问者,这样就完成了资源的调用。
那么Representation在这个过程中是什么样的一个概念呢?
在我们使用URI的过程中,我们并不指定这个资源的格式,因为URI仅仅代表这个文件的访问路径,而这个路径不应该包含这个资源它本身的格式和内容,因此Representation(表征)即指这个资源的表示形式。这种形式可以是JPG、JSON、XML、HTML、AVI。

State Transfer

通过上面的阅读,我们知道了在访问过程中我们使用URI仅仅只是得到了这个资源,那么将这个资源转换成Represtation的这个过程即为State Transfer(状态转换)。这个转换过程发生在服务器上,我们在客户端通过HTTP协议向服务器发送请求去控制这个过程,要求服务器返回我们希望的内容。

综上所诉

我直接引用我学习的博客中的总结,我认为非常的精炼。

综合上面的解释,我们总结一下什么是RESTful架构:
  (1)每一个URI代表一种资源;
  (2)客户端和服务器之间,传递这种资源的某种表现层;
  (3)客户端通过四个HTTP动词,对服务器端资源进行操作,实现”表现层状态转化”。

二.How  Retrofit的使用

理解了REST的概念,接下来就可以学习Retrofit的具体使用了。
使用步骤

1.创建一个Retrofit实例

1
2
3
4
5
public static final String BASE_URL = "http://api.myservice.com/";
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build();

可以看到,根据REST架构,首先我们确定我们所访问的Api的资源基本URI,这个URI是这一批接口的公共前缀。
我们要使用的具体的每一个Api都是BASE_URL/xxx/xxx这种形式。
接下来创建一个Retrofit实例,将这个BaseURL传入,并指定Retrofit解析这个资源使用的解析库,具体使用哪一个需要具体分析,这里不再叙述。

有如下的解析库可以选择:
Converter Library
Gson com.squareup.retrofit:converter-gson:2.0.0-beta2
Jackson com.squareup.retrofit:converter-jackson:2.0.0-beta1
Moshi com.squareup.retrofit:converter-moshi:2.0.0-beta1
Protobuf com.squareup.retrofit:converter-protobuf:2.0.0-beta1
Wire com.squareup.retrofit:converter-wire:2.0.0-beta1
Simple XML com.squareup.retrofit:converter-simplexml:2.0.0-beta1

这样,我们就创建了一个符合我们要求的retrofit实例了。

2.根据我们使用接口返回数据的格式创建对应的实体类

这里我推荐使用一款Android Studio插件GsonFormat,根据字符串一键生成对应的实体类。

3.创建对应的接口

1
2
3
4
5
6
7
public interface ApiInterface{


@GET("users/{username}")
Call<User> getUser(@Path("username") String username);

}

定义一个Retrofit接口一共需要指定两样东西。
(1)指定HTTP的动作
我们可以选择的参数有四个,分别为:
GET
POST
PUT
DELETE
不清楚这几个动作在HTTP中的具体的含义的同学可以自行百度再仔细研究。
这里我推荐一篇文章:
http://286.iteye.com/blog/1420713
在后面的参数为BASE_URL后另加入的地址,从而指定我们要调用的Api的URL。
这里有一个坑!
BaseURL和@GET后的附加的URL的问题。
我使用的retrofit2.0在BaseURL后的URL必须以/结尾!!
也就是说

1
2
http://api.myservice.com/
http://api.myservice.com

是不同的!这个/你不能接在@GET中的URL的附加中,retrofit对这一点有绝对的要求,如果你像后面一样,你就会成功接收到返回的数据,但是狂报404..请记住这点
(2)创建在Api调用完成后被回调的函数。
在Retrofit2.0之前这个回调函数是可以有两种定义模式的,分别为同步与异步,但是在2.0删除了同步的写法,目前只有一种异步的定义方式。

1
Call<User> getUser(@Path("username") String username);

Call的泛型中的类型即为完成调用后解析得到的实体类的类型。
在方法中同时可以使用特殊的注解。
这里有几种特殊的注解参数可以选择

@Path
使用@Path注解可以将URL中的变量替换为注定的值。
@Query
使用@Query注解可以提供查询参数,这个注解结合POST使用。
@Body
使用@Body注解可以将指定类型的对象直接发送到服务器。
剩下的注解可以在使用时再具体查询,我这里对我认为不太重要的注解不再介绍。
(3)执行我们创建的call。
如果想要在当前线程执行call。

1
call.execute();

一行代码即可。
如果想要在另外一个新线程中执行enqueue

1
call.enqueue()

使用令call入队等待执行。
注意每一个Call仅能执行一次,如果想要再次执行这个Call,就必须再创建一个新的Call,官方当然给了我们解决方法。使用call.clone()即可复制一个完全一样的Call。

下面附上我项目中使用的一个完整的实例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(ApiConfig.ZHIHU_BASE)
.addConverterFactory(GsonConverterFactory.create())
.build();


StartImageInterface startImageInterface= retrofit.create(StartImageInterface.class);

retrofit.Call<StartGsonBean> beanCall = startImageInterface.startImage();

beanCall.enqueue(new retrofit.Callback<StartGsonBean>() {
@Override
public void onResponse(retrofit.Response<StartGsonBean> response, Retrofit retrofit) {
Log.d("TAG",response.body().getImg());
}

@Override
public void onFailure(Throwable t) {
t.printStackTrace();
}
});

1
2
3
4
public interface StartImageInterface {
@GET(ApiConfig.START_IMAGE)
Call<StartGsonBean> startImage();
}

好了,Retrofit使用的基本介绍到这里就结束了。

##三.Why Retrofit实现的原理
这一块内容还是等我水平足够再仔细研究。。

##四.其它
如果要学习Retrofit,请在官方处下载当前最新的2.0,而且注意,2.0与之前的版本有较大差异,如果要查询相关的问题,请记得注意Retrofit的版本问题,有不少研究Retrofit的文章已经过时了。

使用Retrofit+Okhttp,完美解决网络调用问题。
注意,Retrofit只有在Api符合REST规范,即它是RESTful时,我们使用才足够的方便。如果碰上一些个别的奇葩Api,这个时候我们可以使用Okhttp来解决问题,Okhttp是Squre的另一个网络通讯库,而且Retrofit的底层也正是基于Okhttp实现的,可以说,Retrofit就是以Okhttp为骨架,封装了一些另外的功能,从而方便我们调用RESTful的专用库。因此Okhttp的用法与Retrofit几乎相同,我们使用时也不用考虑什么兼容问题,放心大胆的使用就可以了!

Retrofit官方网站

Retrofit

OkHttp官方网站
OkHttp
参考博客
Retrofit 2.0:有史以来最大的改进
理解RESTful架构

rxJava详解

这个周六一整天都在学习rxJava,又涨了不少姿势。

一.What? rxJava是什么,它有什么用,有什么优点

rxJava是一套函数响应式Java编程框架。整套结构基于观察者模式,它能够解决我们头疼的异步问题,结合rxAndroid可以完全抛弃Android原生的AsyncTask。其链式调用方式让整个思维过程变得清晰流畅。

这里着重介绍rxJava的优点:
1.一套完整的异步解决方案
与AsyncTask相比更加优雅,而且支持的功能更加强大。更重要的是,由于Acitivty生命周期的影响,AsyncTask存在极大的隐患,我们在使用AsyncTask时通常以内部类的形式嵌入Activity中,因为AsyncTask隐式持有Activity的引用,但是AsyncTask如果不被cancel或者将其任务执行完毕,它就不会被回收,因为两者生命周期不同步,当执行耗时任务或者手机屏幕旋转导致Activity被销毁时极有可能造成内存泄露!使用rxJava和rxAndroid可以轻松解决这个问题。

2.链式调用。
rxJava整个使用过程中是没有组合嵌套的。因此它不会造成多余的耦合,而且其过程非常清晰,甚至不需要很复杂的注释就能读懂整个过程的逻辑。
它的调用过程大概是这样的:

1
.map(fun)
.subscribeOn(thread)
.observeOn(thread)
.suscribe(new  Action{
    public void call(params){
    }
  } )

可以看到,整个调用过程的逻辑是非常清晰的。

3.强大的map和filter功能。
rxJava引入了很多函数式编程的思想,恰巧我最近正在看《计算机程序的构造与解释》,学习了一些List的方言Scheme的语法知识,简单了解了函数式编程中的一些思想。rxJava引入的map和filter就是函数式编程中非常重要的概念,map是一种映射变换的思想,通过map我们可以把一种Observable变换成另一种Observable,这是一种闭包的性质,我们可以通过map得到任何我们想要的Observable,在Android开发过程的直接体现就是我们可以把传入Observable的参数转化为任意的另外一个参数!
比如我在项目中使用的一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Observable.just(faceBitmap)
.map(new Func1<Bitmap, DetectFaceInteractor>() {
@Override
public DetectFaceInteractor call(Bitmap bitmap) {
JSONObject respsonse = null;
try {
respsonse = youtu.FaceShape(faceBitmap, 0);
FacePositionModel model = gson.fromJson(respsonse.toString(), FacePositionModel.class);
detectFaceInteractor = new DetectFaceInteractorImpl(model, BitmapRich.toGrayscale(faceBitmap));
} catch (IOException e) {
e.printStackTrace();
} catch (JSONException e) {
e.printStackTrace();
} catch (KeyManagementException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return detectFaceInteractor;
}
})

通过map我实现了一个变换:
将传入的bitmap通过指定接口得到Json格式的数据,并通过Gson解析成一个JavaBean,最后传入Interactor得到一个业务类!
注意,这里的map的参数一个匿名内部类fun,实际上我们这个需要的仅仅是一个处理变换过程的函数,但是由于Java是面向对象的语言,很多时候我们迫不得已使用了大量的匿名内部类来实现这种仅有一个函数起作用的类。这也是与函数式编程不同的地方,在函数式编程中可以直接把一个函数A当做参数传入另一个函数B,函数B就叫做高阶函数,这种实现方式在Java8中已经通过lambda表达式引入了Java中,之所以称rxJava为函数式的框架,也是因为这个原因,它其中引入了很多函数式编程的概念,因此如果要使用rxJava,配合lambda表达式会把你的代码简化到极致!

二.How? rxjava怎么使用

要学会如何使用rxJava,我们需要弄明白三块内容

1.观察者模式。
rxJava的整个架构都是基于观察者模式,如果你不理解这种设计模式,在使用时你将寸步难行。
什么是观察者模式?
在Java使用时你有没有发现在很多情况下,一个对象的变化依赖于另一个对象的变化,我们将这种通用的情况抽出来进行总结,将其称之为观察者模式。观察者模式存在一个观察者和一个被观察者,当被观察者对象发现变化时,它将通知与它关联的观察者,这时观察者就将做出相应的应对。
简单的来说,观察者模式的实现方式就是

1
2
3
Observable.onChange(){
observer.changeWithIt;
}

具体的就不再展开。

2.rxJava的重要类和接口
rxJava中最重要的几个接口和类的关系理清楚时你就将明白它是如何实现观察者模式。
(1)Observable类 被观察者
(2)Observer接口 观察者
(3)Subscribe类 订阅者,注意这个类是Observable类的子
类,同时又是Observer的实现类。这点可能有些奇怪,我们暂且不去深究这样设计的意义。
(4)Function接口 实现函数式编程中对应的函数参数
(5)Action接口 Function的子接口,起到简化使用的作用
由名字就可以知道前几个关键类在观察者模式对应的地位。后面两个接口在使用时再仔细研究。
暂且就到这里。

3.函数式编程思想
如果没有接触过函数式编程,直接学习rxJava会非常难以适应其中大量的陌生概念。这里主要说明几个我目前接触到的相关概念。
(1)高阶函数
以函数为参数的函数被称为高阶函数,在Java中没有对应的概念,为了实现高阶函数,我们经常使用匿名内部类来替代函数传入,这让Java代码变得非常臃肿,在Java8中引入lambda表达式就是实现了这个功能,想要深入学习的同学可以再深入的去砍一下lambda表达式的相关内容。
(2)闭包映射
当一个结构满足闭包性质时,即对它进行的一系列操作返回的都是另一个相应的结构时,我们就可以以此为基础构建更大更抽象的结构。这其实就是封装的概念,在Java中直接的体现就是类和对象,一个对象我们去操作它的成员,最终返回同一个类的另一种对象,我们把这个类当做砖从而去造大房子,map和filter就作用在这类结构上,理论上我们可以使用它们构造出所有我们需要的砖。

三.Why? rxjava的实现原理

这一部分内容留置以后研究。

参考资料
给 Android 开发者的 RxJava 详解

Github
rxJava
rxAndroid

在AndroidStudio中使用lambda表达式
gradle-retrolambda