[Android] 转-RxJava+MVP+Retrofit+Dagger2+Okhttp大杂烩

原文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了。
时间: 2024-10-19 01:05:26

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

[android架构篇]mvp+rxjava+retrofit+eventBus

android架构篇 mvp+rxjava+retrofit+eventBus 高层不应该知道低层的细节,应该是面向抽象的编程.业务的实现交给实现的接口的类.高层只负责调用. 首先,要介绍一下一个项目中好架构的好处:好的软件设计必须能够帮助开发者发展和扩充解决方案,保持代码清晰健壮,并且可扩展,易于维护,而不必每件事都重写代码.面对软件存在的问题,必须遵守SOLID原则(面向对象五大原则),不要过度工程化,尽可能降低框架中模块的依赖性. 之前的一段时间,学习了一些新的技术,并把自己关注的技术整合

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

本博客转自郭霖公众号:http://mp.weixin.qq.com/s?__biz=MzA5MzI3NjE2MA==&mid=2650236866&idx=1&sn=da666831f67303eeb7a57c1591204b43&scene=24&srcid=0910wJAKSLdsEFTBKwKfTNor#wechat_redirect http://blog.csdn.net/qq_27778869/article/details/52121208 MVP

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

前几天分别对Retrofit和RxJava进行了总结,这个帖子打算把Retrofit结合RxJava使用的方法总结以下.有还不了解Retrofit或RxJava的朋友可以参考下面的帖子学习~ [Android - 框架]之Retrofit的使用 [Android - 框架]之RxJava的使用 首先导入依赖: dependencies { compile fileTree(include: ['*.jar'], dir: 'libs') androidTestCompile('com.andro

RxJava 与 Retrofit 结合的最佳实践

前言 RxJava和Retrofit也火了一段时间了,不过最近一直在学习ReactNative和Node相关的姿势,一直没有时间研究这些新东西,最近有个项目准备写,打算先用Android写一个Demo出来,却发现Android的世界发生了天翻地覆的变化,EventBus和OKHttp啥的都不见了,RxJava和Retrofit是什么鬼? 好吧,到Github上耐着性子看过了RxJava和Retrofit的介绍和几个Demo,原来Android的大神Jake Wharton为Retrofit这个项

RxJava和Retrofit的简单使用

在最近做的项目中,因为频繁用到网络请求,所以使用了现在比较流行的框架RxJava和Retrofit来代替之前的Okhttp的繁琐请求. retrofit是用来做网络请求操作,RxJava是用来切换线程.转换数据操作的. 首先第一步,使用三方的框架,肯定是先添加依赖包.项目中用的Rxjava2.0.retrofit2.0,而且Rxjava2.0和Rxjava1.0并不兼容,所以使用起来需要注意.添加一下依赖. compile "com.squareup.retrofit2:retrofit:$ro

【Android实战】----从Retrofit源码分析到Java网络编程以及HTTP权威指南想到的

一.简介 接上一篇[Android实战]----基于Retrofit实现多图片/文件.图文上传中曾说非常想搞明白为什么Retrofit那么屌.最近也看了一些其源码分析的文章以及亲自查看了源码,发现其对Java网络编程及HTTP权威指南有了一个很好的诠释.一直以来,都信奉一个原则,在这个新技术日新月异的时代,如何在Java界立足,凭借的就两点: 1.基本功,包括:Java基本知识,(Java编程思想.Effective Java),Java进阶(Java虚拟机.Java设计模式).网络相关(这个时

rxjava 调用retrofit执行网络请求的过程

retrofit流程图 -1.RxJava调用Retrofit,从requestGtPushSaeUserInfo()中获得被观察者observable,然后new一个观察者向它订阅 0.从业务中发起网络请求调用具体接口 RobotApiService就是Retrofit需要的接口文件 1.在RobotApiService文件中会新建一个Retrofit对象添加RxJava2CallAdapterFactory对象用以产生RxJava2CallAdapter, Retrofit retrofit

Rxjava、Retrofit返回json数据解析异常处理

每个App都避免不了要进行网络请求,从最开始的用谷歌封装的volley到再到android-async-http再到OKHttpUtils再到现在的Retrofit和RxJava,从我自己用后的体验来看,用了retrofit和RxJava真的回不去了,回不去了,不去了,去了,了-(哈哈,本来还想分析下这四个的区别,网上这样的文章很多,我就没必要多添乱了-.-).不多逼逼,下面开始正文. 1.Rxjava和Retrofit依赖导入: compile 'io.reactivex:rxandroid:

RxJava 与 Retrofit

RxJava 与 Retrofit