**
1.配置service接口
网络请求需要哪些信息?
**
一般网络请求,会需要如下这些信息:
请求的网址
请求方式;是GET请求,还是POST请求
请求参数
参数传递方式;是通过表单方式传递,还是通过JSON方式传递
请求头
如何配置?
将这些信息写到一个接口中。
创建Model
/**
* 歌单详情包裹对象
* <p>
* 只是用来测试
*/
public class SheetDetailWrapper {
/**
* 歌单详情
*/
private Sheet data;
public Sheet getData() {
return data;
}
public void setData(Sheet data) {
this.data = data;
}
}
/**
* 歌单对象
*/
public class Sheet {
/**
* 歌单Id
*/
private String id;//这个用字符串类型,防止以后id变为字符串了,不好搞
/**
* 歌单标题
*/
private String title;
/**
* 歌单封面
*/
private String banner;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getBanner() {
return banner;
}
public void setBanner(String banner) {
this.banner = banner;
}
}
{
"data": {
"id": 1,
"title": "这世上所有的歌zheshishangtest",
"banner": "assets/list1.jpg",
"description": "这是因为iOS9引入了新特性App Transport Security (ATS),他要求App内网络请求必须使用HTTPS协议。解决方法是要么改为HTTPS,要么声明可以使用HTTP,可以声明部分使用HTTP,也可以所有;但需要说明的是如果APP内所有请求都是HTTP,那么如果要上架App Store的时候基本都会被拒。",
"clicks_count": 16773,
"collections_count": 28,
"comments_count": 172,
"user": {
"id": 1,
"nickname": "爱学啊dev666",
"avatar": "67133b479b364e8c9bfceb58015cd71f.jpg",
"gender": 0
},
"songs": [
{
"id": 11,
"title": "忘不了的温柔",
"banner": "assets/yuanfengredehuo_andongyang.jpg",
"uri": "assets/wangbiliaodewenrou_andongyang.mp3",
"clicks_count": 0,
"comments_count": 0,
"created_at": "2019-09-17T05:52:50.000Z",
"user": {
"id": 1,
"nickname": "爱学啊dev666",
"avatar": "67133b479b364e8c9bfceb58015cd71f.jpg"
},
"singer": {
"id": 43,
"nickname": "安东阳",
"avatar": "assets/andongyang.jpg"
}
},
{
"id": 10,
"title": "伤心的站台",
"banner": "assets/yuanfengredehuo_andongyang.jpg",
"uri": "assets/shangxingzhantai_andongyang.mp3",
"clicks_count": 0,
"comments_count": 0,
"created_at": "2019-09-17T05:50:50.000Z",
"user": {
"id": 1,
"nickname": "爱学啊dev666",
"avatar": "67133b479b364e8c9bfceb58015cd71f.jpg"
},
"singer": {
"id": 43,
"nickname": "安东阳",
"avatar": "assets/andongyang.jpg"
}
}
]
}
创建Service接口
我们希望将项目中,所有请求的信息都放到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);
}
如果需要更多接口,只需要在这里添加就行了;这里暂时只添加了一个接口,目的是后面封装网络框架的时候会用到,其他的接口,用到了在添加。
2.如何使用Retrofit请求网络
//测试网络请求
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 service = retrofit.create(Service.class);
//请求歌单详情
service.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());
}
/**
* 请求结束
*/
@Override
public void onComplete() {
}
});
测试
运行项目,点击按钮,就可以在日志中查看到歌单为1的JSON数据了。
2.2 网络请求错误 处理
代码演示:
在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)
因为我们上面代码判断了,所以手机上报错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异常,所以在这里代码判断就行了。
2.3 封装
1. 封装网络请求Api
可以把初始化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在主线程)
}
2. 使用
把之前的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() {
}
});
2.4 加载对话框
public class LoadingUtil {
private static ProgressDialog progressDialog;
/**
* 使用一个加载对话框,使用默认提示文字
*
* @param activity Activity
*/
public static void showLoading(Activity activity) {
showLoading(activity, activity.getString(R.string.loading));
}
/**
* 显示一个加载对话框(可以输入任何的message)
*
* @param activity Activity
* @param message Message
*/
private static void showLoading(Activity activity, String message) {
//判断activity为空或者已经销毁了
if (activity == null || activity.isFinishing()) {
return;
}
//判断是否显示了
if (progressDialog != null) {
//已经显示了 不需要再次显示
//就不再显示了
return;
}
//创建一个进度对话框
progressDialog = new ProgressDialog(activity);
progressDialog.setTitle("提示");//提示标题
progressDialog.setMessage(message);//提示信息
//点击外部弹窗不会自动隐藏
progressDialog.setCancelable(false);
progressDialog.show();
}
/**
* 隐藏加载提示对话框
*/
public static void hideLoading() {
if (progressDialog != null && progressDialog.isShowing()) {
progressDialog.hide();
progressDialog = null;
}
}
}
如何使用
//测试加载提示框
LoadingUtil.showLoading(getMainActivity());
//3秒中隐藏加载提示框
//因显示后无法点击后面的按钮(也就是当前页面点击的3s后关闭,在另一个页面关闭麻烦)
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
LoadingUtil.hideLoading();
}
}, 3000);
完成网络请求加载显示
前面已经学习了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();//隐藏加载提示框
}
}
测试
确认请求网络能显示,并隐藏。
2.5 请求数据
2.5.1 json数据分析
{
"data": [
{
"id": 1,
"title": "这世上所有的歌zheshishangtest",
"banner": "assets/list1.jpg",
"clicks_count": 16795,
"collections_count": 28,
"comments_count": 172,
"user": {
"id": 1,
"nickname": "爱学啊dev666",
"avatar": "67133b479b364e8c9bfceb58015cd71f.jpg",
"gender": 0
}
},
{
"id": 2,
"title": "我向来做事十拿九不稳 不信可以试试woxianglaitest",
"banner": "assets/list2.jpg",
"clicks_count": 2099,
"collections_count": 11,
"comments_count": 2,
"user": {
"id": 1,
"nickname": "爱学啊dev666",
"avatar": "67133b479b364e8c9bfceb58015cd71f.jpg",
"gender": 0
}
},
{
"id": 3,
"title": "网络离别歌曲最后还是离开了wangllibietest",
"banner": "assets/list3.jpg",
"clicks_count": 4433,
"collections_count": 10,
"comments_count": 0,
"user": {
"id": 1,
"nickname": "爱学啊dev666",
"avatar": "67133b479b364e8c9bfceb58015cd71f.jpg",
"gender": 0
}
},
{
"id": 9,
"title": "伤心的人怎可愿意听慢歌",
"banner": "assets/yuanfengredehuo_andongyang.jpg",
"clicks_count": 620,
"collections_count": 7,
"comments_count": 4,
"user": {
"id": 1,
"nickname": "爱学啊dev666",
"avatar": "67133b479b364e8c9bfceb58015cd71f.jpg",
"gender": 0
}
},
{
"id": 10,
"title": "你开始懂得了歌词",
"banner": "assets/shengburusi.jpg",
"clicks_count": 772,
"collections_count": 9,
"comments_count": 0,
"user": {
"id": 1,
"nickname": "爱学啊dev666",
"avatar": "67133b479b364e8c9bfceb58015cd71f.jpg",
"gender": 0
}
},
{
"id": 92,
"title": "2020",
"banner": null,
"clicks_count": 15,
"collections_count": 0,
"comments_count": 0,
"user": {
"id": 622,
"nickname": "凌锴",
"avatar": null,
"gender": 0
}
},
{
"id": 91,
"title": "最好听的歌单",
"banner": "https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=3489003925,2338759571&fm=26&gp=0.jpg",
"clicks_count": 120,
"collections_count": 1,
"comments_count": 0,
"user": {
"id": 516,
"nickname": "乐天gg",
"avatar": "7f71228986a340a8830cb778d2c9037b.jpg",
"gender": 10
}
},
{
"id": 90,
"title": "2020年度金曲",
"banner": "https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=3489003925,2338759571&fm=26&gp=0.jpg",
"clicks_count": 41,
"collections_count": 1,
"comments_count": 0,
"user": {
"id": 516,
"nickname": "乐天gg",
"avatar": "7f71228986a340a8830cb778d2c9037b.jpg",
"gender": 10
}
},
{
"id": 89,
"title": "最好听的粤语歌",
"banner": "https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=3489003925,2338759571&fm=26&gp=0.jpg",
"clicks_count": 29,
"collections_count": 1,
"comments_count": 0,
"user": {
"id": 516,
"nickname": "乐天gg",
"avatar": "7f71228986a340a8830cb778d2c9037b.jpg",
"gender": 10
}
},
{
"id": 88,
"title": "sfsfdx",
"banner": null,
"clicks_count": 20,
"collections_count": 0,
"comments_count": 0,
"user": {
"id": 118,
"nickname": "阿健",
"avatar": "95a189d10eb94da4b795585af183c774.jpg",
"gender": 10
}
}
]
}
分析:
可以当成一个大对象(也就是外围是一个类),然后对象里面的成员变量又是一个对象的话,按照对象的思维去解析;否则直接在大对象的类里面直接添加一个成员变量即可。
我们先折叠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;
}
}
2.5.2 请求数据并简单使用
1.创建模型数据,前面已经分析了
/**
* 歌单详情包裹对象
* <p>
* 只是用来测试
*/
//这里类这里还没有用到,先写上 这个是请求单个歌单详情时候用到的model类
public class SheetDetailWrapper {
/**
* 歌单详情
*/
private Sheet data;
//这里返回的是单个歌单Sheet,//SheetListWrapper那边返回的是多个歌单,也就是list,
public Sheet getData() {
return data;
}
public void setData(Sheet data) {
this.data = data;
}
}
/**
* 歌单列表模型
* <p>
* 只是用来测试
* <p>
* 地址:http://dev-my-cloud-music-api-rails.ixuea.com/v1/sheets
* 后面没有数字,有数字的是具体某个歌单
*/
public class SheetListWrapper {
/**
* 歌单列表
*/
private List<Sheet> data;
public List<Sheet> getData() {
return data;
}
public void setData(List<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;
}
2.接口配置
/**
* 网络接口配置
* <p>
* 之所以调用接口还能返回数据
* 是因为Retrofit框架内部处理了
* 这里不讲解原理
* 在《详解Retrofit》课程中讲解
*/
public interface Service {
/**
* 歌单列表
*/
@GET("v1/sheets")
Observable<SheetListWrapper> sheets();
}
Api类
public class Api {
....
/**
* 歌单列表
*
* @return 返回Observable<SheetListWrapper>
*/
public Observable<SheetListWrapper> sheets() {
return service.sheets()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
3.使用
//请求歌单列表
Api.getInstance()
.sheets()
.subscribe(new Observer<SheetListWrapper>() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(SheetListWrapper sheetListWrapper) {
LogUtil.d(TAG, "onNext:" + sheetListWrapper.getData().size());
}
@Override
public void onError(Throwable e) {
}
@Override
public void onComplete() {
}
});
}
2.5.3 封装请求数据Model
前面我们也说了,项目中所有的网络,请求最外层都有一层包装,真实的数据在data里面,如果是详情,data就是一个对象,如果是列表,data就是一个数组;前面的解析歌单详情的时候,还要给歌单详情外面创建一个包装类,那如果其他对象也按照这种方式实现的话,第一个是要创建很多的包装类,同时外面的包装类是重复的,所以说,可以创建一个通用的包装类,通过泛型的方式指定里面的内容
1.如何实现封装
前面看到每个网络请求,最外层都有可能有,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;
}
}
2.使用封装后请求
2.1单个对象详情使用
统一将里面的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() {
}
});
2.2 列表使用
类似的,使用也把Model类改成 ListResponse 即可。
最后还要测试能正常的运行。
2.5.4 封装网络请求回调
如果大家不知道从和下手,还是按照我们前面的方法,从使用的位置,也就是我们期望封装完成后的效果开始。
请求网络的时候先通过Api的getInstance方法,获取到Api类,然后调用相应的方法,他返回的是Observable对象,然后重写onSucceeded只关注成功,通过onFailed关注失败。
1.实现通用Observer回调
可以看到现在使用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());
}
});
可以看到只需要重写需要的方法就行了。
最后确保能正确的运行
2.实现HttpObserver回调
后面我们要实现自动网络错误处理,而这部分逻辑放到ObserverAdapter不太好,所以我们创建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 数据(对象或者集合) 比如传入进来的:DetailResponse<Sheet>模型对象类
* @param e Throwable
* @return boolean
*/
public boolean onFailed(T data, Throwable e) {
return false;
}
@Override
public void onNext(T t) {
super.onNext(t);
LogUtil.d(TAG, "onNext:" + t);
//TODO 处理错误
//请求正常
onSucceeded(t);
}
@Override
public void onError(Throwable e) {
super.onError(e);
LogUtil.d(TAG, "onError:" + e.getLocalizedMessage());
//TODO 处理错误
}
}
使用:
//使用HttpObserver
Api.getInstance().sheetDetail("1")
.subscribe(new HttpObserver<DetailResponse<Sheet>>() {
@Override
public void onSucceeded(DetailResponse<Sheet> data) {
LogUtil.d(TAG, "onSucceeded:" + data.getData().getTitle());
}
});
确保能运行成功
2.2.加强(添加错误处理)
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());
}
}
}
}
}
2.2.1使用(代码实现404和500错误,看看是否会弹出错误提示)
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/sheets11111111/{id}")//404
@GET("v1/sheets/{id}")
// Observable<SheetDetailWrapper> sheetDetail(@Path("id") String id);
Observable<DetailResponse<Sheet>> sheetDetail(@Path("id") String id);
//模拟500(也就是)
/**
* 用户详情
* //后面的查询参数会自动添加到后面的
* http://dev-my-cloud-music-api-rails.ixuea.com/v1/users/-1?nickname=11111111
* 比如这里吗的 问号?和参数nickname=11111111会添加到后面
* 因为这里有个参数 @QueryMap Map<String,String> data
*/
@GET("v1/users/{id}")
Observable<DetailResponse<User>> userDetail(@Path("id") String id, @QueryMap Map<String, String> data);
}
上面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());
}
});
最后确保能正确的运行。
2.3.手动处理错误(HttpObserver添加代码)
有些时候,我们可能希望自定义错误处理,而现在默认是,出错了就在父类处理了。
如何实现?
可以使用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());
}
}
}
}
}
}
3.重构错误到工具类(HttpUtil)
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;//外部处理,(就是说内部的那个提示没有弹出来)
}
});
4.网络请求加载对话框显示和隐藏(网络请求时显示,成功和失败都隐藏)
在HttpObserver的构造方法中添加一个参数控制是否显示对话框
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());
}
});