1. 程式人生 > >從零開始搭建一個主流專案框架(三)—RxJava2.0+Retrofit2.0+OkHttp

從零開始搭建一個主流專案框架(三)—RxJava2.0+Retrofit2.0+OkHttp

個人部落格:haichenyi.com。感謝關注

  上一篇,我們把mvp+dagger加進去了,這一篇,我們把網路請求加上

  我這裡的網路請求是用的裝飾者模式去寫的,什麼是裝飾者模式呢?在不必改變原類檔案和使用繼承的情況下,動態地擴充套件一個物件的功能。它是通過建立一個包裝物件,也就是裝飾來包裹真實的物件。我的理解就是一個介面,兩個實現類,一個實現類負責呼叫介面的方法,另一個類負責功能的具體實現。本文中所提到的程式碼都是虛擬碼,最後會給出完整的,最初版本的專案框架。不包含任何業務邏輯

專案結構.png

  容我一個一個來說,首先,我們一般請求網路的時候,會有統一的返回資料格式,一個是需要判斷返回code碼的,就比方說登入功能,那登入成功,還是失敗,我們只用判斷code碼即可,這種型別,我們統一是HttpNoResult。還有一個是返回資料的,就比方說查一個列表資料。這裡我們統一的是HttpResult。我先給出這兩個類的程式碼:

package com.haichenyi.myproject.model.http;

/**
 * Author: 海晨憶
 * Date: 2018/2/23
 * Desc:沒有解析資料的返回
 */
public class HttpNoResult {
  private int code;
  private String msg;

  public int getCode() {
    return code;
  }

  public HttpNoResult setCode(int code) {
    this.code = code;
    return this;
  }

  public
String getMsg() { return msg; } public HttpNoResult setMsg(String msg) { this.msg = msg; return this; } @Override public String toString() { return "HttpNoResult{" + "code=" + code + ", msg='" + msg + '\'' + '}'; } }
package com.haichenyi.myproject.model.http;

import com.google.gson.annotations.SerializedName;

/**
 * Author: 海晨憶
 * Date: 2018/2/23
 * Desc:有解析資料的返回
 */
public class HttpResult<T> { private int code; private String msg; @SerializedName(value = "result") private T data; public int getCode() { return code; } public HttpResult setCode(int code) { this.code = code; return this; } public String getMsg() { return msg; } public HttpResult setMsg(String msg) { this.msg = msg; return this; } public T getData() { return data; } public HttpResult setData(T data) { this.data = data; return this; } @Override public String toString() { return "HttpResult{" + "code=" + code + ", msg='" + msg + '\'' + ", data=" + data + '}'; } }

  這裡我就需要說一點,有資料返回的時候,每個資料型別都是不一樣的,所以,這裡我用的泛型傳遞,不同的資料型別,傳不同的bean物件

  言歸正傳,我們來說說網路請求的一個介面,兩個實現類。

一個介面—HttpHelper

package com.haichenyi.myproject.model.http;

import io.reactivex.Flowable;

/**
 * Author: 海晨憶
 * Date: 2018/2/23
 * Desc:網路介面,介面引數Token統一處理,方法中不傳Token
 */
public interface HttpHelper {
  /**
   * 登入時獲取驗證碼.
   *
   * @param phone 手機號
   * @return {"code":0}
   */
  Flowable<HttpNoResult> loginCode(String phone);
  /*Flowable<HttpResult<Login>> login(String phone, String code);
  Flowable<HttpResult<List<DiyBean>>> diyKeys(String allId);*/
}

  Flowable是RxJava2.0新增的,所以說RxJava完美相容Retrofit,泛型就是我們需要解析的資料

  1. loginCode方法是說返回資料,我們只用判斷是否是成功還是失敗,

  2. login方法是說返回資料是一個Login物件,至於物件是什麼內容,那就是和你們後臺確認了

  3. diyKeys方法就是說,返回資料是一個list物件,每個list的item是DiyBean物件

package com.haichenyi.myproject.model;

import com.haichenyi.myproject.model.http.HttpHelper;
import com.haichenyi.myproject.model.http.HttpNoResult;

import io.reactivex.Flowable;

/**
 * Author: 海晨憶
 * Date: 2018/2/23
 * Desc:網路請求的實現類
 */
public class DataHelper implements HttpHelper {
  private HttpHelper http;

  public DataHelper(HttpHelper http) {
    this.http = http;
  }

  @Override
  public Flowable<HttpNoResult> loginCode(String phone) {
    return http.loginCode(phone);
  }
}

  DataHelper是HttpHelper的實現類,他的唯一作用就是呼叫介面的方法即可,具體的功能實現是後面一個類,這裡需要說明的是這個類的構造方法要public表示,因為他要dagger生成,用private或者protected表示無法生成。

package com.haichenyi.myproject.model.http;

