前言
之前也做过OkHttp和RxJava方面的学习,只是简单总结了以下,但是并没有将两者真正结合起来使用过。
众所周知,OkHttp请求后返回的结果回调并不是在主线程中的,而RxJava却可以做线程的调度。所以,我们可以使用RxJava在io线程中请求网络,获取结果后,在主线程中处理。
服务端(SpringMVC)
我们首先约定数据格式,data字段中放Object类型的数据,客户端根据约定获取数据。
code=1时处理成功,msg=success,并返回data
code=0时处理失败,msg可以附上失败原因。
{"code":1,"msg":"success","data":{}}
再创建两个Model类
public class BaseResponse {
private int code;
private String msg;
private Object data;
public BaseResponse() {
super();
}
public BaseResponse(int code, String msg, Object data) {
super();
this.code = code;
this.msg = msg;
this.data = data;
}
// getter and setter...
}
public class OkBean {
private int id;
private String name;
private List<String> values;
private Date date;
public OkBean() {
super();
}
public OkBean(int id, String name, List<String> values, Date date) {
super();
this.id = id;
this.name = name;
this.values = values;
this.date = date;
}
// getter and setter...
}
在控制器中写两个方法,返回json格式数据:
@RequestMapping("ok")
@Controller()
public class HttpTest {
@ResponseBody
@RequestMapping(value="get")
public String getJson() {
List<String> values = new ArrayList<String>();
values.add("aaa");
values.add("bbb");
values.add("ccc");
OkBean okBean = new OkBean(1, "okhttp", values, new Date());
BaseResponse result = new BaseResponse(1, "success", okBean);
return JSON.toJSONString(result);
}
@ResponseBody
@RequestMapping(value="get2")
public String getJson2() {
List<String> values = new ArrayList<String>();
values.add("aaa");
values.add("bbb");
values.add("ccc");
OkBean okBean = new OkBean(1, "okhttp", values, new Date());
return JSON.toJSONString(okBean);
}
}
第二个方法未按约定数据格式返回数据,这个是为了一会儿的测试使用。
客户端
说明:
在使用高版本的okhttp和okio时,报了
java.lang.NoClassDefFoundError: didn't find class kotlin.text.charsets;异常,但使用了一些网上的方法后并没有解决。所以现在使用较低版本的okhttp和okio,可以去maven上去下载:https://mvnrepository.com/ ,打开网址后分别输入okhttp和okio选择版本号下载即可。
这是我这次测试使用的jar包:
经过这个小插曲就要开始写代码了。
先定义异常类,来封装异常类型
public class OkhttpException extends Exception {
private int code;
public OkhttpException(int code) {
this.code = code;
}
public int getCode() {
return code;
}
}
定义异常类型,大致可以分为以下类型,已在注释中标注
1.有网络但,请求异常
2.网络链接失败
3.返回格式出错
public class ExceptionCode {
//有网络,但请求失败,如接口地址写错404,服务器异常500等。
public static final int CODE_REQUEST_FAIL = 1;
//没网,无法链接服务器等
public static final int CODE_NET_ERROR = 2;
//成功返回数据,但数据格式不对,json解析失败
public static final int CODE_DATA_FORMAT = 3;
}
定义回调类,我们要在这个回调中集中处理各种情况
public interface RxHttpHandler {
void onStart();
void onSuccess(String s);
void onError(int code);
void onFinish();
}
实现回调类,这个类继承上面的RxHttpHandler接口 ,只做json格式的校验,并将所需的数据类型自动回调给调用方。
public class RxJsonHttpHandler<T extends BaseResponseBean> implements RxHttpHandlerInte {
private Class<T> clazz;
public RxJsonHttpHandler(Class<T> clazz) {
this.clazz = clazz;
}
@Override
public void onStart() {
}
@Override
public void onSuccess(String s) {
T t = JSON.parseObject(s, clazz);
if (t != null) {
onSuccess(t);
} else {
onError(ExceptionCode.CODE_DATA_FORMAT);
}
}
public void onSuccess(T t) {
}
@Override
public void onError(int code) {
}
@Override
public void onFinish() {
}
}
okhttp,get和post方法
import java.util.Map;
import okhttp3.Callback;
import okhttp3.FormBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
public class HttpUtil {
private OkHttpClient client;
public HttpUtil () {
client = new OkHttpClient();
}
public void get(String url, Callback callback) {
Request request = new Request.Builder().url(url).build();
client.newCall(request).enqueue(callback);
}
public void post(String url, Map<String, String> requestParams, Callback callback) {
FormBody.Builder bodyBuilder = new FormBody.Builder();
for (Map.Entry<String, String> entry : requestParams.entrySet()) {
bodyBuilder.add(entry.getKey(), entry.getValue());
}
Request request = new Request.Builder()
.url(url)
.post(bodyBuilder.build())
.build();
client.newCall(request).enqueue(callback);
}
}
Rx代码实现异步功能。终于到这一步了~~
这里第一个get方法中做网络请求,将各种事件 “发射” 出去,返回Observable<String>对象,由第二个get方法来调用。
import java.io.IOException;
import io.reactivex.Observable;
import io.reactivex.ObservableEmitter;
import io.reactivex.ObservableOnSubscribe;
import io.reactivex.Observer;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.annotations.NonNull;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.Response;
public class RxHttp {
private HttpUtil httpUtil = new HttpUtil();
public Observable<String> get(final String url) {
return Observable.create(new ObservableOnSubscribe<String>() {
@Override
public void subscribe(@NonNull final ObservableEmitter<String> emitter) throws Exception {
httpUtil.get(url, new Callback() {
@Override
public void onFailure(Call call, IOException e) {
emitter.onError(new OkhttpException(ExceptionCode.CODE_NET_ERROR));
}
@Override
public void onResponse(Call call, Response response) throws IOException {
if (response.isSuccessful()) {
String body = response.body().string();
Log.e("responseJson", body);
emitter.onNext(body);
emitter.onComplete();
} else {
emitter.onError(new OkhttpException(ExceptionCode.CODE_REQUEST_FAIL));
}
}
});
}
});
}
public void get(String url, final RxHttpHandlerInte handler) {
get(url)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<String>() {
@Override
public void onSubscribe(@NonNull Disposable disposable) {
handler.onStart();
}
@Override
public void onNext(@NonNull String s) {
handler.onSuccess(s);
}
@Override
public void onError(@NonNull Throwable throwable) {
//强转为我们自定义的 OkhttpException,onError中传入异常类型
if (throwable.getClass().equals(OkhttpException.class)) {
int code = ((OkhttpException) throwable).getCode();
handler.onError(code);
handler.onFinish();
}
}
@Override
public void onComplete() {
handler.onFinish();
}
});
}
}
下面来测试三种情况,
1.正常返回数据。2.请求网址出错(其实就是404,这个和500测试结果是一样的)3.数据格式出错(这里用的就是 http://192.168.1.111:8080/ssh/ok/get2 这个地址)。
最后终于开始调用写页面逻辑了
xml页面
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="get"
android:text="get" />
<EditText
android:id="@+id/et_url"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="http://192.168.1.111:8080/ssh/ok/get" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="get2"
android:text="get2" />
</LinearLayout>
activity
public class OkHttpActivity extends AppCompatActivity {
private static final String TAG = "Rx_Ok";
private RxHttp mRxHttp;
private EditText mEtUrl;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_ok_http);
mEtUrl = ((EditText) findViewById(R.id.et_url));
mRxHttp = new RxHttp();
}
public void get(View v) {
String url = "http://192.168.1.111:8080/ssh/ok/get";
mRxHttp.get(url)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<String>() {
@Override
public void onSubscribe(@NonNull Disposable disposable) {
}
@Override
public void onNext(@NonNull String s) {
}
@Override
public void onError(@NonNull Throwable throwable) {
if (throwable.getClass().equals(OkhttpException.class)) {
int code = ((OkhttpException) throwable).getCode();
switch (code) {
case ExceptionCode.CODE_REQUEST_FAIL:
// TODO: 2018/12/20 请求失败
break;
case ExceptionCode.CODE_NET_ERROR:
// TODO: 2018/12/20 网络异常
break;
default:
break;
}
}
}
@Override
public void onComplete() {
}
});
}
public void get2(View v) {
String url = "";
url = mEtUrl.getText().toString().trim();
mRxHttp.get(url, new RxJsonHttpHandler<ResponseOkBean>(ResponseOkBean.class) {
@Override
public void onStart() {
// 请求之前,弹出dialog 等
Log.e(TAG, "onStart");
}
@Override
public void onSuccess(ResponseOkBean responseOkBean) {
int code = responseOkBean.getCode();
if (code == 1) {
OkBean data = responseOkBean.getData();
Log.e(TAG, data.toString());
// TODO: setView...设置界面数据
} else {
String msg = responseOkBean.getMsg();
if (TextUtils.isEmpty(msg)) {
Log.e(TAG, "xxx失败");
} else {
Log.e(TAG, msg);
}
}
}
@Override
public void onError(int code) {
switch (code) {
case ExceptionCode.CODE_REQUEST_FAIL:
Log.e(TAG, "请求失败");
Toast.makeText(OkHttpActivity.this, "请求失败", Toast.LENGTH_SHORT).show();
break;
case ExceptionCode.CODE_NET_ERROR:
Log.e(TAG, "网络异常");
Toast.makeText(OkHttpActivity.this, "网络异常", Toast.LENGTH_SHORT).show();
break;
case ExceptionCode.CODE_DATA_FORMAT:
Log.e(TAG, "请求数据格式不正确");
Toast.makeText(OkHttpActivity.this, "请求数据格式不正确", Toast.LENGTH_SHORT).show();
break;
default:
break;
}
}
@Override
public void onFinish() {
// 请求结束:关闭dialog 等
Log.e(TAG, "onFinish");
}
});
}
}
get(View v)点击事件是为了测试RxHttp中的第一个get(final String url)方法。
get2(View v)点击事件,测试了上诉三种情况:
1正常情况:
12-20 17:53:30.859 19877-19877/com.app.rxandroid E/Rx_Ok: onStart
12-20 17:53:30.894 19877-20019/com.app.rxandroid E/responseJson: {"code":1,"data":{"date":1545299610162,"id":1,"name":"okhttp","values":["aaa","bbb","ccc"]},"msg":"success"}
12-20 17:53:30.933 19877-19877/com.app.rxandroid E/Rx_Ok: OkBean{id=1, name='okhttp', values=[aaa, bbb, ccc], date=Thu Dec 20 17:53:30 GMT+08:00 2018}
12-20 17:53:30.933 19877-19877/com.app.rxandroid E/Rx_Ok: onFinish
2.请求失败。(如:404)
12-20 17:54:59.288 19877-19877/com.app.rxandroid E/Rx_Ok: onStart
12-20 17:54:59.318 19877-19877/com.app.rxandroid E/Rx_Ok: 请求失败
12-20 17:54:59.388 19877-19877/com.app.rxandroid E/Rx_Ok: onFinish
3.数据格式不正确。
12-20 17:55:38.497 19877-19877/com.app.rxandroid E/Rx_Ok: onStart
12-20 17:55:38.551 19877-21414/com.app.rxandroid E/responseJson: {"date":1545299737813,"id":1,"name":"okhttp","values":["aaa","bbb","ccc"]}
12-20 17:55:38.558 19877-19877/com.app.rxandroid E/Rx_Ok: xxx失败
12-20 17:55:38.558 19877-19877/com.app.rxandroid E/Rx_Ok: onFinish
其实还有第四种情况,就是网络问题导致的IOException。如网络未连接。
@Override
public void onFailure(Call call, IOException e) {
emitter.onError(new OkhttpException(ExceptionCode.CODE_NET_ERROR));
}
这个我也测试过,正常回调到了这里:
case ExceptionCode.CODE_NET_ERROR:
Log.e(TAG, "网络异常");
Toast.makeText(OkHttpActivity.this, "网络异常", Toast.LENGTH_SHORT).show();
break;
网友评论