MysteryGank项目的技术要点(一)

前言

近3个月没动手写Android项目了,这个期间一直在看书,感觉有时也只能浅显地了解技术表面的东西,没真正地动手去写一些东西,部分自身存在的问题不能被发现,技术是会处于瓶颈期不能有所提升的.所以此时通过上网了解了一下,gank.io里面有开放的api适合当练手的项目.github上也有几个关于的项目做为参考,再也合适不过了.于是一言不合项目就开始啦,取名为MysteryGank,中文名叫蜜汁Gank,别吐槽哈.

其中Gank.io的API网站:http://gank.io/api

1.Retrofit2和RxJava相结合获取数据

Retrofit能使用RESTful的架构方法实现对网络的超方便请求, 不熟悉可以点这查看官网.
RxJava作为ReactiveX Java的简写,是响应式编程的代表,看其在Github的star数目就知道其多受欢迎啦,使用过后你会发现你会离不开它了.不熟悉可以看下扔物线大神写的RxJava的博文,个人觉得写得十分不错.

1.1添加依赖

首先在项目(app)的build.gradle加入这些库的依赖,代码如下:

1
2
3
4
5
compile 'io.reactivex:rxjava:1.1.3'
compile 'io.reactivex:rxandroid:1.1.0'
compile 'com.squareup.retrofit2:retrofit:2.0.2'
compile 'com.squareup.retrofit2:converter-gson:2.0.2'
compile 'com.squareup.retrofit2:adapter-rxjava:2.0.2'

注意:

  • adapter-rxjava这个库是retrofit2用于适配rxjava的接口的库,采用的是设计模式中的适配器模式,注意版本要与Retrofit2的一致,不然在运行中会报出错误,而且特别难找出这个错误的原因!

1.2 创建Bean类


根据返回的Json结果,我们使用GsonFormat对json数据做出格式化处理(没听说过GsonF可以点这里):

MeiZhiEntity.java:

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

public class MeiZhi {

private String _id;
private Date createdAt;
private String desc;
private Date publishedAt;
private String source;
private String type;
private String url;
private boolean used;
private String who;

@Override
public String toString() {
return "MeiZhi{" +
"_id='" + _id + '\'' +
", createdAt='" + createdAt + '\'' +
", desc='" + desc + '\'' +
", publishedAt='" + publishedAt + '\'' +
", source='" + source + '\'' +
", type='" + type + '\'' +
", url='" + url + '\'' +
", used=" + used +
", who='" + who + '\'' +
'}';
}

//Getter 和 Setter 方法省略

还有一个类是RelaxVedioEntity.java,也是通过访问http://gank.io/api/data/休息视频/10/1获取json代码后生成的对象,这里就不贴代码了.

需要注意的地方:

  • 在使用GsonFormat的时候,注意手动把需要解析成为日期的改成Date类.
  • 为了方便调试,记得生成toString方法.

1.3 创建接收API的接口

1
2
3
4
5
6
7
8
9
10
11
12
public interface GankAPI {

@GET("/api/data/{type}/{num}/{page}")
Observable<HttpResult<List<MeiZhi>>> getGankData(@Path("type") String type, @Path("num") String num, @Path("page") String page);

@GET("/api/data/福利/" + CrainaxRetrofit.NUMBER_PER_PAGE + "/{page}")
Observable<HttpResult<List<MeiZhi>>> getMeizhi(@Path("page") String page);

@GET("/api/data/休息视频/" + CrainaxRetrofit.NUMBER_PER_PAGE + "/{page}")
Observable<HttpResult<List<RelaxVideo>>> getRelaxVedio(@Path("page") String page);

}

这里只写出了休息视频和妹子的路径,对于干货的获取方式,类似.

  • 其中的GankRetrofit.NUMBER_PER_PAGE是常量 10,写在单例类里面,方便修改.

1.4 创建工厂类,并创建单例对象

单例类GankRetrofit:

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
public class GankRetrofit {

private Retrofit retrofit;
/**
* 定义在APi里面的常数
*/

public static final int NUMBER_PER_PAGE = 10;
private final GankAPI gankAPI;

public Retrofit getRetrofit() {
return retrofit;
}

public GankAPI getGankAPI() {
return gankAPI;
}

private GankRetrofit() {

Gson gson = new GsonBuilder()
.setDateFormat("yyyy-MM-dd'T'14:42:21.265Z")
.serializeNulls()
.create();

retrofit = new Retrofit.Builder()
.baseUrl("http://gank.io/")
.addConverterFactory(GsonConverterFactory.create(gson))
//这里不能省略,用于适配RxJava的接口
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.build();

gankAPI = retrofit.create(GankAPI.class);
}

private static class SingletonHolder {
private static final GankRetrofit INSTANCE = new GankRetrofit();
}

/**
* 返回单例
*/

public static GankRetrofit getInstance() {
return SingletonHolder.INSTANCE;
}

}

