RxJava+Retrofit版知乎日报首页以及遇到的一些坑

这几天抽时间学习了一下火到不行的两个两个框架RxJavaRetrofit,两个框架一结合,牛逼的不要不要的,真的火得有理由啊;
于是乎,山寨了一个阉割版的知乎日报,只实现了知乎日报首页。
截图

API

知乎日报的API没什么好说的,可以看看下面的链接:

知乎日报 API 分析

Dependencies

compile ‘io.reactivex:rxjava:1.1.5’
compile ‘io.reactivex:rxandroid:1.2.0’
compile ‘com.squareup.retrofit2:retrofit:2.0.0-beta4’
compile ‘com.squareup.retrofit2:converter-gson:2.0.0-beta4’
compile ‘com.squareup.retrofit2:adapter-rxjava:2.0.0-beta4’
compile ‘com.google.code.gson:gson:2.6.2’
compile ‘com.github.bumptech.glide:glide:3.7.0’
compile ‘com.jakewharton:butterknife:7.0.1’

Retrofit

Retrofit使用注解+java接口来定义后台服务API接口,定义一个接口,和三个Get请求;

1
2
3
4
5
6
7
8
9
10
11
12
13
public interface ZhihuApi {
//最新消息
@GET("api/4/news/latest")
Observable<LatestStory> getLatest();

//日报内容
@GET("api/4/news/{id}")
Observable<Content> getContent(@Path("id") String id);

//过往消息,date是一个日期,如:20160606
@GET("api/4/news/before/{date}")
Observable<BeforeStroy> getBefore(@Path("date") String date);
}

