RxJava+MVP+Retrofit+Dagger2+Okhttp大杂烩


RxJava+MVP+Retrofit+Dagger2+Okhttp大杂烩

"你并不满足逡巡在我的海洋,渴望占据我一片高地,可我飘着白云的高地,还筑着城墙。--kurny ”

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

主要利用以下技术点

下面来介绍一下知识点由于篇幅限制,只简单介绍一下相关概念)并且讲解一下如何封装我们日常用的网络请求框架。

1.Retrofit2

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之间的关系

2.Rxjava

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可以一个一个数据返回或者一次性返回整个列表之类的)

这里我推荐扔物线大头鬼的深入浅出RxJava序列

3.Dagger2

Dagger2,它的大名字想必都知道,但是否都用过它那就另当别论了。它是一个依赖注入框架,又叫控制反转(IOC),方便我们在大型项目上解耦,各层对象的调用完全面向接口,更好地写出单元测试,有利于我们对大型项目的维护。
以前在我们的项目中,引用其它类中中的方法,我们肯定是A a = new A();a.doSomething();如果这样的话我们在写单元测试的时候还要保证A实例有没有初始化成功,同时代码中也耦合了A这个类。引入Dagger2后,我们有Inject这个方法负责把A类注入到某个Activity中。同时我们也要理解这里面的@Commponent(负责连接某个类和Activity的连接),@Module(生产各个要注入的实例),@Provides(对外提供实例方法的注解),@Scope and @Singleton(划分作用域的,标记当前对象使用范围)这些概念,这些在下面的框架的封装中都会提现出来,希望大家不要从入门到放弃。
这里我推荐泡在网上的日子详解Dagger2

4.MVP

MVP模式:相信大家很多都是MVC模式过来的,那时我们的在Fragment和Activity中,不仅负责了View试图层,还在里面写了一堆业务逻辑,网络请求数据,一个复杂的类里面上千行代码,再加上一些别人写的一些不优雅的代码,我的天呐,根本没法写单元测试,谁维护谁头疼,总有一种想推翻重写的想法,但那是不现实的,于是MVP模式应运而生,具体大家参考谷歌的官方demo,看一遍就都明白了,比我三言两语讲的清楚。

上面废话太多,下面来进入封装阶段。


先来看一下项目结构:

Screen Shot 2016-11-20 at 21.27.20_meitu_1.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上的代码仓库

总结:通过dagger2,我们可以轻松地将其它模块注入到我们所需要的类中,然后对网络请求统一的参数处理,返回结果的统一处理,可以很方便的处理我们的网络请求。


后记

通过这个框架,我们可以进行基础的网络请求,get,post,put,delete等基础的网络请求,对网络请求错误和没有网络的情况做了统一的抛出处理,对加载对话框(菊花)也做了统一的封装,可以很好的自定义加载对话框和控制网络请求是否加载对话框,数据持久化。与此同时,可以对网络请求对统一的拦截处理,方便我们添加统一的请求参数,比如token,时间戳,签名等。
由于项目中我们采用阿里云的OSS的上传方案,以及最近经常加班,时间有限,框架还存在着很多不完善的地方,比如对cookie的处理,上传,下载的处理,失败后的retry等等,后续会继续完善。

由于本项目涉及到的知识点比较多,看起来比较凌乱,忘谅解,上面说的很多都是学习中的体会,希望大家慢慢体会,当你们亲身经历后读起来就很easy了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容