  • 单例模式使用的是内部类Holder单例方法,详情可以看这里Java 单例真的写对了么?.
  • 注意Gson对象要为其设置特定的日期解析格式,依据返回的json格式而定.

工厂类APIFactory.java:

1
2
3
4
5
6
7
public class APIFactory {

public static GankAPI getGankAPI(){
return GankRetrofit.getInstance().getGankAPI();
}

}

1.5 写出调试代码

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
GankAPI gankAPI = APIFactory.getGankAPI();

//获取休息视频与妹子的接口,并剥离其"error"外壳.
Observable<List<RelaxVideoEntity>> oRelaxVideo = gankAPI.getRelaxVedio("1")
.map(new HttpMethod<List<RelaxVideoEntity>>());
Observable<List<MeizhiEntity>> oMeizhi = gankAPI.getMeizhi("1")
.map(new HttpMethod<List<MeizhiEntity>>());

//不使用匿名内部类的形式,方便返回
Subscriber<List<MeizhiEntity>> subscriber = new Subscriber<List<MeizhiEntity>>() {
@Override
public void onCompleted() {
Log.i(TAG, "onCompleted.");
}

@Override
public void onError(Throwable e) {
Log.w(TAG, "onError: ", e);
}

@Override
public void onNext(List<MeizhiEntity> meiZhis) {
for (MeizhiEntity meiZhi : meiZhis) {
Log.i(TAG, "onNext: " + meiZhi);
}
Log.i(TAG, "onNext Thread: " + Thread.currentThread());
}
};

//将休息视频的描述用zip整合到妹子中去
Observable.zip(oRelaxVideo, oMeizhi, new Func2<List<RelaxVideoEntity>, List<MeizhiEntity>, List<MeizhiEntity>>() {

@Override
public List<MeizhiEntity> call(List<RelaxVideoEntity> relaxVideos, List<MeizhiEntity> meiZhis) {
Log.i(TAG, "call: " + Thread.currentThread().getName());
for (MeizhiEntity meiZhi : meiZhis) {

for (RelaxVideoEntity relaxVideo : relaxVideos) {
if (meiZhi.getPublishedAt().equals(relaxVideo.getPublishedAt())) {
meiZhi.setDesc(meiZhi.getDesc() + " : " + relaxVideo.getDesc());
}
}

}
return meiZhis;
}

})
//获取网络数据,我们在IO线程中.
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.computation())
//用于排序的Computation线程.
.map(new Func1<List<MeizhiEntity>, List<MeizhiEntity>>() {
@Override
public List<MeizhiEntity> call(List<MeizhiEntity> meiZhis) {
Log.i(TAG, "sorting in :" + Thread.currentThread().getName());
return meiZhis;
}
})
//返回的接口,用于在Android UI线程.
.observeOn(AndroidSchedulers.mainThread())
.subscribe(subscriber);
  • 对于zip功能:

    将两个retrofit接口请求后得到的两个数据源Observable Observable进行合并
    我们需要把这两个数据源的数据拼接起来,所以我们可以考虑使用zip操作符,该操作符可以将两个数据源发射出来的数据依次组装在一起。
    比如一个Observable数据源依次发射出1, 3, 5, 7, 另一个Observable数据源依次发射出a, b, c, d,那么zip操作符组装后会对外发射出1a, 3b, 5c, 7d这样的数据。

    在这里我们要把RelaxVideo中的Desc(图片描述)取出放在Meizhi实例中,所以需要用到这个方法.至于返回值,我们还是选择Meizhi这个类,重复利用,而不必新建一个组合类.

  • 其中的map功能,第一个,在于创建将Json数据中的”error: false”给剥离,如果这个字段为true,则抛出一个我们自己定义的异常ApiException,用于在Subscriber去处理这个异常.

  • 第二个map,在其前面使用到Schedulers.computation()指定为计算的线程,在里面完成排序的过程,以日期作为排序字段.

通过这段代码,我们可以看出RxJava的优势所在,实力解耦.

然后我们可以简单的调试一下,发现调试结果也符合我们所需要:

1.6 在