Skip to content

Commit f717e16

Browse files
committed
修改演示界面,优化文档,增加说明
1 parent 51f9fc6 commit f717e16

11 files changed

+158
-81
lines changed

README.md

+110-64
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,6 @@
22
[![Build Status](https://travis-ci.org/ysmintor/Retrofit2RxjavaDemo.svg?branch=master)](https://travis-ci.org/ysmintor/Retrofit2RxjavaDemo)
33
***
44

5-
## English Version
6-
[please click here to see English version.](language/README-EN.md)
7-
To explain how to retrieve data from non restful response to get the right data(I don't have much time to finish English version, please wait for more detail coverage).
8-
9-
---
105

116
## 中文版
127

@@ -16,34 +11,31 @@ To explain how to retrieve data from non restful response to get the right data(
1611
![rx](screenshots/rx.gif)
1712
![common](screenshots/common.gif)
1813

19-
本文主要介绍了使用Retrofit2配合Rxjava[这里指Rxjava1, 暂时未适配Rxjava2] 来处理非restful的网络请求结果。一般而言请求的非REST结果如下:
14+
本文主要介绍了使用Retrofit2配合Rxjava[这里指Rxjava1, 暂时未适配Rxjava2] 来处理非restful的网络请求结果。一般而言请求的非REST结果如下,包括一个code代表结果的状态和msg表示描述,以及data对应的真实的数据。我们的目的就是直接取`data`对应的数据,如果是数组那么取的也是数组`list<databean>`类型的,如果有错误,那么就会调用onError()方法。更多演示效果请查看video目录下的视频。
15+
2016
```json
2117
{
22-
"status": 1, //请求返回状态码
18+
"code": 0, //请求返回状态码 0表示成功,其它则为对应的错误类型
2319
"msg": "请求成功", //请求返回状态
24-
"result": { //返回结果
25-
"npage": 1, //当前页
26-
"pageSize": 10, //每页信息条数
27-
"total": 1, //查询到的信息条数
28-
"data": [{
20+
"data": { //返回结果
2921
"phone": "010-62770334;010-62782051", //电话
3022
"website": "www.tsinghua.edu.cn", //官网
3123
"email": "[email protected]", //邮箱
3224
"address": "北京市海淀区清华大学", //地址
33-
"zipcode": "", //邮编
25+
"zipcode": "0102770334", //邮编
3426
"name": "清华大学", //学校名称
3527
"img": "http://img.jidichong.com/school/3.png", //学校logo图片
3628
"parent": "教育部", //隶属部门
3729
"type": " 211 985", //学校类型
38-
"profile": "", //简介
30+
"profile": "xasd", //简介
3931
"info": "院士:68人 博士点:198个 硕士点:181个", //说明
4032
"city": "北京" //所在省市
41-
}]
33+
}
4234
}
4335
}
4436
```
4537

46-
而Retrofit2 请求的结果一般都分为Header 和Body。当然使用使用Rxjava后,则可以实现获取这个result的结果。当是单个实体的时候,在onNext中就直接得到这个结果,如果是一个数组的时候,则是返回List<Entity> 这种形式。
38+
而Retrofit2 请求的结果一般都分为Header 和Body。在获取这些数据后再在Rxjava subscriber 中的onNext()来处理比较麻烦。所以这个demo介绍了如何结果Retrofit2与Rxjava1来处理数据并在成功时得到data字段的数据,服务端返回有错误是进入onError(),当是单个实体的时候,在onNext中就直接得到这个结果,如果是一个数组的时候,则是返回List<Entity> 这种形式。同时也简化了定义gson实体不用每次都加code, msg这样一层
4739

4840

4941

@@ -52,61 +44,76 @@ To explain how to retrieve data from non restful response to get the right data(
5244

5345
使用方法:
5446

55-
1. 配置对应的外层实体,例如下面。开发中一般都非标准的REST都是一个数据(百度开放平台的接口就基本都是这种形式),一个状态码和一个消息。其中data的类型是泛型,可以在生成请求的api指定实际返回的类型,如果为空的情况可以使用String。 而code 和message是对应于服务端定义的代码code和返回的错误信息。Demo里有初始值是因为找的接口没有使用这两个字段,但我想模拟这类情形而设置的。如果你的后台后台字段不同,你可以按需要修改这个HttpResult的相应字段。
47+
1. 配置对应的外层实体,例如下面。开发中一般都非标准的REST都是一个数据(百度开放平台的接口就基本都是这种形式),一个状态码和一个消息。其中data的类型是泛型,可以在生成请求的api指定实际返回的类型,如果为空的情况可以使用String。 而code 和message是对应于服务端定义的代码code和返回的错误信息。如果你的后台后台字段不同,你可以按需要修改这个HttpResult的相应字段。
48+
5649
```java
57-
{
58-
// code 为返回的状态码, message 为返回的消息, 演示的没有这两个字段,考虑到真实的环境中基本包含就在这里写定值
59-
private int code = 0;
60-
private String message = "OK";
61-
62-
//用来模仿Data
63-
@SerializedName(value = "subjects")
64-
private T data;
65-
}
50+
{
51+
// code 为返回的状态码, message 为返回的消息, 演示的没有这两个字段,考虑到真实的环境中基本包含就在这里写定值
52+
private int code = 0;
53+
private String message = "OK";
54+
55+
//用来模仿Data
56+
@SerializedName(value = "subjects")
57+
private T data;
58+
}
6659
```
6760

68-
2. 同Retrofit2一样要定义接口,如下。这里仅仅是有get的接口在demo里,其它的Put, Delete, Query等都是一样的
61+
2. 同Retrofit2一样要定义接口,如下。这里仅仅是有GET的接口在demo里,POST, PUT, DELETE, QUERY等都是一样的
6962

70-
`HttpResult`里的类型就是指定泛型data的具体类型。可以使用String, JSONObject,定义的实体等等。
63+
`HttpResult`里T的类型就是指定泛型data的具体类型。可以使用String, JSONObject,定义的实体等等。
7164

7265
另外有朋友问题访问参数是JSON对象怎么办 _(Body Paramter JSON Object)_?这其实就是将你的参数设置成一个已经定义的实体,我也给出一个项目中的接口。下面的post就是这种方式。关于请求的REST方式我会在文章后面放出详细的参考,若你不熟悉请参考这些文章。
7366
```java
7467

75-
@GET("top250")
76-
Observable<HttpResult<List<ContentBean>>> getTopMovie(@Query("start") int start, @Query("count") int count);
68+
@GET("mock3")
69+
Observable<HttpResult<MockBean>> getMock3();
7770

78-
@POST("user/login")
79-
Observable<HttpResult<UserLoginBean>> postLogin(@Body UserLoginRequest request);
71+
@GET("mock1")
72+
Observable<HttpResult<List<MockBean>>> getMock1();
73+
74+
@GET("mock4")
75+
Observable<HttpResult<MockBean>> getMock4();
76+
77+
@GET("mock2")
78+
Observable<HttpResult<MockBean>> getMock2();
8079
```
8180

8281
3. 请求网络。
8382

8483
直接调用
8584
```java
86-
ServiceFactory.movieApi()
87-
.getTopMovie(1, 10)
85+
public static MockApi mockApi() {
86+
return ServiceFactory.createService(MockApi.class);
87+
}
8888
```
8989

90-
使用一个默认的`.compose(new DefaultTransformer<List<ContentBean>>())`可以非常方便地进行转化成了需要的`Observable`。
90+
使用一个默认的`.compose(new DefaultTransformer<List<MockBean>>())`可以非常方便地进行转化成了需要的`Observable`。如下代码中那样进行了线程的转换,错误的处理在这个transformer,可以自定义自己的transformer。
91+
```java
92+
return observable
93+
.subscribeOn(Schedulers.io())
94+
.observeOn(Schedulers.newThread())
95+
.compose(ErrorTransformer.<T>getInstance())
96+
.observeOn(AndroidSchedulers.mainThread());
97+
```
9198

9299
另外准备了常用的subscriber,包含了网络连接的错误处理,例如非200状态,另外是服务端(业务)错误的处理,默认是将错误编码和错误信息在控制台和手机上输出。
93-
正确的情况下则必须实现
100+
提供的`RxSubscriber`和 `CommonScriber`中的`onNext()`必须实现
94101

95-
建议使用Rxlifecycle防止使用Rxjava内存泄露
102+
建议使用`RxLifecycle`防止使用`RxJava`内存泄露。其它方面使用同RxJava与Retrofit2结合的使用是相同的,所以得到结果您若有若要对数据进行处理仍然是链式调用
96103

97104
---
98105

99-
错误方面
106+
#关于错误处理方面介绍
107+
108+
主要使用了RxJava中的`onErrorResumeNext`,遇到错误后将错误通过`ExceptionEngine.handleException(throwable)`进行处理。
100109

101-
定义了服务器错误和连接错误等。主要使用了RxJava中的`onErrorResumeNext`,遇到错误后将错误通过`ExceptionEngine.handleException(throwable)`进行处理。
102110
```java
103111
@Override
104112
public Observable<T> call(Observable<HttpResult<T>> responseObservable) {
105113
return responseObservable.map(new Func1<HttpResult<T>, T>() {
106114
@Override
107115
public T call(HttpResult<T> httpResult) {
108116
// 通过对返回码进行业务判断决定是返回错误还是正常取数据
109-
// if (httpResult.getCode() != 200) throw new RuntimeException(httpResult.getMessage());
110117
if (httpResult.getCode() != ErrorType.SUCCESS) throw new ServerException(httpResult.getMessage(), httpResult.getCode());
111118
return httpResult.getData();
112119
}
@@ -120,7 +127,7 @@ To explain how to retrieve data from non restful response to get the right data(
120127
});
121128
}
122129
```
123-
其中的ApiException
130+
其中的ApiException包括code和错误的详情
124131
```java
125132
public class ApiException extends Exception {
126133
// 异常处理,为速度,不必要设置getter和setter
@@ -134,7 +141,7 @@ public class ApiException extends Exception {
134141
}
135142
```
136143

137-
重点在于下面这个处理。你可以再定义自己的业务相关的错误,目前常用的都已经有了,而业相关的错误大部分通过接收的数据直接显示了。若不是直接显示的情况,你完全可以在提供的subscriber里去实现。
144+
__重点在于下面这个处理。你可以再定义自己的业务相关的错误,目前常用的都已经有了,而业相关的错误大部分通过接收的数据直接显示了。若不是直接显示的情况,你完全可以在提供的subscriber里去实现。__
138145
```java
139146
public class ExceptionEngine {
140147
//对应HTTP的状态码
@@ -208,46 +215,85 @@ public class ExceptionEngine {
208215
}
209216
```
210217

218+
#关于处理服务器在错误时将错误信息直接放在data字段,即data字段在结果成功和失败对应的类型不定。处理思路是自定义GsonConverter,可以查看Demo里的`MockDataActivity`去看使用方法,其实就修改Retofit2初始化传入的GsonConverter。关键是对于`CustomGsonResponseBodyConverter`的修改。
219+
```java
220+
@Override
221+
public T convert(ResponseBody value) throws IOException {
222+
String response = value.string();
223+
JsonElement jsonElement = jsonParser.parse(response);
224+
int parseCode = jsonElement.getAsJsonObject().get("code").getAsInt();
225+
//
226+
if (parseCode != ErrorType.SUCCESS) {
227+
value.close();
228+
String msg = jsonElement.getAsJsonObject().get("data").getAsString();
229+
throw new ServerException(msg, parseCode);
230+
} else {
231+
232+
MediaType contentType = value.contentType();
233+
Charset charset = contentType != null ? contentType.charset(UTF_8) : UTF_8;
234+
InputStream inputStream = new ByteArrayInputStream(response.getBytes());
235+
Reader reader = new InputStreamReader(inputStream, charset);
236+
JsonReader jsonReader = gson.newJsonReader(reader);
237+
238+
try {
239+
return adapter.read(jsonReader);
240+
} finally {
241+
value.close();
242+
}
243+
}
244+
}
245+
```
246+
247+
这里先解析code字段再进行判断,所以处理这种服务器返回的话,是需要将上面的`"code"``"data"`替换成你服务端具体的字段。
248+
-------------------
249+
250+
251+
252+
211253
## Update
254+
### 2017-03-27
255+
更新文档,增加短视频说明,增加错误信息放在data字段的说明。
256+
### 2017-03-07
212257

258+
添加处理非REST接口在token失效时或code异常时,错误信息放在data字段的解析办法处理。
213259

214-
2017-03-07
215-
添加处理非REST接口在token失效时或code异常时,错误信息放在data字段的解析办法处理。
216-
>```json
217-
{
218-
code:-1
219-
data:"token失效"
220-
}
260+
```json
261+
{
262+
code:-1
263+
data:"token失效"
264+
}
265+
```
266+
```json
267+
{
268+
code:0
269+
data:{name:"xiaoming", age:23}
270+
}
271+
```
221272

222-
{
223-
code:0
224-
data:{name:"xiaoming", age:23}
225-
}
226-
````
227-
273+
针对上面的JSON都要在同一个接口里处理,解决办法都是两次解析的办法,第一次取到code并且判断,不是期望的值进行处理。期望的值可按原路处理。这里采用了修改GsonConverter的办法。
228274

229-
针对上面的JSON都要在同一个接口里处理,解决办法都是两次解析的办法,第一次取到code并且判断,不是期望的值进行处理。期望的值可按原路处理。这里采用了修改GsonConverter的办法。
230-
---
275+
--------------------
231276

232-
2016-12-26
277+
### 2016-12-26
233278
解决了执行onCompleted()之后执行onError()的问题
234-
>```java
279+
```java
235280
if (!isUnsubscribed())
236281
{
237282
unsubscribe();
238283
}
239-
```
284+
```
240285

241286

242-
---
287+
-----------------
288+
243289

290+
### 2016-10-13
244291

245-
2016-10-13
246292
修正了服务端code没有处理,返回为错误时认为是json实体解析问题。
247293

248-
>``` java
294+
``` java
249295
if (httpResult.getCode() != ErrorType.SUCCESS || httpResult.getCode() != ErrorType.SUCCESS)
250-
```
296+
```
251297

252298

253299
## Thanks

app/src/main/AndroidManifest.xml

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
88

99
<application
10+
android:name=".MyApplication"
1011
android:allowBackup="true"
1112
android:icon="@mipmap/ic_launcher"
1213
android:label="@string/app_name"

app/src/main/java/york/com/retrofit2rxjavademo/activity/MainActivity.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ private void withDialog() {
101101
@Override
102102
public void onNext(MockBean mockBean) {
103103
Toast.makeText(mContext, "onNext", Toast.LENGTH_SHORT).show();
104-
resultOne.setText("Single bean begin >>>>>>>>>>>>>>>>." + mockBean);
104+
resultOne.setText("Single bean begin >>>>>>>>>>>>>>>>.\n" + mockBean);
105105
Log.d("main", "onNext: " + mockBean);
106106
}
107107
});

app/src/main/java/york/com/retrofit2rxjavademo/activity/MockDataActivity.java

+21-4
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,24 @@
88
import android.widget.Button;
99
import android.widget.TextView;
1010

11+
import retrofit2.Retrofit;
12+
import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory;
1113
import york.com.retrofit2rxjavademo.R;
1214
import york.com.retrofit2rxjavademo.entity.MockBean;
15+
import york.com.retrofit2rxjavademo.gsonconverter.CustomGsonConverterFactory;
1316
import york.com.retrofit2rxjavademo.http.ServiceFactory;
1417
import york.com.retrofit2rxjavademo.subscribers.RxSubscriber;
1518
import york.com.retrofit2rxjavademo.transformer.DefaultTransformer;
1619

20+
import static york.com.retrofit2rxjavademo.http.ServiceFactory.BASE_URL;
21+
1722
public class MockDataActivity extends AppCompatActivity {
1823
private TextView mTv;
1924
private Button mBtn1;
2025
private Button mBtn2;
2126
private Context mContext;
27+
private Retrofit sRetrefit;
28+
2229
@Override
2330
protected void onCreate(Bundle savedInstanceState) {
2431
super.onCreate(savedInstanceState);
@@ -28,11 +35,21 @@ protected void onCreate(Bundle savedInstanceState) {
2835
mBtn1 = (Button) findViewById(R.id.button);
2936
mBtn2 = (Button) findViewById(R.id.button2);
3037

38+
39+
// 使用自定义Converter处理message在错误时返回在data字段
40+
sRetrefit = new Retrofit.Builder()
41+
.client(ServiceFactory.getsClient())
42+
.baseUrl(BASE_URL)
43+
// 使用自定义Converter处理message在错误时返回在data字段
44+
.addConverterFactory(CustomGsonConverterFactory.create())
45+
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
46+
.build();
47+
3148
mBtn1.setOnClickListener(new View.OnClickListener() {
3249
@Override
3350
public void onClick(View v) {
34-
ServiceFactory.mockApi()
35-
.getMock()
51+
ServiceFactory.mockApi2(sRetrefit)
52+
.getMock3()
3653
.compose(new DefaultTransformer<MockBean>())
3754
.subscribe(new RxSubscriber<MockBean>(mContext) {
3855
@Override
@@ -46,8 +63,8 @@ public void onNext(MockBean mockBean) {
4663
mBtn2.setOnClickListener(new View.OnClickListener() {
4764
@Override
4865
public void onClick(View v) {
49-
ServiceFactory.mockApi()
50-
.getMock2()
66+
ServiceFactory.mockApi2(sRetrefit)
67+
.getMock4()
5168
.compose(new DefaultTransformer<MockBean>())
5269
.subscribe(new RxSubscriber<MockBean>(mContext) {
5370
@Override

app/src/main/java/york/com/retrofit2rxjavademo/gsonconverter/CustomGsonResponseBodyConverter.java

+5-3
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import okhttp3.MediaType;
1818
import okhttp3.ResponseBody;
1919
import retrofit2.Converter;
20+
import york.com.retrofit2rxjavademo.http.exception.ErrorType;
2021
import york.com.retrofit2rxjavademo.http.exception.ServerException;
2122

2223
import static okhttp3.internal.Util.UTF_8;
@@ -44,10 +45,11 @@ public T convert(ResponseBody value) throws IOException {
4445
String response = value.string();
4546
JsonElement jsonElement = jsonParser.parse(response);
4647
int parseCode = jsonElement.getAsJsonObject().get("code").getAsInt();
47-
48-
if (parseCode == -1) {
48+
//
49+
if (parseCode != ErrorType.SUCCESS) {
4950
value.close();
50-
throw new ServerException("原因", -1);
51+
String msg = jsonElement.getAsJsonObject().get("data").getAsString();
52+
throw new ServerException(msg, parseCode);
5153
} else {
5254

5355
MediaType contentType = value.contentType();

app/src/main/java/york/com/retrofit2rxjavademo/http/MockApi.java

+2-5
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,8 @@
1616
* @time 2017/3/7 15:38
1717
*/
1818
public interface MockApi {
19-
@GET("test")
20-
Observable<HttpResult<MockBean>> getMock();
21-
//
22-
// @GET("user/login")
23-
// Observable<HttpResult<MockBean>> getMock2();
19+
@GET("mock3")
20+
Observable<HttpResult<MockBean>> getMock3();
2421

2522
@GET("mock1")
2623
Observable<HttpResult<List<MockBean>>> getMock1();

0 commit comments

Comments
 (0)