import com.haichenyi.myproject.model.http.api.HttpApi;

import io.reactivex.Flowable;

/**
 * Author: 海晨憶
 * Date: 2018/2/23
 * Desc: 網路介面Retrofit實現
 */
public class RetrofitHelper implements HttpHelper{
  private HttpApi httpApi;

  @Inject
  RetrofitHelper(HttpApi httpApi) {
    this.httpApi = httpApi;
  }

  @Override
  public Flowable<HttpNoResult> loginCode(String phone) {
    return httpApi.loginCode(phone);
  }
}

  RetrofitHelper類作為HttpHelper介面的實現類,他是具體功能的實現類,為什麼說他是具體功能的實現類呢?因為,他是呼叫HttpApi介面的方法。HttpApi介面是幹什麼用的呢?

package com.haichenyi.myproject.model.http.api;

import com.haichenyi.myproject.model.http.HttpNoResult;
import com.haichenyi.myproject.model.http.ProtocolHttp;

import io.reactivex.Flowable;
import retrofit2.http.Field;
import retrofit2.http.FormUrlEncoded;
import retrofit2.http.POST;

/**
 * Author: 海晨憶
 * Date: 2018/2/23
 * Desc:網路請求介面api
 */
public interface HttpApi {
  /**
   * 登入時獲取驗證碼.
   *
   * @param phone 手機號
   * @return {"code":0}
   */
  @FormUrlEncoded
  @POST(ProtocolHttp.METHOD_LOGIN_CODE)
  Flowable<HttpNoResult> loginCode(@Field("phone") String phone);
}

這個就是Retrofit的網路請求的方式,看不懂?這個就是Retrofit的東西了
方法註解,包含@GET、@POST、@PUT、@DELETE、@PATH、@HEAD、@OPTIONS、@HTTP。
標記註解,包含@FormUrlEncoded、@Multipart、@Streaming。
引數註解,包含@Query、@QueryMap、@Body、@Field,@FieldMap、@Part,@PartMap。
其他註解,包含@Path、@Header、@Headers、@Url。

這裡我們還差一個介面

package com.haichenyi.myproject.model.http;

/**
 * Author: 海晨憶
 * Date: 2018/2/23
 * Desc:
 */
public interface ProtocolHttp {
  String HTTP_HOST = "http://xxx.xx.xxx.xxx:8080/app/con/";
  String HTTP_COMMON = "common/";
  String METHOD_LOGIN_CODE = HTTP_COMMON + "code";//登入傳送驗證碼
}

  如上,這裡需要注意的是不能以”\”結尾,然後就是,跟你們後臺商量,格式不要錯了,儘量就只有介面名字不同,介面名字前面部分都是一樣的。

  到此,這裡基本上就說完了,那麼有同鞋就會問了,介面定義方法的時候,我們知道該如何寫返回資料型別呢?這個我就不知道了,你得問你們後臺,根據後臺返回的資料型別去寫對應的bean類。推薦一個功能PostMan。

  到目前為止,我們都還沒有初始化網路請求的引數,這些網路請求的引數在哪裡初始化呢?這些引數,我們就只用初始化一次,我們就想到了dagger的全域性單例模式,沒錯,就是這個,我們上一篇寫了很多沒有用的東西,裡面有一個HttpModule

package com.haichenyi.myproject.di.module;

import com.haichenyi.myproject.di.qualifier.ApiUrl;
import com.haichenyi.myproject.model.http.ProtocolHttp;
import com.haichenyi.myproject.model.http.api.HttpApi;

import java.util.concurrent.TimeUnit;

import javax.inject.Singleton;

import dagger.Module;
import dagger.Provides;
import okhttp3.OkHttpClient;
import retrofit2.Retrofit;
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
import retrofit2.converter.gson.GsonConverterFactory;

/**
 * Author: 海晨憶
 * Date: 2018/2/23
 * Desc:網路請求的引數初始化
 */
@Module
public class HttpModule {
  @Provides
  @Singleton
  OkHttpClient.Builder providesOkHttpHelper() {
//請求讀寫超時時間
    return new OkHttpClient.Builder()
        .connectTimeout(10, TimeUnit.SECONDS)
        .readTimeout(10, TimeUnit.SECONDS)
        .writeTimeout(10, TimeUnit.SECONDS);
  }

  @Provides
  @Singleton
  OkHttpClient provideClient(OkHttpClient.Builder builder) {
    return builder
//        .addInterceptor(new MyHttpInterceptor())
        .build();
  }

  @Provides
  @Singleton
  Retrofit.Builder providesRetrofitBuilder() {
    return new Retrofit.Builder();
  }

  @Provides
  @Singleton
  HttpApi provideApi(@ApiUrl Retrofit retrofit) {
    return retrofit.create(HttpApi.class);
  }

