**
** 一般网络请求,会需要如下这些信息:
请求的网址 请求方式;是GET请求,还是POST请求 请求参数 参数传递方式;是通过表单方式传递,还是通过JSON方式传递 请求头 如何配置? 将这些信息写到一个接口中。
我们希望将项目中,所有请求的信息都放到Service接口中,名称可以随便写,每个方法,就代表一个接口,定义是歌单详情,只是我们这里用这种命名方式。
/** * 网络接口配置 * <p> * 之所以调用接口还能返回数据 * 是因为Retrofit框架内部处理了 * 这里不讲解原理 * 在《详解Retrofit》课程中讲解 */ public interface Service { /** * 歌单详情 * * @param id {id} 这样表示id,表示的@Path("id")里面的id, * path里面的id其实就是接收后面String id 的值 * <p> * 一般情况下,三个名称都写成一样,比如3个都是id * <p> * //Retrofit如何知道我们传入的是id呢,其实通过Retrofit注解@Path("id")知道 * (应该是相等于限定了id,其他的应该会报错) * <p> * Observable<SheetDetailWrapper>:相等于把json数据转换成这个SheetDetailWrapper类型的对象 * Observable:rxjava里面的类 */ @GET("v1/sheets/{id}") Observable<SheetDetailWrapper> sheetDetail(@Path("id") String id); }如果需要更多接口,只需要在这里添加就行了;这里暂时只添加了一个接口,目的是后面封装网络框架的时候会用到,其他的接口,用到了在添加。
测试 运行项目,点击按钮,就可以在日志中查看到歌单为1的JSON数据了。
在onError方法可以判断,这样太麻烦,后面会封装
//判断错误类型 if (e instanceof UnknownHostException) { ToastUtil.errorShortToast(R.string.error_network_unknown_host); } else if (e instanceof ConnectException) { ToastUtil.errorShortToast(R.string.error_network_connect); } else if (e instanceof SocketTimeoutException) { ToastUtil.errorShortToast(R.string.error_network_timeout); } else if (e instanceof HttpException) { HttpException exception = (HttpException) e; int code = exception.code(); if (code == 401) { ToastUtil.errorShortToast(R.string.error_network_not_auth); } else if (code == 403) { ToastUtil.errorShortToast(R.string.error_network_not_permission); } else if (code == 404) { ToastUtil.errorShortToast(R.string.error_network_not_found); } else if (code >= 500) { ToastUtil.errorShortToast(R.string.error_network_server); } else { ToastUtil.errorShortToast(R.string.error_network_unknown); } } else { ToastUtil.errorShortToast(R.string.error_network_unknown); }因为我们上面代码判断了,所以手机上报错404和500会爆出相应的提示。 为了方便,我们电脑上先查看下网络状态,然后再 运行到手机app上查看网络状态
如果要404错误,只需要把http://dev-my-cloud-music-api-rails.ixuea.com/v1/sheets中的sheets改下,就变成404 (把URL地址更改为一个不存在的地址;就会提示“你访问内容不存在!”。) 如果要500错误,只要用户名不存在就会变成500错误(用户名不存在就会报500错误) 比如: http://dev-my-cloud-music-api-rails.ixuea.com/v1/users/-1?nickname=11111111这个地址就是500错误打开这个检查界面,然后输入网址就可以捕获到网络状态
我们这里使用的是模拟器,所以关闭电脑WiFi就可以模拟;如果是真实手机,也可以关闭WiFi;测试会发现,关闭网络,会产生一个UnknownHostException异常,所以在这里代码判断就行了。
可以把初始化okhttp,初始化retrofit,还要创建Service放到一个单独的类中,然后把这个类,实现为单例,因为前面的这些初始化,只需要执行一次就行了。
Api 类
public class Api { /** * Api单例字段 */ private static Api instance; /** * Service单例 */ private final Service service; /** * 返回当前对象的唯一实例 * <p> * 单例设计模式 * 由于移动端很少有高并发 * 所以这个就是简单判断 * * @return 本类单例 */ public static Api getInstance() { if (instance == null) { instance = new Api(); } return instance; } /** * 私有构造方法 */ private Api() { OkHttpClient.Builder okhttpClientBuilder = new OkHttpClient.Builder(); //构建者模式 //初始化Retrofit Retrofit retrofit = new Retrofit.Builder() //让Retrofit使用okhttp .client(okhttpClientBuilder.build()) //api地址 .baseUrl(Constant.ENDPOINT)//这里使用的是地址的公共前缀 //适配Rxjava(就是所返回的对象以Rxjava这种方式来工作(比如我们使用了Observable这种方式,接口Service查看)) .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) //使用gson解析json //包括请求参数和响应 // (比如使用Retrofit请求框架请求数据,发送对象,也会转换成json(使用gson转换)) .addConverterFactory(GsonConverterFactory.create()) //创建Retrofit .build(); //创建Service service = retrofit.create(Service.class); } /** * 歌单详情 * * @param id 传入的第几个歌曲Id * @return 返回Retrofit接口实例 里面的方法返回的对象 */ public Observable<SheetDetailWrapper> sheetDetail(String id) { return service.sheetDetail(id) .subscribeOn(Schedulers.io())//在子线程执行 .observeOn(AndroidSchedulers.mainThread());//在主线程观察(操作UI在主线程) }把之前的service去掉,然后用这个Api对象调用里面的方法即可
//请求歌单详情 // service.sheetDetail("1") Api.getInstance().sheetDetail("1") .subscribeOn(Schedulers.io())//在子线程执行 .observeOn(AndroidSchedulers.mainThread())//在主线程观察(操作UI在主线程) //接口方法里面对应的对象:SheetDetailWrapper .subscribe(new Observer<SheetDetailWrapper>() {//订阅回来的数据 @Override public void onSubscribe(Disposable d) { } /** * 请求成功 * * @param sheetDetailWrapper 解析回来的对象 */ @Override public void onNext(SheetDetailWrapper sheetDetailWrapper) { LogUtil.d(TAG, "request sheet detail success:" + sheetDetailWrapper.getData().getTitle()); } /** * 请求失败 * * @param e Error */ @Override public void onError(Throwable e) { e.printStackTrace(); // LogUtil.d(TAG,"request sheet detail failed:" + e.getLocalizedMessage()); //判断错误类型 if (e instanceof UnknownHostException) { ToastUtil.errorShortToast(R.string.error_network_unknown_host); } else if (e instanceof ConnectException) { ToastUtil.errorShortToast(R.string.error_network_connect); } else if (e instanceof SocketTimeoutException) { ToastUtil.errorShortToast(R.string.error_network_timeout); } else if (e instanceof HttpException) { HttpException exception = (HttpException) e; int code = exception.code(); if (code == 401) { ToastUtil.errorShortToast(R.string.error_network_not_auth); } else if (code == 403) { ToastUtil.errorShortToast(R.string.error_network_not_permission); } else if (code == 404) { ToastUtil.errorShortToast(R.string.error_network_not_found); } else if (code >= 500) { ToastUtil.errorShortToast(R.string.error_network_server); } else { ToastUtil.errorShortToast(R.string.error_network_unknown); } } else { ToastUtil.errorShortToast(R.string.error_network_unknown); } } /** * 请求结束 */ @Override public void onComplete() { } });完成网络请求加载显示
前面已经学习了RxJava的回调方法,所以可以在onSubscribe方法显示加载提示; 在onNext和onError方法中隐藏加载提示。
//请求歌单详情 // service.sheetDetail("1") Api.getInstance().sheetDetail("1") .subscribeOn(Schedulers.io())//在子线程执行 .observeOn(AndroidSchedulers.mainThread())//在主线程观察(操作UI在主线程) //接口方法里面对应的对象:SheetDetailWrapper .subscribe(new Observer<SheetDetailWrapper>() {//订阅回来的数据 @Override public void onSubscribe(Disposable d) { Log.d(TAG, "onSubscribe: "); //显示加载对话框 LoadingUtil.showLoading(getMainActivity()); } /** * 请求成功 * * @param sheetDetailWrapper 解析回来的对象 */ @Override public void onNext(SheetDetailWrapper sheetDetailWrapper) { LogUtil.d(TAG, "onNext:" + sheetDetailWrapper.getData().getTitle()); LoadingUtil.hideLoading();//隐藏加载提示框 } /** * 请求失败 * * @param e Error */ @Override public void onError(Throwable e) { Log.d(TAG, "onError: "); LoadingUtil.hideLoading();//隐藏加载提示框 } }测试
确认请求网络能显示,并隐藏。
分析:
可以当成一个大对象(也就是外围是一个类),然后对象里面的成员变量又是一个对象的话,按照对象的思维去解析;否则直接在大对象的类里面直接添加一个成员变量即可。
我们先折叠json数据 我们可以创建一个类
public class SheetListWrapper { }依次张开 这个data是个数组(当做集合处理),这个大对象里面的data成员变量是一个集合。
那集合里面的item又是一个对象,所以我们又得定义一个item对象Sheet(这个之前定义的)
//public class Sheet extends BaseModel { public class Sheet extends BaseMultiItemEntity { //id可以删除了,因为已经定义到父类了 // /** // * 歌单Id // */ // private String id;//这个用字符串类型,防止以后id变为字符串了,不好搞 /** * 歌单标题 */ private String title; /** * 歌单封面 */ private String banner; /** * 描述 */ private String description; }我们依次展开 可以看到
所以最总的
这里记得把set get方法加上
public class SheetListWrapper { /** * 歌单列表 */ private List<Sheet> data; public List<Sheet> getData() { return data; } public void setData(List<Sheet> data) { this.data = data; } }请求单个item数据的model 方法同上。 这里顺便附上代码
/** * 歌单详情包裹对象 * <p> * 只是用来测试 */ public class SheetDetailWrapper { /** * 歌单详情 */ private Sheet data; //这里返回的是单个歌单Sheet,//SheetListWrapper那边返回的是多个歌单,也就是list, public Sheet getData() { return data; } public void setData(Sheet data) { this.data = data; } }item里面的model类
//public class Sheet extends BaseModel { public class Sheet extends BaseMultiItemEntity { //id可以删除了,因为已经定义到父类了 // /** // * 歌单Id // */ // private String id;//这个用字符串类型,防止以后id变为字符串了,不好搞 /** * 歌单标题 */ private String title; /** * 歌单封面 */ private String banner; /** * 描述 */ private String description; }Api类
public class Api { .... /** * 歌单列表 * * @return 返回Observable<SheetListWrapper> */ public Observable<SheetListWrapper> sheets() { return service.sheets() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()); }前面我们也说了,项目中所有的网络,请求最外层都有一层包装,真实的数据在data里面,如果是详情,data就是一个对象,如果是列表,data就是一个数组;前面的解析歌单详情的时候,还要给歌单详情外面创建一个包装类,那如果其他对象也按照这种方式实现的话,第一个是要创建很多的包装类,同时外面的包装类是重复的,所以说,可以创建一个通用的包装类,通过泛型的方式指定里面的内容
前面看到每个网络请求,最外层都有可能有,message,status两个字段,他们是必要的时候才有,还有一个data字段,只是不同的接口,类型不一样;所以我们可以创建一个BaseResponse。
创建BaseResponse
/** * 通用网络请求响应模型 * <p> * 前面看到每个网络请求,最外层都有可能有,message,status两个字段,他们是必要的时候才有, * 还有一个data字段,只是不同的接口,类型不一样;所以我们可以创建一个BaseResponse。 */ public class BaseResponse { /** * 状态码 * <p> * 只有发生了错误才会有 * <p> * 如果用int类型的话,全局变量会默认初始化,这个值就默认为0了 * 而我们不想为0,让发生了错误的时候才会值(不想有值,出错了才有值) * 所以我们这里定义为引用类型Integer,就会默认初始化为null */ private Integer status; /** * 出错的提示信息 * <p> * 发生了错误不一定有 */ private String message; public Integer getStatus() { return status; } public void setStatus(Integer status) { this.status = status; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } }创建详情对象 创建一个DetailResponse,它用来解析详情这类网络请求。
/** * 详情网络请求解析类 * <p> * 继承BaseResponse * 定义了一个泛型T */ public class DetailResponse<T> extends BaseResponse { /** * 真实数据 * 他的类型就是泛型 */ private T data; public T getData() { return data; } public void setData(T data) { this.data = data; } }前面创建了BaseResponse,也实现了详情网络请求的包装,那对应列表请求来说,其实也只有data不一样,所以可以创建一个ListResponse。
/** * 解析列表网络请求 */ public class ListResponse<T> extends BaseResponse { /** * 定义一个列表 * <p> * 里面的对象使用了泛型 */ private List<T> data;//名字要和服务器端的一样 public List<T> getData() { return data; } public void setData(List<T> data) { this.data = data; } }统一将里面的model类改成 DetailResponse
* <p> * 之所以调用接口还能返回数据 * 是因为Retrofit框架内部处理了 * 这里不讲解原理 * 在《详解Retrofit》课程中讲解 */ public interface Service { /** * 歌单详情 * * @param id {id} 这样表示id,表示的@Path("id")里面的id, * path里面的id其实就是接收后面String id 的值 * <p> * 一般情况下,三个名称都写成一样,比如3个都是id * <p> * //Retrofit如何知道我们传入的是id呢,其实通过Retrofit注解@Path("id")知道 * (应该是相等于限定了id,其他的应该会报错) * <p> * Observable<SheetDetailWrapper>:相等于把json数据转换成这个SheetDetailWrapper类型的对象 * Observable:rxjava里面的类 */ @GET("v1/sheets/{id}") // Observable<SheetDetailWrapper> sheetDetail(@Path("id") String id); Observable<DetailResponse<Sheet>> sheetDetail(@Path("id") String id); } /** * 歌单详情 * * @param id 传入的第几个歌曲Id * @return 返回Retrofit接口实例 里面的方法返回的对象 */ public Observable<DetailResponse<Sheet>> sheetDetail(String id) { return service.sheetDetail(id) .subscribeOn(Schedulers.io())//在子线程执行 .observeOn(AndroidSchedulers.mainThread());//在主线程观察(操作UI在主线程) } //请求DetailResponse歌单详情(封装接口后使用) Api.getInstance().sheetDetail("1") .subscribe(new Observer<DetailResponse<Sheet>>() { @Override public void onSubscribe(Disposable d) { } @Override public void onNext(DetailResponse<Sheet> sheetDetailResponse) { LogUtil.d(TAG, "onNext:" + sheetDetailResponse.getData().getTitle()); } @Override public void onError(Throwable e) { } @Override public void onComplete() { } });类似的,使用也把Model类改成 ListResponse 即可。
最后还要测试能正常的运行。
请求网络的时候先通过Api的getInstance方法,获取到Api类,然后调用相应的方法,他返回的是Observable对象,然后重写onSucceeded只关注成功,通过onFailed关注失败。
可以看到现在使用Observer的时候,都需要实现全部方法,这样每次使用的时候比较麻烦,所以可以借鉴Java中的设计,就是给接口创建一个默认实现类,这个类只是简单的实现这些方法。
/** * 通用实现Observer里面的方法 * <p> * 目的是避免要实现所有方法 */ public class ObserverAdapter<T> implements Observer<T> { /** * 开始订阅了执行(可以简单理解为开始执行前) * * @param d Disposable对象 */ @Override public void onSubscribe(Disposable d) { } /** * 下一个Observer(当前Observer执行完成了) * * @param t 具体的对象或者集合 */ @Override public void onNext(T t) { } /** * 发生了错误(执行失败了) * * @param e Throwable对象 */ @Override public void onError(Throwable e) { } /** * 回调了onNext方法后调用 */ @Override public void onComplete() { } } //使用ObserverAdapter Api.getInstance().sheetDetail("1") .subscribe(new ObserverAdapter<DetailResponse<Sheet>>() { @Override public void onNext(DetailResponse<Sheet> sheetDetailResponse) { super.onNext(sheetDetailResponse); LogUtil.d(TAG, "onNext:" + sheetDetailResponse.getData().getTitle()); } });可以看到只需要重写需要的方法就行了。
最后确保能正确的运行
使用:
//使用HttpObserver Api.getInstance().sheetDetail("1") .subscribe(new HttpObserver<DetailResponse<Sheet>>() { @Override public void onSucceeded(DetailResponse<Sheet> data) { LogUtil.d(TAG, "onSucceeded:" + data.getData().getTitle()); } });确保能运行成功
HttpObserver类
package com.ixuea.courses.mymusicold.listener; import android.text.TextUtils; import com.ixuea.courses.mymusicold.R; import com.ixuea.courses.mymusicold.domain.response.BaseResponse; import com.ixuea.courses.mymusicold.util.LogUtil; import com.ixuea.courses.mymusicold.util.ToastUtil; import java.net.ConnectException; import java.net.SocketTimeoutException; import java.net.UnknownHostException; import retrofit2.HttpException; /** * 网络请求Observer * <p> * 有些错误不方便放在ObserverAdapter处理,所以定义了HttpObserver类这个是继承ObserverAdapter<T>的 * <p> * 本类有成功与失败方法 */ public abstract class HttpObserver<T> extends ObserverAdapter<T> { private static final String TAG = "HttpObserver"; /** * 请求成功 * * @param data 数据(对象或者集合) * Succeeded:success 后面的2个s改成ed * 改成抽象类,让子类实现 */ public abstract void onSucceeded(T data); /** * 请求失败 * * @param data 数据(对象或者集合) * @param e Throwable * @return boolean */ public boolean onFailed(T data, Throwable e) { return false; } /** * 如果要404错误,只需要把http://dev-my-cloud-music-api-rails.ixuea.com/v1/sheets中的sheets改下,就变成404 * 如果要500错误,只要用户名不存在就会变成500错误 * 比如: * http://dev-my-cloud-music-api-rails.ixuea.com/v1/users/-1?nickname=11111111这个地址就是500错误 * <p> * 总结:发生错误都会发生在onError中 * * @param t 具体的对象或者集合 */ @Override public void onNext(T t) { super.onNext(t); LogUtil.d(TAG, "onNext:" + t); /** * 已经请求成功,但是登录失败了 * 但是如果用户名 密码错误会返回false * * 可以理解为200~299之间的值就会返回到这里来 * 这里面的错误,可以先看看,到时候遇到再说 */ if (isSucceeded(t)) { //请求正常 onSucceeded(t); } else { //请求出错了(就是登录失败了) requestErrorHandler(t, null); } } @Override public void onError(Throwable e) { super.onError(e); LogUtil.d(TAG, "onError:" + e.getLocalizedMessage()); requestErrorHandler(null, e);//第一个参数为null,出错了,没有数据对象传递到这个方法里面来 } /** * 判断网络请求是否成功 * * @param t T * @return 是否成功 */ private boolean isSucceeded(T t) { //比如返回的code=200,(比如用户名 密码错误) if (t instanceof BaseResponse) { //判断具体的业务请求是否成功 BaseResponse baseResponse = (BaseResponse) t; //没有状态码表示成功 //这是我们和服务端的一个规定(一般情况下status等于0才是成功,我们这里是null才成功) //一般==null,则return true;否则return false return baseResponse.getStatus() == null; } return false; } /** * 处理错误网络请求 * * @param data T 数据模型对象 * @param error Throwable错误对象 */ private void requestErrorHandler(T data, Throwable error) { if (error != null) { //判断错误类型 if (error instanceof UnknownHostException) { ToastUtil.errorShortToast(R.string.error_network_unknown_host); } else if (error instanceof ConnectException) { ToastUtil.errorShortToast(R.string.error_network_connect); } else if (error instanceof SocketTimeoutException) { ToastUtil.errorShortToast(R.string.error_network_timeout); } else if (error instanceof HttpException) { HttpException exception = (HttpException) error; int code = exception.code(); if (code == 401) { ToastUtil.errorShortToast(R.string.error_network_not_auth); } else if (code == 403) { ToastUtil.errorShortToast(R.string.error_network_not_permission); } else if (code == 404) { ToastUtil.errorShortToast(R.string.error_network_not_found); } else if (code >= 500) { ToastUtil.errorShortToast(R.string.error_network_server); } else { ToastUtil.errorShortToast(R.string.error_network_unknown); } } else { ToastUtil.errorShortToast(R.string.error_network_unknown); } } else {//error为null(这个时候是走onNext-->else-->requestErrorHandler) //(登录失败的这种错误) if (data instanceof BaseResponse) { //判断具体的业务请求是否成功 BaseResponse response = (BaseResponse) data; if (TextUtils.isEmpty(response.getMessage())) { //没有错误提示信息(message可能没有错误提示信息) (未知错误,请稍后再试!) ToastUtil.errorShortToast(R.string.error_network_unknown); } else {//message不为空 ToastUtil.errorShortToast(response.getMessage()); } } } } }上面Service接口中用到的User模型类,目前还没有内容,因为我们这里是测试错误。‘’
/** * 用户详情 */ public class User { }API类中的
/** * 歌单详情 * * @param id 传入的第几个歌曲Id * @return 返回Retrofit接口实例 里面的方法返回的对象 */ public Observable<DetailResponse<Sheet>> sheetDetail(String id) { return service.sheetDetail(id) .subscribeOn(Schedulers.io())//在子线程执行 .observeOn(AndroidSchedulers.mainThread());//在主线程观察(操作UI在主线程) } /** * 歌单详情 * * @param id 传入的第几个歌曲Id * @return 返回Retrofit接口实例 里面的方法返回的对象 */ public Observable<DetailResponse<User>> userDetail(String id, String nickname) { //添加查询参数 HashMap<String, String> data = new HashMap<>(); if (StringUtils.isNotBlank(nickname)) { //如果昵称不为空才添加 // nickname=11111111 键Constant.NICKNAME对应nickname; 值nickname对应11111111 data.put(Constant.NICKNAME, nickname); } return service.userDetail(id, data) .subscribeOn(Schedulers.io())//在子线程执行 .observeOn(AndroidSchedulers.mainThread());//在主线程观察(操作UI在主线程) }在登录按钮 点击事件里面 测试
// //使用HttpObserver 和 404 // Api.getInstance().sheetDetail("1") // .subscribe(new HttpObserver<DetailResponse<Sheet>>() { // @Override // public void onSucceeded(DetailResponse<Sheet> data) { // LogUtil.d(TAG, "onSucceeded:" + data.getData().getTitle()); // } // }); //模拟500错误 用户名错误 Api.getInstance().userDetail("-1", "1111111111") .subscribe(new HttpObserver<DetailResponse<User>>() { @Override public void onSucceeded(DetailResponse<User> data) { LogUtil.d(TAG, "onSucceeded:" + data.getData()); } });最后确保能正确的运行。
有些时候,我们可能希望自定义错误处理,而现在默认是,出错了就在父类处理了。
如何实现?
可以使用onFailed方法的返回值来实现,可以这样,如果返回true表示子类处理错误,如果返回false父类处理错误。
//模拟500错误 用户名错误 Api.getInstance().userDetail("-1", "1111111111") .subscribe(new HttpObserver<DetailResponse<User>>() { @Override public void onSucceeded(DetailResponse<User> data) { LogUtil.d(TAG, "onSucceeded:" + data.getData()); } @Override public boolean onFailed(DetailResponse<User> data, Throwable e) { LogUtil.d(TAG, "onFailed:" + e); // return super.onFailed(data, e);//调用父类,内部处理错误 //return true 表示:手动处理错误 return true;//外部处理,(就是说内部的那个提示没有弹出来) } });HttpObserver 类
/** * 网络请求Observer * <p> * 有些错误不方便放在ObserverAdapter处理,所以定义了HttpObserver类这个是继承ObserverAdapter<T>的 * <p> * 本类有成功与失败方法 */ public abstract class HttpObserver<T> extends ObserverAdapter<T> { private static final String TAG = "HttpObserver"; /** * 请求成功 * * @param data 数据(对象或者集合) * Succeeded:success 后面的2个s改成ed * 改成抽象类,让子类实现 */ public abstract void onSucceeded(T data); /** * 请求失败 * * @param data 数据(对象或者集合) * @param e Throwable * @return boolean false :表示父类(本类)要处理错误(内部处理);true:表示子类要处理错误(外部处理) */ public boolean onFailed(T data, Throwable e) { return false; } /** * 如果要404错误,只需要把http://dev-my-cloud-music-api-rails.ixuea.com/v1/sheets中的sheets改下,就变成404 * 如果要500错误,只要用户名不存在就会变成500错误 * 比如: * http://dev-my-cloud-music-api-rails.ixuea.com/v1/users/-1?nickname=11111111这个地址就是500错误 * <p> * 总结:发生错误都会发生在onError中 * * * 已经请求成功,但是登录失败了 * 但是如果用户名 密码错误会返回false * * 可以理解为200~299之间的值就会返回到这里来 * 这里面的错误,可以先看看,到时候遇到再说 * @param t 具体的对象或者集合 */ @Override public void onNext(T t) { super.onNext(t); LogUtil.d(TAG, "onNext:" + t); if (isSucceeded(t)) { //请求正常 onSucceeded(t); } else { //请求出错了(就是登录失败了) requestErrorHandler(t, null); } } @Override public void onError(Throwable e) { super.onError(e); LogUtil.d(TAG, "onError:" + e.getLocalizedMessage()); requestErrorHandler(null, e);//第一个参数为null,出错了,没有数据对象传递到这个方法里面来 } /** * 判断网络请求是否成功 * * @param t T * @return 是否成功 */ private boolean isSucceeded(T t) { //比如返回的code=200,(比如用户名 密码错误) if (t instanceof BaseResponse) { //判断具体的业务请求是否成功 BaseResponse baseResponse = (BaseResponse) t; //没有状态码表示成功 //这是我们和服务端的一个规定(一般情况下status等于0才是成功,我们这里是null才成功) //一般==null,则return true;否则return false return baseResponse.getStatus() == null; } return false; } /** * 处理错误网络请求 * * @param data T 数据模型对象 * @param error Throwable错误对象 */ private void requestErrorHandler(T data, Throwable error) { if (onFailed(data, error)) {//fasle就会走else,父类(可以说本类)处理错误;true:就是外部处理错误 //回调了请求失败方法 //并且该方法返回了true //返回true就表示外部手动处理错误 //那我们框架内部就不用做任何事情了 } else { if (error != null) { //判断错误类型 if (error instanceof UnknownHostException) { ToastUtil.errorShortToast(R.string.error_network_unknown_host); } else if (error instanceof ConnectException) { ToastUtil.errorShortToast(R.string.error_network_connect); } else if (error instanceof SocketTimeoutException) { ToastUtil.errorShortToast(R.string.error_network_timeout); } else if (error instanceof HttpException) { HttpException exception = (HttpException) error; int code = exception.code(); if (code == 401) { ToastUtil.errorShortToast(R.string.error_network_not_auth); } else if (code == 403) { ToastUtil.errorShortToast(R.string.error_network_not_permission); } else if (code == 404) { ToastUtil.errorShortToast(R.string.error_network_not_found); } else if (code >= 500) { ToastUtil.errorShortToast(R.string.error_network_server); } else { ToastUtil.errorShortToast(R.string.error_network_unknown); } } else { ToastUtil.errorShortToast(R.string.error_network_unknown); } } else {//error为null(这个时候是走onNext-->else-->requestErrorHandler) //(登录失败的这种错误) if (data instanceof BaseResponse) { //判断具体的业务请求是否成功 BaseResponse response = (BaseResponse) data; if (TextUtils.isEmpty(response.getMessage())) { //没有错误提示信息(message可能没有错误提示信息) (未知错误,请稍后再试!) ToastUtil.errorShortToast(R.string.error_network_unknown); } else {//message不为空 ToastUtil.errorShortToast(response.getMessage()); } } } } } }HttpUtil 类
/** * 网络请求相关辅助方法 */ public class HttpUtil { /** * 网络请求错误处理 * * @param data Object * @param error Throwable * 这个static后面的<T>是必须要的,否则参数那里会找不到这个泛型T * 也可以不用泛型T,直接用Object */ // public static <T> void handlerRequest(T data, Throwable error) { public static void handlerRequest(Object data, Throwable error) { if (error != null) { //判断错误类型 if (error instanceof UnknownHostException) { ToastUtil.errorShortToast(R.string.error_network_unknown_host); } else if (error instanceof ConnectException) { ToastUtil.errorShortToast(R.string.error_network_connect); } else if (error instanceof SocketTimeoutException) { ToastUtil.errorShortToast(R.string.error_network_timeout); } else if (error instanceof HttpException) { HttpException exception = (HttpException) error; int code = exception.code(); if (code == 401) { ToastUtil.errorShortToast(R.string.error_network_not_auth); } else if (code == 403) { ToastUtil.errorShortToast(R.string.error_network_not_permission); } else if (code == 404) { ToastUtil.errorShortToast(R.string.error_network_not_found); } else if (code >= 500) { ToastUtil.errorShortToast(R.string.error_network_server); } else { ToastUtil.errorShortToast(R.string.error_network_unknown); } } else { ToastUtil.errorShortToast(R.string.error_network_unknown); } } else {//error为null(这个时候是走onNext-->else-->requestErrorHandler) //(登录失败的这种错误) if (data instanceof BaseResponse) { //判断具体的业务请求是否成功 BaseResponse response = (BaseResponse) data; if (TextUtils.isEmpty(response.getMessage())) { //没有错误提示信息(message可能没有错误提示信息) (未知错误,请稍后再试!) ToastUtil.errorShortToast(R.string.error_network_unknown); } else {//message不为空 ToastUtil.errorShortToast(response.getMessage()); } } } } }在原来的HTTPObserver上 改:
@Override public void onNext(T t) { super.onNext(t); LogUtil.d(TAG, "onNext:" + t); if (isSucceeded(t)) { //请求正常 onSucceeded(t); } else { //请求出错了(就是登录失败了) handlerRequest(t, null); } } @Override public void onError(Throwable e) { super.onError(e); LogUtil.d(TAG, "onError:" + e.getLocalizedMessage()); handlerRequest(null, e);//第一个参数为null,出错了,没有数据对象传递到这个方法里面来 } /** * 处理错误网络请求 * * @param data T 数据模型对象 * @param error Throwable错误对象 */ private void handlerRequest(T data, Throwable error) { if (onFailed(data, error)) {//fasle就会走else,父类(可以说本类)处理错误;true:就是外部处理错误 //回调了请求失败方法 //并且该方法返回了true //返回true就表示外部手动处理错误 //那我们框架内部就不用做任何事情了 } else { //调用工具处理错误(这个是父类,内部处理错误) HttpUtil.handlerRequest(data, error); } }子类这里我们也调用它的工具类HttpUtil来处理错误。 注意:这样的话,处理错误的逻辑是和父类一样的(因为都封装到HTTPUtil这个工具类里面了),但是这里我们还是返回true,调用下HTTPUtil类方法。
//模拟500错误 用户名错误 Api.getInstance().userDetail("-1", "1111111111") .subscribe(new HttpObserver<DetailResponse<User>>() { @Override public void onSucceeded(DetailResponse<User> data) { LogUtil.d(TAG, "onSucceeded:" + data.getData()); } @Override public boolean onFailed(DetailResponse<User> data, Throwable e) { LogUtil.d(TAG, "onFailed:" + e); // return super.onFailed(data, e);//调用父类,内部处理错误 //return true 表示:手动处理错误 //调用工具类处理错误(这个时候会有提示弹出来) HttpUtil.handlerRequest(data, e); return true;//外部处理,(就是说内部的那个提示没有弹出来) } });HttpObserver类
package com.ixuea.courses.mymusicold.listener; import com.ixuea.courses.mymusicold.activity.BaseCommonActivity; import com.ixuea.courses.mymusicold.domain.response.BaseResponse; import com.ixuea.courses.mymusicold.util.HttpUtil; import com.ixuea.courses.mymusicold.util.LoadingUtil; import com.ixuea.courses.mymusicold.util.LogUtil; import io.reactivex.disposables.Disposable; /** * 网络请求Observer * <p> * 有些错误不方便放在ObserverAdapter处理,所以定义了HttpObserver类这个是继承ObserverAdapter<T>的 * <p> * 本类有成功与失败方法 */ public abstract class HttpObserver<T> extends ObserverAdapter<T> { private static final String TAG = "HttpObserver"; //final 修饰的成员变量,必须要初始化一次,而这里有个空构造方法,有可能没有初始化成员变量, // 所以final修饰的成员变量在编译的时候可能没有初始化,故报错 // private final BaseCommonActivity activity; // private final boolean isShowLoading; private BaseCommonActivity activity;//公共Activity private boolean isShowLoading;//是否显示加载对话框 /** * 无参构造方法 添加这个的主要目的是:父类中有个有参构造方法(这时父类没有无参构造方法,子类在new无参构造的时候就会报错,所以这里要添加无参构造方法) */ public HttpObserver() { } /** * 有参构造方法 * * @param activity BaseCommonActivity * @param isShowLoading 是否显示加载提示框 */ public HttpObserver(BaseCommonActivity activity, boolean isShowLoading) { this.activity = activity; this.isShowLoading = isShowLoading; } /** * 请求成功 * * @param data 数据(对象或者集合) * Succeeded:success 后面的2个s改成ed * 改成抽象类,让子类实现 */ public abstract void onSucceeded(T data); /** * 请求失败 * * @param data 数据(对象或者集合) * @param e Throwable * @return boolean false :表示父类(本类)要处理错误(内部处理);true:表示子类要处理错误(外部处理) */ public boolean onFailed(T data, Throwable e) { return false; } @Override public void onSubscribe(Disposable d) { super.onSubscribe(d); if (isShowLoading) { //显示加载对话框 LoadingUtil.showLoading(activity); } } /** * 如果要404错误,只需要把http://dev-my-cloud-music-api-rails.ixuea.com/v1/sheets中的sheets改下,就变成404 * 如果要500错误,只要用户名不存在就会变成500错误 * 比如: * http://dev-my-cloud-music-api-rails.ixuea.com/v1/users/-1?nickname=11111111这个地址就是500错误 * <p> * 总结:发生错误都会发生在onError中 * * * 已经请求成功,但是登录失败了 * 但是如果用户名 密码错误会返回false * * 可以理解为200~299之间的值就会返回到这里来 * 这里面的错误,可以先看看,到时候遇到再说 * @param t 具体的对象或者集合 */ @Override public void onNext(T t) { super.onNext(t); LogUtil.d(TAG, "onNext:" + t); //检查是否需要隐藏加载提示框(其他地方如onError中用到,提取到一个方法中) checkHideLoading(); if (isSucceeded(t)) { //请求正常 onSucceeded(t); } else { //请求出错了(就是登录失败了) handlerRequest(t, null); } } @Override public void onError(Throwable e) { super.onError(e); LogUtil.d(TAG, "onError:" + e.getLocalizedMessage()); //检查是否需要隐藏加载提示框 checkHideLoading(); handlerRequest(null, e);//第一个参数为null,出错了,没有数据对象传递到这个方法里面来 } /** * 判断网络请求是否成功 * * @param t T * @return 是否成功 */ private boolean isSucceeded(T t) { //比如返回的code=200,(比如用户名 密码错误) if (t instanceof BaseResponse) { //判断具体的业务请求是否成功 BaseResponse baseResponse = (BaseResponse) t; //没有状态码表示成功 //这是我们和服务端的一个规定(一般情况下status等于0才是成功,我们这里是null才成功) //一般==null,则return true;否则return false return baseResponse.getStatus() == null; } return false; } /** * 处理错误网络请求 * * @param data T 数据模型对象 * @param error Throwable错误对象 */ private void handlerRequest(T data, Throwable error) { if (onFailed(data, error)) {//fasle就会走else,父类(可以说本类)处理错误;true:就是外部处理错误 //回调了请求失败方法 //并且该方法返回了true //返回true就表示外部手动处理错误 //那我们框架内部就不用做任何事情了 } else { //调用工具处理错误(这个是父类,内部处理错误) HttpUtil.handlerRequest(data, error); } } /** * 检查是否需要隐藏加载提示框 */ private void checkHideLoading() { if (isShowLoading) { LoadingUtil.hideLoading(); } } }使用
//测试自动显示加载对话框 Api.getInstance().sheetDetail("1") .subscribe(new HttpObserver<DetailResponse<Sheet>>(getMainActivity(), true) { //false表示不显示 // .subscribe(new HttpObserver<DetailResponse<Sheet>>(getMainActivity(),false) { //无参构造方法(没有参数,也是不显示提示加载对话框) // .subscribe(new HttpObserver<DetailResponse<Sheet>>(getMainActivity(),false) { @Override public void onSucceeded(DetailResponse<Sheet> data) { LogUtil.d(TAG, "onSucceeded:" + data.getData().getTitle()); } });