可以看到,返回值类型已经不再是Call<T>了,而是Observable,因为使用了Rxjava,只要初始化Retrofit的时候,设置一个 addCallAdapterFactory(RxJavaCallAdapterFactory.create())即可;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Network {
private static Retrofit retrofit;
private static ZhihuApi zhihuApi;

public static ZhihuApi getZhihuApi() {
if (zhihuApi == null) {
retrofit = new Retrofit.Builder()
.baseUrl("http://news-at.zhihu.com/")
.client(new OkHttpClient())
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.build();
zhihuApi = retrofit.create(ZhihuApi.class);
}
return zhihuApi;
}
}
`

通过NetworkgetZhuhuApi()方法就能拿到一个ZhihuApi对象。通过他就可以调用不同的接口了。

获取最新日报列表

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
/**
*
* 获区日报列表
* @param isRefresh 刷新
*/

private void initStories(final boolean isRefresh) {
stories = new ArrayList<>();
Network.getZhihuApi().getLatest()
.subscribeOn(Schedulers.io())
.flatMap(new Func1<LatestStory, Observable<Stroy>>() {
@Override
public Observable<Stroy> call(LatestStory latestStory) {
date = latestStory.getDate();
return Observable.from(latestStory.getStories());
}
})
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<Stroy>() {
@Override
public void onNext(Stroy stroy) {
//给每个stroy添加date属性值
stroy.setDate(date);
stories.add(stroy);
}

@Override
public void onCompleted() {
if (isRefresh) {
//如果是下拉刷新,则更新列表
adapter.addStories(stories, true);
swipeRefresh.setRefreshing(false);
stories = null;//更新ui后重新集合为null
} else
//否则初始化UI
initUI();
}

@Override
public void onError(Throwable e) {
Toast.makeText(mContext, "出错了", Toast.LENGTH_SHORT).show();
}
});
}

RxJava简化了一步操作,使得代码更加美观了,链式结构,阅读性非常强,来看看下面两句代码

1
2
subscribeOn(Schedulers.io())
observeOn(AndroidSchedulers.mainThread())

subscribeOn(Schedulers.io())表示事件将在Schedulers.io()执行,I/O 操作(读写文件、读写数据库、网络信息交互等)所使用的 Scheduler
observeOn(AndroidSchedulers.mainThread())表示事件将AndroidSchedulers.mainThread()(UI线程)消费,这是相当的方便的,一行代码就能实现线程的调度。有没一种一键切换的感觉。

1
2
3
4
5
6
7
flatMap(new Func1<LatestStory, Observable<Stroy>>() {
@Override
public Observable<Stroy> call(LatestStory latestStory) {
...
return Observable.from(latestStory.getStories());
}
})

flatMap这是一个很有用但非常难理解的变换,返回的是Observable,这个确实不好解释解释这个,在本例中,是将LatestStory最终转换为一个Story

获取日报详情

需要传入一个日报的唯一ID,即可得到日报详细内容,返回的json中,body字段是HTML格式的详细内容。所以这里用的是WebView展示内容。这也是我遇到肯定地方,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Network.getZhihuApi().getContent(id)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.map(new Func1<Content, String>() {
@Override
public String call(Content content) {
share_url = content.getShare_url();
title = content.getTitle();
setTitleImg(content.getImage());
return structHtml(content.getBody(), content.getCss(), content.getJs());
}
})
.subscribe(new Action1<String>() {
@Override
public void call(String s) {
webView.loadDataWithBaseURL(null, s, "text/html", "utf-8", null);
}
}, new Action1<Throwable>() {
@Override
public void call(Throwable throwable) {
Toast.makeText(mContent, "出错了", Toast.LENGTH_SHORT).show();
}
});

第一坑

看看webview的方法。如果上面的代码这样写

1
webView.loadData(s,"text/html","utf-8");

结果将会是这样的

乱码
wtf,明明指定utf-8,还是乱码了。好吧,还是这样写:

1
webView.loadDataWithBaseURL(null, s, "text/html", "utf-8", null);

第二坑

现在来看一下这个方法,拼凑了一个完整html文本。好,运行一下看看。

1
2
3
4
5
6
7
8
public String structHtml(String htmlBody, List<String> cssList, List<String> jsList) {

StringBuilder htmlString = new StringBuilder("<html><head>");
htmlString.append("</head><body>");
htmlString.append(htmlBody);
htmlString.append("</body></html>");
return htmlString.toString();
}

无css
现在没有乱码了,但是你看看是不是排版有点奇怪?是的,这是由于没有加CSS造成的。返回的json中,给了我们CSS的链接,只要在拼凑html的时候加进去就可以了。这样写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
   public String structHtml(String htmlBody, List<String> cssList, List<String> jsList) {
StringBuilder htmlString = new StringBuilder("<html><head>");
for (String css : cssList) {
htmlString.append(setCssLink(css));
}
htmlString.append("</head><body>");
htmlString.append(htmlBody);
htmlString.append("</body></html>");
return htmlString.toString();
}

public String setCssLink(String css) {
return "<link rel=\"stylesheet\" href=\"" + css + "\">";
}

跑起来再看看。
空白

第三坑

文字终于有样式了,等等,上面怎么有一片空白呢?这里其实是图片和标题用的,用过知乎日报的应该都知道吧,就不贴图了。看了一下html代码,发现空白处是一个<Div>造成的,因为的title图片是放在最上面的ImageView,所以我的处理是将那段代码删掉了。。。最终,我是这样写的。因为还返回了JS所以一块加上。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public String structHtml(String htmlBody, List<String> cssList, List<String> jsList) {
String deleteDiv = "<div class=\"img-place-holder\"></div>";
htmlBody = htmlBody.replace(deleteDiv, "");
StringBuilder htmlString = new StringBuilder("<html><head>");
for (String css : cssList) {
htmlString.append(setCssLink(css));
}
for (String js : jsList) {
htmlString.append(setJsLink(js));
}
htmlString.append("</head><body>");
htmlString.append(htmlBody);
htmlString.append("</body></html>");
return htmlString.toString();
}

public String setCssLink(String css) {
return "<link rel=\"stylesheet\" href=\"" + css + "\">";
}

private String setJsLink(String js) {
return "<script src=\"" + js + "\"></script>";
}

终于正常了!!!

结束

这个小Demo只是对RxjavaRetrofit的一个小实践,代码写的并不好,以后慢慢修吧。
源码托管在Githubhttps://github.com/hunao0221/XDaily
apk下载:http://pan.baidu.com/s/1boZmKbX

参考

给 Android 开发者的 RxJava 详解

坚持原创技术分享,您的支持将鼓励我继续创作!

热评文章