  @Provides
  @Singleton
  @ApiUrl
  Retrofit providesApiRetrofit(Retrofit.Builder builder, OkHttpClient client) {
    return createRetrofit(builder, client, ProtocolHttp.HTTP_HOST);//這裡就是你的網路請求的url
  }

  private Retrofit createRetrofit(Retrofit.Builder builder, OkHttpClient client, String host) {
    return builder.client(client)
        .baseUrl(host)
        .addConverterFactory(GsonConverterFactory.create())//新增gson自動解析,我們不用關
        .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
        .build();
  }
}

如上程式碼,註釋寫的都有,考過去用就行了

在AppModule裡面新增如下程式碼

package com.haichenyi.myproject.di.module;

import com.haichenyi.myproject.base.MyApplication;
import com.haichenyi.myproject.model.DataHelper;
import com.haichenyi.myproject.model.http.HttpHelper;
import com.haichenyi.myproject.model.http.RetrofitHelper;

import javax.inject.Singleton;

import dagger.Module;
import dagger.Provides;

/**
 * Author: 海晨憶
 * Date: 2018/2/23
 * Desc:
 */
@Module
public class AppModule {
  private MyApplication application;

  public AppModule(MyApplication application) {
    this.application = application;
  }

  @Provides
  @Singleton
  DataHelper provideDataHelper(HttpHelper httpHelper) {
    return new DataHelper(httpHelper);
  }

  @Provides
  @Singleton
  HttpHelper provideHttpHelper(RetrofitHelper retrofitHelper) {
    return retrofitHelper;
  }
}

這裡都是dagger了生成全域性單例物件需要的東西

在AppComponent裡面新增如下程式碼

package com.haichenyi.myproject.di.component;

import com.haichenyi.myproject.di.module.AppModule;
import com.haichenyi.myproject.di.module.HttpModule;
import com.haichenyi.myproject.model.DataHelper;

import javax.inject.Singleton;

import dagger.Component;

/**
 * Author: 海晨憶
 * Date: 2018/2/23
 * Desc:
 */
@Singleton
@Component(modules = {AppModule.class, HttpModule.class})
public interface AppComponent {
  DataHelper getDataHelper();
}

在BaseMvpPresenter裡面新增如下程式碼

package com.haichenyi.myproject.base;

import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;

/**
 * Author: 海晨憶
 * Date: 2018/2/23
 * Desc:
 */
public class BaseMvpPresenter<T extends BaseView> implements BasePresenter<T> {
  protected T baseView;
  private CompositeDisposable disposables;

  @Override
  public void attachView(T baseView) {
    this.baseView = baseView;
  }

  protected void addSubscribe(Disposable disposable) {
    if (null == disposables) {
      disposables = new CompositeDisposable();
    }
    disposables.add(disposable);
  }

  @Override
  public void detachView() {
    this.baseView = null;
    unSubscribe();
  }

  private void unSubscribe() {
    if (null != disposables) {
      disposables.clear();
      disposables = null;
    }
  }
}

至此,就全部寫完了,關於網路請求的內容。呼叫方式如下:

package com.haichenyi.myproject.presenter;

import com.haichenyi.myproject.base.BaseMvpPresenter;
import com.haichenyi.myproject.base.MyApplication;
import com.haichenyi.myproject.contract.MainContract;
import com.haichenyi.myproject.model.DataHelper;

import javax.inject.Inject;

import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;

/**
 * Author: 海晨憶
 * Date: 2018/2/23
 * Desc:
 */
public class MainPresenter extends BaseMvpPresenter<MainContract.IView>
    implements MainContract.Presenter {
  private DataHelper dataHelper;
  @Inject
  MainPresenter() {
    dataHelper = MyApplication.getAppComponent().getDataHelper();
  }

  @Override
  public void loadData() {
    addSubscribe(dataHelper.loginCode("134xxxxxxxx")
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe());
//    baseView.showTipMsg("載入資料");
  }
}

記得在清單檔案裡面,加上網路許可權

<uses-permission android:name="android.permission.INTERNET"/>

網路請求,這樣呼叫之後在哪處理呢?我給出我的幾個處理的工具類。首先,按如下圖設定1.8支援lambda表示式

配置.png

然後新增如下幾個類

HttpCode

package com.haichenyi.myproject.model.http;

/**
 * Author: 海晨憶.
 * Date: 2017/12/21
 * Desc: 網路請求狀態碼
 */
public interface HttpCode {
  /**
   * 成功.
   */
  int SUCCESS = 0;
  /**
   * 引數為空.
   */
  int NO_PARAMETER = 1;
  /**
   * 伺服器錯誤.
   */
  int SERVER_ERR = 3;
}

ApiException

package com.haichenyi.myproject.model.http;

