作者:catRuan(阮妹子)
联系方式:QQ:940472401 邮箱:940472401@qq.com
RNet源码分析:
Rnet的使用请参考我上一篇文章:使用retrofit2和rxjava封装网络框架RNet:(一)RNet的使用
本项目Github地址:https://github.com/CatRuan/RnetDemo
本文主要介绍Rnet的源码,阅读本部分内容前,需要知道的知识
1、retrofit2的基本使用 戳我
2、rxjava的基本使用 戳我
1、使用单例模式封装网络请求
先看一RNet的初始化,建议在application中进行
public class MyApplication extends Application {
private LightNet mLightNet;//网络请求框架
private NetService mNetService;//网络请求服务
@Override
public void onCreate() {
super.onCreate();
……
initNetWork(); //初始化网络请求
……
}
Map<String, String> mHeaders;
/**
* 初始化网络请求
*/
private void initNetWork() {
mHeaders = new HashMap<>();
mHeaders.put("Content-Type", "application/json;encoding-uft-8");
mHeaders.put("Accept", "application/json");
mLightNet = LightNet.getInstance(this, Constants.BASE_URL, mHeaders);
mNetService = mLightNet.create(NetService.class);
}
public NetService getNetService() {
return mNetService;
}
public LightNet getLightNet() {
return mLightNet;
}
}
使用单例模式封装RNet
Q:为什么使用单例模式?
A: 避免程序中生成过多网络请求实例;使用单例还有一个最重要的原因是保持同一个会话
Q:为什么提供两个getInstance方法?
A:getInstance(Application ctx, String url) 需要自己在实际使用时为每一个网络请求方法添加请求头
getInstance(Application ctx, String url, Map<String, String> headers)调用此方法传入请求头,后续操作中不需要每一个方法再添加头
Q:为什么要提供create方法?该方法返回的是什么?为什么要返回?
A:create方法返回的是retrofit中要求定义的网络请求接口的对象,我们在这个接口里定义具体的请求方法,之所以要在RNet中返回这个对象,是为了使用它调用我们自己定义的网络请求的方法(这句话非常拗口,继续往下看就知道原因了)
public class RNet {
private static Retrofit mRetrofit;
private volatile static RNet mNetClient;
private RNet(Application ctx, String url) {
this(ctx, url, null);
}
private RNet(Application ctx, String url, Map<String, String> headers) {
Retrofit.Builder builder = new Retrofit.Builder();
builder.baseUrl(url)
.addConverterFactory(FastJsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create());
OkHttpClient client = new OkHttpClient.Builder()
……
.build();
builder.client(client);
mRetrofit = builder.build();
}
/**
* 获取实例
*
* @param url baseUrl
* @return
*/
public static RNet getInstance(Application ctx, String url) {
if (mNetClient == null) {
synchronized (RNet.class) {
if (mNetClient == null) {
mNetClient = new RNet(ctx, url);
}
}
}
return mNetClient;
}
/**
* 获取实例
*
* @param url baseUrl
* @param headers 请求头
* @return
*/
public static RNet getInstance(Application ctx, String url, Map<String, String> headers) {
if (mNetClient == null) {
synchronized (RNet.class) {
if (mNetClient == null) {
mNetClient = new RNet(ctx, url, headers);
}
}
}
return mNetClient;
}
/**
* 创建请求示例
*
* @param service
* @param <T>
* @return
*/
public <T> T create(final Class<T> service) {
if (service == null) {
throw new RuntimeException("api service is null!");
}
if (mRetrofit == null) {
throw new RuntimeException("retrofit is null! has you init RNet ?");
}
return mRetrofit.create(service);
}
……
}
2、使用rxjava实现get和post请求
这里以post请求为例子,先展示使用的方法
首先定义一个网络服务接口,和refrofit2用法一模一样(不懂的去查资料吧)
/**
* 定义一个网络请求服务,和retrofit2要求的定义方式一模一样
*/
public interface NetService {
//在NetService中定义我们的方法,其方式与retrofit2要求的方式一模一样。
//注意抽象方法的返回值是Observable,这是因为我们使用了rxjava进行网络请求(后面会讲到)
@POST("base/test")
Observable<Request> test(@Body RequestBody request);
……
}
使用RNet进行网络请求:
private void test() {
//从application获取RNet示例
//注意不要调用RNet的getInstance获取示例,非要调用也么啥关系,就是大侠您传参麻烦
RNet mRNet = MyApplication.getContext().getRNet();
NetService mNetService = MyApplication.getContext().getNetService();
Request request = new Request("param1","param2","param3");//请求实体
//mRNet.objectToRequestBody(request)是RNet提供的一个工具方法,将对象转换为RequestBody
Observable<ResponseBody> task = mNetService.test(mRNet.objectToRequestBody(request));
NetCallback netCallback = new NetCallback<ResponseBody>(getApplication()) {//请求回调,后面会讲
@Override
public void onSuccess(ResponseBody response) {
//成功的处理
}
@Override
public void onFail(String message) {
//失败的处理
}
};
mRNet.post(getApplication(), task, netCallback);
}
post和get请求的源码,可以说大侠您要是懂rxjava,下面这段代码是非常简单了
/**
* get请求
*
* @param context 上下文
* @param observable 被观察者
* @param callback 请求回调
* @return 订阅
*/
public Subscription get(final Context context
, Observable<? extends Object> observable
, final NetCallback callback) {
Subscription subscription = observable.subscribeOn(Schedulers.newThread())//请求在新的线程中执行
.observeOn(AndroidSchedulers.mainThread())//最后在主线程中执行
.subscribe(callback);
return subscription;
}
/**
* post请求
*
* @param context 上下文
* @param observable 被观察者
* @param callback 请求回调
* @return 订阅
*/
public Subscription post(final Context context
, Observable<? extends Object> observable
, final NetCallback callback) {
return get(context, observable, callback);
}
3、网络请求回调NetCallBack的封装
在前文的演示中RNet提供了一个叫做NetCallBack的网络请求回调类,
Q: NetCallBack这个家伙本质是什么?它怎么实现的回调呢?
A: 哈哈,其实这个NetCallBack是Rxjava中Subscriber的子类,在NetCallBack中我定义了两个抽象方法分别处理请求成功和失败的情况
Q: 为什么不直接使用Subscriber?
A:因为Subscriber的回调方法有三个(感觉方法有点多了)而且对返回结果没做太多预处理。
为了代码优雅,更重要的是为了对放回结果进行充分的预处理,所以封装了NetCallBack
Q: ExceptionHandle.handleException(mContext, e)是什么鬼?
A: 使用ExceptionHandle.handleException方法对请求失败的原因进行分析,并以简洁的形式回传给回调方法。
Q: LoadingPage.hideLoadingDialog()又是什么鬼?
A: 这个嘛,主要是用来隐藏加载框的,当然大侠您在这里可以完全不理会它,后面还会细讲的
public abstract class NetCallback<T> extends Subscriber<T> {
private Context mContext;
public NetCallback(Context context) {
mContext = context;
}
@Override
public void onCompleted() {
Log.i("NetCallback", "onCompleted");
}
@Override
public void onError(Throwable e) {
//请求失败
e.printStackTrace();
// Log.i("NetCallback", "onError");
onFail(ExceptionHandle.handleException(mContext, e));
LoadingPage.hideLoadingDialog();
}
@Override
public void onNext(T response) {
//Log.i("NetCallback", "response");
//请求成功
onSuccess(response);
LoadingPage.hideLoadingDialog();
}
public abstract void onSuccess(T response);
public abstract void onFail(String message);
}
4、网络请求返回预处理
在实际的网络请求中,网络请求失败的界定是怎么样的呢?
我将请求失败分为两种:
①、网络连接失败:即网络连接根本就没有成功,原因可能是服务器异常啦,证书异常啦,没有网络啦……
②、网络连接成功,但返回结果失败:就是访问服务器成功,而且服务器也成功的返回了数据,只是服务器返回的消息里明确说明此次访问是失败的,原因可能就是参数带的不对,比如登录时候用户名和密码不匹配~
我认为上面两种失败,只有对第二种的处理才叫预处理,对于第一种的处理成为失败原因的预分析更恰当
Q:RNet对上述第二种情况(即服务器成功返回,但返回的消息里说明访问失败的)这种情况有预处理吗?
A:很遗憾,没有。这里解释一下原因,虽然网络上有很多此类的预处理代码,但是这类预处理意味着我们必须清楚的知道服务端返回的数据的结构,可以说返回数据的结构确实大同小异(比如肯定会有一个标志位,标明此次访问结果),无非就是标志位、具体信息等等,但是RNet作为一个通用的网络框架,不可能兼顾所有的返回数据结构(实际上也很难做到),一旦返回数据的结构稍有不同,那么RNet就很可能报错,所以这里含恨舍弃了
Q:如果一定要实现第二种情况的预处理该怎么做的?
A:这是非常简单的,我们可以在以下两个地方实现
一是自定义的转换器中(后面会讲怎么自定义数据转换器)
二是请求NetCallBack的OnNext方法中
5、不带进度的文件下载(适合小文件)
①、创建文件下载回调FileCallBack,文件下载回调不使用get和post方法的回调类NetCallback,这是为什么呢?
public abstract class FileCallBack {
private String destFileDir;
private String destFileName;
public FileCallBack(String destFileDir, String destFileName) {
this.destFileDir = destFileDir;
this.destFileName = destFileName;
}
public abstract void onSuccess();
public abstract void onFail(String msg);
/**
* @param body ResponseBody
*/
public void saveFile(ResponseBody body) {
InputStream is = null;
byte[] buf = new byte[2048];
int len;
FileOutputStream fos = null;
if (null == body) {
throw new RuntimeException("the ResponseBody is null");
} else {
try {
is = body.byteStream();
File dir = new File(destFileDir);
if (!dir.exists()) {
dir.mkdirs();
}
File file = new File(dir, destFileName);
fos = new FileOutputStream(file);
while ((len = is.read(buf)) != -1) {
fos.write(buf, 0, len);
}
fos.flush();
} catch (Exception e) {
e.printStackTrace();
//将catch到的异常重新抛出,使Rxjava的OnError捕获到该异常
throw Exceptions.propagate(e);
} finally {
try {
if (is != null) is.close();
if (fos != null) fos.close();
} catch (IOException e) {
// Log.e("saveFile", e.getMessage());
//将catch到的异常重新抛出,使Rxjava的OnError捕获到该异常
throw Exceptions.propagate(e);
}
}
}
}
}
很明显, FileCallBack与NetCallBack相比,多了一个保存文件的方法,有了这个方法我们就可以把网络上的流保存到本地。
(这里注意 throw Exceptions.propagate(e)这个语句,非常重要,它把已经catch的异常重新抛出,保证RxJava可以捕获到此异常,从而进行异常的预处理,后面会详细解释)
那么怎么把NetCallBack类与retrofit 和rxjava配合使用呢?
下面是下载方法的封装:
public class RNet {
……
/**
* 文件普通下载
*
* @param context 上下文
* @param observable 被观察者
* @param callBack 文件回调
*/
public Subscription download(final Context context, Observable<Response<ResponseBody>> observable, final FileCallBack callBack) {
Subscription subscription = observable.subscribeOn(Schedulers.io())//请求网络 在调度者的io线程
.observeOn(Schedulers.io()) //指定线程保存文件
.doOnNext(new Action1<Response<ResponseBody>>() {
@Override
public void call(Response<ResponseBody> response) {
Log.i("RNet", "response->" + response);
callBack.saveFile(response.body());
}
})
.observeOn(AndroidSchedulers.mainThread()) //在主线程中更新ui
.subscribe(new Subscriber<Response<ResponseBody>>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
LoadingPage.hideLoadingDialog();
e.printStackTrace();
callBack.onFail(ExceptionHandle.handleException(context, e));
}
@Override
public void onNext(Response<ResponseBody> response) {
LoadingPage.hideLoadingDialog();
if (response.code() == 200) {
callBack.onSuccess();
} else {
callBack.onFail(response.errorBody() + "");
}
}
});
return subscription;
}
……
}
我封装的下载方法中传入了三个参数,分别是什么呢?回顾一下下载方法的调用:
/**
* 文件下载,不带进度。适合小文件,如图片
*/
private void downLoad() {
Observable<Response<ResponseBody>> observable = mNetService.download();
FileCallBack fileCallBack = new FileCallBack(SD_PATH, "test.jpg") {
@Override
public void onSuccess() {
Toast.makeText(MainActivity.this, "下载文件成功", Toast.LENGTH_SHORT).show();
}
@Override
public void onFail(String msg) {
Toast.makeText(MainActivity.this, "下载失败:" + msg, Toast.LENGTH_SHORT).show();
}
};
mRNet.showLoadingDialog(this);
mDownloadSubscription = mRNet.download(getApplication(), observable, fileCallBack);
}
下载方法的三个参数,分别是上下文(建议使用application)、网络请求被观察者(使用retrofit的方法产生)、文件下载回调
我们看看下载方法的工作
①调用observeOn(Schedulers.io()) 指定rxjava在io线程中执行下载操作
②在rxjava的doOnNext中调用FileCallBack的savefile保存文件
.doOnNext(new Action1<Response<ResponseBody>>() {
@Override
public void call(Response<ResponseBody> response) {
callBack.saveFile(response.body());
}
})
③、紧接着调用observeOn(AndroidSchedulers.mainThread()) ,指定在主线程中更新ui
④、在rxjava的subscribe相应方法中调用FileCallBack的成功和失败方法
subscribe(new Subscriber<Response<ResponseBody>>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
LoadingPage.hideLoadingDialog();
e.printStackTrace();
callBack.onFail(ExceptionHandle.handleException(context, e));
}
@Override
public void onNext(Response<ResponseBody> response) {
LoadingPage.hideLoadingDialog();
if (response.code() == 200) {
callBack.onSuccess();
} else {
callBack.onFail(response.errorBody() + "");
}
}
});
至此,简单下载封装完毕,其中ExceptionHandle.handleException(context, e)是我的返回结果预处理类
6、带进度条的文件下载
7、header的统一添加
8、cookie的自动化管理
9、自定义converter
10、HTTPS证书默认信任
11、封装加载页
最近非常慢,文章更新有些慢,非常抱歉,不妨去github看看源码