配置Retrofit网络框架及其使用

**

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());
            }
        });
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值