/**
 * Author: 海晨憶.
 * Date: 2017/12/21
 * Desc: 介面異常判斷處理
 */
public class ApiException extends Exception {
  private int code;

  @SuppressWarnings("unused")
  public ApiException(int code) {
    this.code = code;
  }

  public ApiException(int code, String message) {
    super(message);
    this.code = code;
  }

  public int getCode() {
    return code;
  }

  public ApiException setCode(int code) {
    this.code = code;
    return this;
  }
}

MyRxUtils

package com.haichenyi.myproject.model.http;

import io.reactivex.BackpressureStrategy;
import io.reactivex.Flowable;
import io.reactivex.FlowableTransformer;
import io.reactivex.Scheduler;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;

/**
 * Author: 海晨憶.
 * Date: 2017/12/27
 * Desc:切換執行緒的工具類
 */
public class MyRxUtils {
  /**
   * 從其他執行緒轉到主執行緒.
   *
   * @param scheduler Schedulers.io()等等
   * @param <T>       t
   * @return FlowableTransformer
   */
  public static <T> FlowableTransformer<T, T> toMain(Scheduler scheduler) {
    return upstream -> upstream.subscribeOn(scheduler).observeOn(AndroidSchedulers.mainThread());
  }

  public static <T> FlowableTransformer<HttpResult<T>, T> handResult() {
    return upstream -> upstream.subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread())
        .flatMap(tHttpResult -> {
          if (tHttpResult.getCode() == HttpCode.SUCCESS) {
            return /*createData(tHttpResult.data)*/Flowable.just(tHttpResult.getData());
          } else {
            return Flowable.error(new ApiException(tHttpResult.getCode(), tHttpResult.getMsg()));
          }
        });
  }

  private static <T> Flowable<T> createData(final T data) {
    return Flowable.create(e -> {
      e.onNext(data);
      e.onComplete();
    }, BackpressureStrategy.ERROR);
  }
}

MySubscriber

package com.haichenyi.myproject.model.http;


import com.haichenyi.myproject.base.BaseView;
import io.reactivex.subscribers.ResourceSubscriber;

/**
 * Author: 海晨憶.
 * Date: 2017/12/21
 * Desc:
 */
public abstract class MySubscriber<T> extends ResourceSubscriber<T> {
  private BaseView baseView;
  private boolean showLoading;

  public MySubscriber(BaseView baseView) {
    this.baseView = baseView;
  }

  public MySubscriber(BaseView baseView, boolean showLoading) {
    this.baseView = baseView;
    this.showLoading = showLoading;
  }

  @Override
  protected void onStart() {
    super.onStart();
    if (null != baseView && showLoading) {
      baseView.showLoading();
    }
  }

  @Override
  public void onError(Throwable t) {
    if (null == baseView) {
      return;
    }
    baseView.hideLoading();
    if (t instanceof ApiException) {
      ApiException apiException = (ApiException) t;
      switch (apiException.getCode()) {
        case HttpCode.NO_PARAMETER:
          baseView.showTipMsg("引數為空");
          break;
        case HttpCode.SERVER_ERR:
          baseView.showTipMsg("伺服器錯誤");
          break;
        default:
          break;
      }
    }
  }

  @Override
  public void onComplete() {
    if (null != baseView) {
      baseView.hideLoading();
    }
  }
}

這幾個類不想多做解釋,結合註釋,仔細看幾遍,就知道是幹嘛用的了

加上這幾個之後呼叫方式就變成了以下的方式:

package com.haichenyi.myproject.presenter;

import com.haichenyi.myproject.base.BaseMvpPresenter;
import com.haichenyi.myproject.base.MyApplication;
import com.haichenyi.myproject.contract.MainContract;
import com.haichenyi.myproject.model.DataHelper;
import com.haichenyi.myproject.model.http.HttpNoResult;
import com.haichenyi.myproject.model.http.MyRxUtils;
import com.haichenyi.myproject.model.http.MySubscriber;

import javax.inject.Inject;

import io.reactivex.schedulers.Schedulers;

/**
 * Author: 海晨憶
 * Date: 2018/2/23
 * Desc:
 */
public class MainPresenter extends BaseMvpPresenter<MainContract.IView>
    implements MainContract.Presenter {
  private DataHelper dataHelper;

  @Inject
  MainPresenter() {
    dataHelper = MyApplication.getAppComponent().getDataHelper();
  }

  @Override
  public void loadData() {
    addSubscribe(dataHelper.loginCode("134xxxxxxxx")
        .compose(MyRxUtils.toMain(Schedulers.io()))
        .subscribeWith(new MySubscriber<HttpNoResult>(baseView, true) {
          @Override
          public void onNext(HttpNoResult httpNoResult) {

          }
        }));
//    baseView.showTipMsg("載入資料");
  }
}

完了,完了,終於寫完了。

專案連結