原文url: http://blog.iliyun.net/2016/11/20/%E6%A1%86%E6%9E%B6%E5%B0%81%E8%A3%85/

这几年来android的网络请求技术层出不穷,网络请求从最初的HttpClient,HttpURLConnection到Volley,OkHttp,Retrofit。但是如果直接使用,每个网络请求都会重复很多相同的代码,这显然不是一个老司机需要的。接下来我们就讲讲网络请求封装那点事。

主要利用以下技术点
- Retrofit2 Retrofit官网
- OkHttp OkHttp官网
- RxJava RxJava官网
- Dagger2 Dagger2
- MVP开发模式参考谷歌官方的MVP项目 下面来介绍一下知识点由于篇幅限制,只简单介绍一下相关概念)并且讲解一下如何封装我们日常用的网络请求框架。 1. Retrofit2入门其实相当简单,官网上给了最基础的用法,相信大家看一遍就会用了。这里介绍下Retrofit2和Okhttp的关系,以及retrofit的面向接口的设计。
```
Retrofit.Builder builder = new Retrofit.Builder();
builder.client(okHttpClient)
.baseUrl(ApiService.SERVER_URL)
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.build();
``` Retrofit2实际上是对Okhttp做了一层封装,把网络请求都交给给了Okhttp,我们只需要通过简单的配置就能使用retrofit来进行网络请求。 ```
public interface GitHubService {
@GET("users/{user}/repos")
Call<List<Repo>> listRepos(@Path("user") String user);
} GitHubService service = retrofit.create(GitHubService.class); ```
然后我们就可以愉快地 Call<List<Repo>> call = service.listRepos(user);接下来调用call.enqueue,产生两个回调函数onResponse和onFailure,我们在这里做相应的处理。 这里我推荐[鸿洋的 Retrofit2 完全解析探索与okhttp之间的关系](http://blog.csdn.net/lmj623565791/article/details/51304204) 2. RxJava,一个在 Java VM上使用可观测的序列来组成异步的、基于事件的程序的库。这个有点拗口,其实大家只要去理解订阅者观察者模式(Observables发出一系列事件,Subscribers处理这些事件),事件驱动,异步这几个概念,然后再去看RxJava的语法,多敲多练,很快就能上手。 RxJava 的异步实现,是通过一种扩展的观察者模式来实现的。从纯Java的观点来看,RxJava Observable类源自于经典的观察者模式。
它添加了三个缺少的功能: - 生产者在没有更多数据可用时能够发出通知信号:onCompleted事件。
- 生产者在发生错误时能够发出通知信号:onError()事件。
- RxJava Observables能够组合而不是嵌套,从而避免开发者陷入回调的地狱。 那么我们什么时候使用观察者模式(题外话)? - 当你的架构有两个实体类,一个依赖另外一个,你想让它们互不影响或者是独立复用它们。
- 当一个变化的对象通知那些与它自身变化相关联的未知数量的对象时。
- 当一个变化的对象通知那些无需推断具体的对象是谁。 RxJava的观察者模式:
Observable (被观察者)、 Observer (观察者)、 subscribe (订阅)。Observable 和 Observer 通过 subscribe() 方法实现订阅关系,从而 Observable 可以在需要的时候发出事件来通知 Observer。 RxJava的回调方法主要有三个,onNext(),onError(),onCompleted()。
- onNext() 对于Subscribler我们可以理解为接收数据。
- onCompleted() 观测的事件的队列任务都完成了,当不再有onNext()发射数据时,onCompleted事件被触发。
- onError() 当事件异常时响应此方法,一旦此方法被触发,队列自动终止,不再发射任何数据。
(其实onCompleted()和onError()我们可以理解为网络请求框架中的onSuccess()和onError(),一个是服务器响应成功,一个是响应失败,这两个方法同时只有一个能够被执行,onCompleted()和onError()同理,onNext()可以理解为客户端接收数据,不同的是服务器必须一次性返回响应信息,而RxJava可以一个一个数据返回或者一次性返回整个列表之类的) 这里我推荐[扔物线](https://gank.io/post/560e15be2dca930e00da1083)和[大头鬼的深入浅出RxJava序列](http://blog.csdn.net/lzyzsd/article/details/41833541)。 3. Dagger2,它的大名字想必都知道,但是否都用过它那就另当别论了。它是一个依赖注入框架,又叫控制反转(IOC),方便我们在大型项目上解耦,各层对象的调用完全面向接口,更好地写出单元测试,有利于我们对大型项目的维护。
以前在我们的项目中,引用其它类中中的方法,我们肯定是A a = new A();a.doSomething();如果这样的话我们在写单元测试的时候还要保证A实例有没有初始化成功,同时代码中也耦合了A这个类。引入Dagger2后,我们有Inject这个方法负责把A类注入到某个Activity中。同时我们也要理解这里面的@Commponent(负责连接某个类和Activity的连接),@Module(生产各个要注入的实例),@Provides(对外提供实例方法的注解),@Scope and @Singleton(划分作用域的,标记当前对象使用范围)这些概念,这些在下面的框架的封装中都会提现出来,希望大家不要从入门到放弃。
这里我推荐[泡在网上的日子详解Dagger2](http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0519/2892.html) 4. MVP模式:相信大家很多都是MVC模式过来的,那时我们的在Fragment和Activity中,不仅负责了View试图层,还在里面写了一堆业务逻辑,网络请求数据,一个复杂的类里面上千行代码,再加上一些别人写的一些不优雅的代码,我的天呐,根本没法写单元测试,谁维护谁头疼,总有一种想推翻重写的想法,但那是不现实的,于是MVP模式应运而生,具体大家参考[谷歌的官方demo](https://github.com/googlesamples/android-architecture/tree/todo-mvp),看一遍就都明白了,比我三言两语讲的清楚。 上面废话太多,下面来进入封装阶段。 ---------------------------------------
先来看一下项目结构: ![Alt text](http://ww3.sinaimg.cn/large/9e17bee5gw1f9yw8xvk8bj20l20r0n1t.jpg) 项目结构看起来还是很清晰的,data包下面的local和remote分别是处理本地存储和远程获取数据的,PreferenceManger也可以负责数据持久化处理,di包下面的主要负责对okhttp的拦截处理以及生产响应的okhttp模块(主要是应用dagger2)。
下面关于如何封装我们来说个5毛钱的。
1. 初始化retrofit,添加对RxJava的适配
```
@Provides
@Singleton
public Retrofit provideRestAdapter(OkHttpClient okHttpClient) {
Retrofit.Builder builder = new Retrofit.Builder();
builder.client(okHttpClient)
.baseUrl(ApiService.SERVER_URL)
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create());
return builder.build();
}
``` 2. Service接口的处理
```
public interface ApiService {
String SERVER_URL = "http://127.0.0.1:3000/"; @FormUrlEncoded
@POST("/api/v1/authproject/login")
Observable<BaseResponse<User>> login(@Field("phone") String username, @Field("password") String password); @FormUrlEncoded
@POST("/api/v1/authproject/{id}/modify_activity")
Observable<BaseResponse<CommonInfo>> modifyActivity(@Path("id")String id, @Field("user_id") String user_id, @Field("access_token") String access_token); @GET("/api/v1/authproject/{id}")
Observable<BaseResponse<ActivityInfo>> getActivityInfo(@Path("id")String id, String user_id, @Query("access_token") String access_token);
}
```
这个可以具体参考retrofit的官方文档 3. 回调函数 ```
public interface SimpleCallback<T> {
void onStart();
void onNext(T t);
void onComplete();
}
```
onStart()主要是发起请求时,onNext()是请求有结果之前,onComplete()是请求完成。 4. 数据统一请求分发
```
public class BaseResponseFunc<T> implements Func1<BaseResponse<T>, Observable<T>> {
@Override
public Observable<T> call(BaseResponse<T> tBaseResponse) {
//遇到非200错误统一处理,将BaseResponse转换成您想要的对象
if (tBaseResponse.getStatus_code() != 200) {
return Observable.error(new Throwable(tBaseResponse.getStatus_msg()));
}else{
return Observable.just(tBaseResponse.getData());
}
}
}
``` 5. json数据统一返回格式
```
public class BaseResponse<T> { private int status_code;
private String status_msg;
private T data; public int getStatus_code() {
return status_code;
} public void setStatus_code(int status_code) {
this.status_code = status_code;
} public String getStatus_msg() {
return status_msg;
} public void setStatus_msg(String status_msg) {
this.status_msg = status_msg;
} public T getData() {
return data;
} public void setData(T data) {
this.data = data;
}
}
``` 6. 自定义dialog,错误提示,取消网络请求,回调结果由ExceptionSubscriber统一处理
```
public class ExceptionSubscriber<T> extends Subscriber<T> implements ProgressCancelListener{ private SimpleCallback<T> simpleCallback;
private Application application; private ProgressDialogHandler mProgressDialogHandler; private Context context; private Boolean isShow = true; public ExceptionSubscriber(SimpleCallback simpleCallback, Application application, Context context, Boolean isShow) {
this.simpleCallback = simpleCallback;
this.application = application;
this.context = context; mProgressDialogHandler = new ProgressDialogHandler(context
, this, true);
this.isShow = isShow;
} public ExceptionSubscriber(SimpleCallback simpleCallback, Application application, Context context) {
this.simpleCallback = simpleCallback;
this.application = application;
this.context = context; mProgressDialogHandler = new ProgressDialogHandler(context
, this, true);
} private void showProgressDialog() {
if (mProgressDialogHandler != null) {
mProgressDialogHandler.obtainMessage(ProgressDialogHandler.SHOW_PROGRESS_DIALOG).sendToTarget();
}
} private void dismissProgressDialog() {
if (mProgressDialogHandler != null) {
mProgressDialogHandler.obtainMessage(ProgressDialogHandler.DISMISS_PROGRESS_DIALOG).sendToTarget();
mProgressDialogHandler = null;
}
} @Override
public void onStart() {
super.onStart();
if (simpleCallback != null)
simpleCallback.onStart();
if (isShow)
showProgressDialog();
} @Override
public void onCompleted() {
if (simpleCallback != null)
simpleCallback.onComplete();
dismissProgressDialog(); } @Override
public void onError(Throwable e) {
try {
System.out.println("-------"+e.getMessage());
e.printStackTrace();
if (e instanceof SocketTimeoutException) {
Toast.makeText(application, "网络中断,请检查您的网络状态", Toast.LENGTH_SHORT).show();
} else if (e instanceof ConnectException) {
Toast.makeText(application, "网络中断,请检查您的网络状态", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(application, "" + e.getMessage(), Toast.LENGTH_SHORT).show();
}
if (simpleCallback != null)
simpleCallback.onComplete();
dismissProgressDialog();
} catch (Throwable el) {
dismissProgressDialog();
el.printStackTrace();
} } @Override
public void onNext(T t) {
if (simpleCallback != null)
simpleCallback.onNext(t);
} @Override
public void onCancelProgress() {
if (!this.isUnsubscribed()) {
this.unsubscribe();
}
}
}
```
在Subscriber的四个回调函数中,onStart中实现加载框,onError中实现统一的错误处理,onNext中传出一个泛型的对象做后续处理,onComplete完成网络请求。 7.拦截器的处理
```
public OkHttpClient provideOkHttpClient() {
final OkHttpClient.Builder builder = new OkHttpClient.Builder();
builder.addInterceptor(new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Request.Builder requestBuilder = request.newBuilder();
Request newRequest = null;
if (request.body() instanceof FormBody) {
FormBody.Builder newFormBody = new FormBody.Builder();
FormBody oidFormBody = (FormBody) request.body();
for (int i = 0; i < oidFormBody.size(); i++) {
newFormBody.addEncoded(oidFormBody.encodedName(i), oidFormBody.encodedValue(i));
}
String url = request.url().encodedPath();
newFormBody.addEncoded("timestamp", CommonUtils.getTimestamp());
newFormBody.addEncoded("signature", SecerityUtils.getUnsignedContent(newFormBody));
requestBuilder.method(request.method(), newFormBody.build());
} else {//GET请求
String url = String.valueOf(request.url());
int index1 = url.indexOf("?");
int index2 = url.indexOf("=");
if (index1 != -1 && index2 != -1) {
requestBuilder.url(request.url() + "&signature="+SecerityUtils.getUnsignedContent(String.valueOf(request.url()), CommonUtils.getTimestamp())+"&timestamp=" + CommonUtils.getTimestamp());
} else {
requestBuilder.url(request.url() + "?signature="+SecerityUtils.getUnsignedContent(String.valueOf(request.url()), CommonUtils.getTimestamp())+ "&timestamp=" + CommonUtils.getTimestamp());
}
}
newRequest = requestBuilder.build();
return chain.proceed(newRequest);
}
}); if (BuildConfig.DEBUG) {
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
logging.setLevel(HttpLoggingInterceptor.Level.BODY);
builder.addInterceptor(logging);
} builder.connectTimeout(60 * 1000, TimeUnit.MILLISECONDS)
.readTimeout(60 * 1000, TimeUnit.MILLISECONDS); return builder.build();
}
```
为了不需要每一个请求都添加时间戳和signature,需要对所有的请求统一添加,上面的例子是之前一个项目中用到的get请求和post请求。对于post请求,我们拦截body参数,对于get请求,我们截取?后面的参数。然后生成相应的签名。这样大大方便了我们对统一参数的管理。 8. 发起http请求 ```
public void login(final String username, final String password) {
apiManager.login((Activity) loginView, username, password, new SimpleCallback<User>() {
@Override
public void onStart() { } @Override
public void onNext(User user) {
loginView.loginSuccess();
} @Override
public void onComplete() { }
},true);
}
``` 通过对应的回调函数反馈到activity或者fragment中来处理相关的UI。 9. 对于相应的module,我们都可以通过注解到响应的类中,只需要在这里添加对应的Component
```
@Singleton @Component(modules = {AppModule.class, ApiModule.class}) public interface AppComponent { LoginComponent plus(LoginModule loginModule); }
```
然后在Activity或者fragment中提供响应的注入即可。
```
@Override public void setupActivityComponent() { AuthApplication.get(this).getAppComponent().plus(new LoginModule(this)).inject(this);}
```
[Github上的代码仓库](https://github.com/xzwc/AndroidProject/tree/master/AuthProject)
总结:通过dagger2,我们可以轻松地将其它模块注入到我们所需要的类中,然后对网络请求统一的参数处理,返回结果的统一处理,可以很方便的处理我们的网络请求。 ## 后记 通过这个框架,我们可以进行基础的网络请求,get,post,put,delete等基础的网络请求,对网络请求错误和没有网络的情况做了统一的抛出处理,对加载对话框(菊花)也做了统一的封装,可以很好的自定义加载对话框和控制网络请求是否加载对话框,数据持久化。与此同时,可以对网络请求对统一的拦截处理,方便我们添加统一的请求参数,比如token,时间戳,签名等。 由于项目中我们采用阿里云的OSS的上传方案,以及最近经常加班,时间有限,框架还存在着很多不完善的地方,比如对cookie的处理,上传,下载的处理,失败后的retry等等,后续会继续完善。 由于本项目涉及到的知识点比较多,看起来比较凌乱,忘谅解,上面说的很多都是学习中的体会,希望大家慢慢体会,当你们亲身经历后读起来就很easy了。

[Android] 转-RxJava+MVP+Retrofit+Dagger2+Okhttp大杂烩的更多相关文章

  1. android完整资讯App、Kotlin新闻应用MVP + RxJava + Retrofit + Dagger2、优雅区间选择器等源码

    Android精选源码 Android完整资讯客户端源码 android展示注册进度效果源码 Android Wifi热点数据传输Socket 通信示例源码 Android Dota的辅助信息app源 ...

  2. Rxjava + retrofit + dagger2 + mvp搭建Android框架

    最近出去面试,总会被问到我们项目现在采用的什么开发框架,不过据我的经验网络框架(volley)+图片缓存(uIl)+数据库(orm)+mvp,不过现在这套框架比较好了,现在采用什么呢?Rxjava + ...

  3. Android使用RxJava+Retrofit2+Okhttp+MVP练习的APP

    Android使用RxJava+Retrofit2+Okhttp+MVP练习的APP 项目截图     这是我的目录结构 五步使用RxJava+Retrofit2+Okhttp+RxCache 第一步 ...

  4. 设计模式笔记之四:MVP+Retrofit+RxJava组合使用

    本博客转自郭霖公众号:http://mp.weixin.qq.com/s?__biz=MzA5MzI3NjE2MA==&mid=2650236866&idx=1&sn=da66 ...

  5. 【Android - 框架】之Retrofit+RxJava的使用

    前几天分别对Retrofit和RxJava进行了总结,这个帖子打算把Retrofit结合RxJava使用的方法总结以下.有还不了解Retrofit或RxJava的朋友可以参考下面的帖子学习~ [And ...

  6. Android开源实战:使用MVP+Retrofit开发一款文字阅读APP

    文字控 使用MVP+Retrofit开发的一款文艺APP,它是一个非常优美的文字阅读应用,界面基本上符合material design设计规范. 在该项目中,我采用的是MVP架构,该架构目前在Andr ...

  7. android全功能音乐播放器、基于MVP-Clean + Weex + RxJava2 + Retrofit + Dagger2 + MTRVA的综合应用、图片滤镜处理等源码

    Android仿微信朋友圈查看图片下拽返回. Android图片滤镜处理,相机滤镜处理效果源码 Android自定义View源码:一个水平的进度条 基于MVP-Clean + Weex + RxJav ...

  8. 带你封装自己的MVP+Retrofit+RxJava2框架(一)

    前言 文本已经收录到我的Github个人博客,欢迎大佬们光临寒舍:我的GIthub博客 看完本篇文章的,可以看下带你封装自己的MVP+Retrofit+RxJava2框架(二),里面封装得到了改进 本 ...

  9. Android网络访问库 - Retrofit学习(1)基础

    Retrofit是什么 Retrofit是一个类型安全的HTTP客户端,支持Android和Java.它是Square公司开源的项目,当前版本2.0. 在实际开发中,我们Retrofit配合OKHTT ...

随机推荐

  1. IntelliJ IDEA创建web项目

    Intellij IDEA 创建Web项目并在Tomcat中部署运行 一.创建Web项目 1.File -> New Module,进入创建项目窗口 2.选择Java类型,在 Module na ...

  2. TIJ -- CountDownLatch

    1. 2. Class : CountDownLatchDemo package lime.thinkingInJava._021._007._001; import java.sql.Time; i ...

  3. RuntimeException: Type "nmethodBucket*", referenced in VMStructs::localHotSpotVMStructs in the remot

    问题:使用jmap命令查看某个进程的堆情况时(jmap -heap 198376),抛异常如下: Attaching to process ID 198376, please wait...Excep ...

  4. Linux下的awk文本分析命令实例(一)

    1.  入门实例1.1 显示最近登录的5个帐号: [root@localhost ~]# | awk '{print $1}' root root root root reboot 1.2 如果只是显 ...

  5. css 背景图片虚化效果

    转载地址:http://blog.csdn.net/ohehehou/article/details/51975539 需求:一个div设置了background: url,现在需要使图片背景模糊,d ...

  6. swust oj 962

    括号匹配问题 1000(ms) 65535(kb) 3045 / 13375 假设表达式中允许包含两种括号:圆括号和方括号.编写一个算法判断表达式中的括号是否正确配对. 输入 由括号构成的字符串,包含 ...

  7. MySQL数据库一些常用命令

    输入mysql –u root(用户名) -p 回车后输入密码,就可以连接到mysql数据库. 1. 创建数据库:create database 数据库名称: 2. 删除数据库:drop databa ...

  8. [java] 在linux+chrome/firefox上使用java applet

    我现在的浏览器用不了java applet,我的环境是archlinux + chrome or firefox. java网站的指引是这样说的 按照这些说明操作,以便在 Ubuntu Linux 上 ...

  9. PrimeNG之TreeTable

    --treetable用于显示分层数据表格的格式 Import import {TreeTableModule,TreeNode,SharedModule} from 'primeng/primeng ...

  10. rebuild online时意外中断 再次重建时报错解决方法

    rebuild online时意外中断 再次重建时报错 SQL> alter index PARTY.IDX_CM_INDIV_CUSTOMER_4 rebuild online; alter ...