本文首發於 2017年04月30日CSDN,轉載請註明出處。java
源碼傳送門react
要說2016年最火的Android技術是什麼,毫無疑問確定是RxJava+Retrofit+Mvp。現現在2017年也已通過了快一半了。相信作android開發的小夥伴對RxJava和Retrofit也再也不陌生。即便沒有刻意的去學習過,也應該對RxJava和Retrofit有個只知其一;不知其二。去年的時候學習了Rxjava和Retrofit的基本用法,但一直沒有在實際項目中運用。今年開作新項目,果斷在新項目中引入了RxJava和Retrofit。本篇文章將介紹筆者在項目中對Retrofit的封裝。 先來看一下封裝事後的Retrofit如何使用。android
RetrofitHelper.getApiService()
.getMezi()
.compose(RxUtil.<List<MeiZi>>rxSchedulerHelper(this))
.subscribe(new DefaultObserver<List<MeiZi>>() {
@Override
public void onSuccess(List<MeiZi> response) {
showToast("請求成功,妹子個數爲" + response.size());
}
});
複製代碼
沒錯,就是這麼簡潔的一個鏈式調用,能夠顯示加載動畫,還加入了Retrofit生命週期的管理。 開始以前須要先在module項目裏的Gradle文件中添加用到的依賴庫git
compile "io.reactivex.rxjava2:rxjava:$rootProject.ext.rxjava2Version"
compile "com.squareup.retrofit2:retrofit:$rootProject.ext.retrofit2Version"
compile "com.squareup.retrofit2:converter-scalars:$rootProject.ext.retrofit2Version"
compile "com.squareup.retrofit2:converter-gson:$rootProject.ext.retrofit2Version"
compile "com.squareup.retrofit2:adapter-rxjava2:$rootProject.ext.retrofit2Version"
compile 'com.jakewharton.retrofit:retrofit2-rxjava2-adapter:1.0.0'
compile 'io.reactivex.rxjava2:rxandroid:2.0.1'
compile 'com.squareup.okhttp3:logging-interceptor:3.4.1'
compile "com.trello.rxlifecycle2:rxlifecycle:$rootProject.ext.rxlifecycle"
//compile "com.trello.rxlifecycle2:rxlifecycle-android:$rootProject.ext.rxlifecycle"
compile "com.trello.rxlifecycle2:rxlifecycle-components:$rootProject.ext.rxlifecycle"
複製代碼
爲了方便依賴庫版本的修改咱們採用"io.reactivex.rxjava2:rxjava:$rootProject.ext.rxjava2Version"這中方式添加依賴,所以須要在project的build.gradle文件的加上如下內容:github
ext {
supportLibVersion = '25.1.0'
butterknifeVersion = '8.5.1'
rxjava2Version = '2.0.8'
retrofit2Version = '2.2.0'
rxlifecycle='2.1.0'
gsonVersion = '2.8.0'
}
複製代碼
下面將經過幾個小節對本次封裝做詳細的解析:json
假定服務器返回的Json數據格式以下:緩存
{
"code": 200,
"message": "成功",
"content": {
...
}
}
複製代碼
根據Json數據格式構建咱們的BasicResponse(BasicResponse中的字段內容須要根據本身服務器返回的數據肯定)。代碼以下:bash
public class BasicResponse<T> {
private int code;
private String message;
private T content;
...此處省去get、set方法。
複製代碼
該類經過RetrofitUtils來獲取ApiService的實例。代碼以下:服務器
public class IdeaApi {
public static <T> T getApiService(Class<T> cls,String baseUrl) {
Retrofit retrofit = RetrofitUtils .getRetrofitBuilder(baseUrl).build();
return retrofit.create(cls);
}
}
複製代碼
RetrofitUtils用來構建Retrofit.Builder,並對OkHttp作如下幾個方面的配置:網絡
public class RetrofitUtils {
public static OkHttpClient.Builder getOkHttpClientBuilder() {
HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() {
@Override
public void log(String message) {
try {
LogUtils.e("OKHttp-----", URLDecoder.decode(message, "utf-8"));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
LogUtils.e("OKHttp-----", message);
}
}
});
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
File cacheFile = new File(Utils.getContext().getCacheDir(), "cache");
Cache cache = new Cache(cacheFile, 1024 * 1024 * 100); //100Mb
return new OkHttpClient.Builder()
.readTimeout(Constants.DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS)
.connectTimeout(Constants.DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS)
.addInterceptor(loggingInterceptor)
.addInterceptor(new HttpHeaderInterceptor())
.addNetworkInterceptor(new HttpCacheInterceptor())
// .sslSocketFactory(SslContextFactory.getSSLSocketFactoryForTwoWay()) // https認證 若是要使用https且爲自定義證書 能夠去掉這兩行註釋,並自行配製證書。
// .hostnameVerifier(new SafeHostnameVerifier())
.cache(cache);
}
public static Retrofit.Builder getRetrofitBuilder(String baseUrl) {
Gson gson = new GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss").serializeNulls().create();
OkHttpClient okHttpClient = RetrofitUtils.getOkHttpClientBuilder().build();
return new Retrofit.Builder()
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create(gson))
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.baseUrl(baseUrl);
}
}
複製代碼
在第一節中咱們構建了服務器響應數據BasicResponse,BasicResponse由code、message、和content三個字段。其中code爲服務器返回的錯誤碼。咱們會事先和服務器約定成功時的code值,好比200表示請求成功。但一般在請求服務器數據過程當中免不了會出現各類錯誤。例如用戶登陸時密碼錯誤、請求參數錯誤的狀況。此時服務器會根據錯誤狀況返回對應的錯誤碼。通常來講,咱們只關心成功時即code爲200時的content數據。而對於code不爲200時咱們只須要給出對應的Toast提示便可。事實上咱們對咱們有用的僅僅時code爲200時的content數據。所以咱們能夠考慮過濾掉code和message,在請求成功的回調中只返回content的內容。 在此種狀況下就須要咱們經過自定義GsonConverterFactory來實現了。咱們能夠直接從Retrofit的源碼中copy出GsonConverterFactory的三個相關類來作修改。 其中最終要的一部分是修改GsonResponseBodyConverter中的convert方法。在該方法中拿到服務器響應數據並判斷code是否爲200。若是是,則獲取到content並返回,若是不是,則在此處能夠拋出對應的自定義的異常。而後再Observer中統一處理異常狀況。GsonResponseBodyConverter代碼以下:
final class GsonResponseBodyConverter<T> implements Converter<ResponseBody, Object> {
private final TypeAdapter<T> adapter;
GsonResponseBodyConverter(TypeAdapter<T> adapter) {
this.adapter = adapter;
}
@Override
public Object convert(ResponseBody value) throws IOException {
try {
BasicResponse response = (BasicResponse) adapter.fromJson(value.charStream());
if (response.getCode()==200) {
return response.getResults();
} else {
// 特定 API 的錯誤,在相應的 DefaultObserver 的 onError 的方法中進行處理
throw new ServerResponseException(response.getCode(), response.getMessage());
}
} finally {
value.close();
}
return null;
}
}
複製代碼
上一節中咱們講到了在請求服務器時可能出現的一些例如密碼錯誤、參數錯誤的狀況,服務器給咱們返回了對應的錯誤碼,咱們根據錯誤碼拋出了對應自定義異常。除此以外在咱們發起網絡請求時還可能發生一些異常狀況。例如沒有網絡、請求超時或者服務器返回了數據但在解析時出現了數據解析異常等。對於這樣的狀況咱們也要進行統一處理的。那麼咱們就須要自定義一個DefaultObserver類繼承Observer,並重寫相應的方法。 該類中最重要的兩個方法時onNext和onError。 **1.在服務器返回數據成功的狀況下會回調到onNext方法。**所以咱們能夠在DefaultObserver中定義一個抽象方法onSuccess(T response),在調用網絡時重寫onSuccess方法便可。 **2.若是在請求服務器過程當中出現任何異常,都會回調到onError方法中。**包括上節中咱們本身拋出的異常都會回調到onError。所以咱們的重頭戲就是處理onError。在onError中咱們根據異常信息給出對應的Toast提示便可。 DefaultObserver類的代碼以下:
public abstract class DefaultObserver<T> implements Observer<T> {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(T response) {
onSuccess(response);
onFinish();
}
@Override
public void onError(Throwable e) {
LogUtils.e("Retrofit", e.getMessage());
if (e instanceof HttpException) { // HTTP錯誤
onException(ExceptionReason.BAD_NETWORK);
} else if (e instanceof ConnectException
|| e instanceof UnknownHostException) { // 鏈接錯誤
onException(ExceptionReason.CONNECT_ERROR);
} else if (e instanceof InterruptedIOException) { // 鏈接超時
onException(ExceptionReason.CONNECT_TIMEOUT);
} else if (e instanceof JsonParseException
|| e instanceof JSONException
|| e instanceof ParseException) { // 解析錯誤
onException(ExceptionReason.PARSE_ERROR);
}else if(e instanceof ServerResponseException){
onFail(e.getMessage());
} else {
onException(ExceptionReason.UNKNOWN_ERROR);
}
onFinish();
}
@Override
public void onComplete() {
}
/**
* 請求成功
*
* @param response 服務器返回的數據
*/
abstract public void onSuccess(T response);
/**
* 服務器返回數據,但響應碼不爲200
*
*/
public void onFail(String message) {
ToastUtils.show(message);
}
public void onFinish(){}
/**
* 請求異常
*
* @param reason
*/
public void onException(ExceptionReason reason) {
switch (reason) {
case CONNECT_ERROR:
ToastUtils.show(R.string.connect_error, Toast.LENGTH_SHORT);
break;
case CONNECT_TIMEOUT:
ToastUtils.show(R.string.connect_timeout, Toast.LENGTH_SHORT);
break;
case BAD_NETWORK:
ToastUtils.show(R.string.bad_network, Toast.LENGTH_SHORT);
break;
case PARSE_ERROR:
ToastUtils.show(R.string.parse_error, Toast.LENGTH_SHORT);
break;
case UNKNOWN_ERROR:
default:
ToastUtils.show(R.string.unknown_error, Toast.LENGTH_SHORT);
break;
}
}
/**
* 請求網絡失敗緣由
*/
public enum ExceptionReason {
/**
* 解析數據失敗
*/
PARSE_ERROR,
/**
* 網絡問題
*/
BAD_NETWORK,
/**
* 鏈接錯誤
*/
CONNECT_ERROR,
/**
* 鏈接超時
*/
CONNECT_TIMEOUT,
/**
* 未知錯誤
*/
UNKNOWN_ERROR,
}
}
複製代碼
關於Loading咱們能夠經過RxJava的compose操做符來作一個很是優雅的處理。首先定義一個ProgressUtils工具類,而後經過RxJava的ObservableTransformer作一個變換來處理Loading。想要顯示Loading,只須要加上.compose(ProgressUtils.< T >applyProgressBar(this))便可。 ProgressUtils代碼以下:
public class ProgressUtils {
public static <T> ObservableTransformer<T, T> applyProgressBar(
@NonNull final Activity activity, String msg) {
final WeakReference<Activity> activityWeakReference = new WeakReference<>(activity);
final DialogUtils dialogUtils = new DialogUtils();
dialogUtils.showProgress(activityWeakReference.get());
return new ObservableTransformer<T, T>() {
@Override
public ObservableSource<T> apply(Observable<T> upstream) {
return upstream.doOnSubscribe(new Consumer<Disposable>() {
@Override
public void accept(Disposable disposable) throws Exception {
}
}).doOnTerminate(new Action() {
@Override
public void run() throws Exception {
Activity context;
if ((context = activityWeakReference.get()) != null
&& !context.isFinishing()) {
dialogUtils.dismissProgress();
}
}
}).doOnSubscribe(new Consumer<Disposable>() {
@Override
public void accept(Disposable disposable) throws Exception {
/*Activity context;
if ((context = activityWeakReference.get()) != null
&& !context.isFinishing()) {
dialogUtils.dismissProgress();
}*/
}
});
}
};
}
public static <T> ObservableTransformer<T, T> applyProgressBar(
@NonNull final Activity activity) {
return applyProgressBar(activity, "");
}
}
複製代碼
至此關於RxJava和Retrofit的二次封裝已經基本完成。可是咱們不能忽略了很重要的一點,就是網絡請求的生命週期。咱們將在下一節中詳細講解。
當activity被銷燬時,網絡請求也應該隨之終止的。要否則就可能形成內存泄漏。會嚴重影到響App的性能!所以Retrofit生命週期的管理也是比較重要的一點內容。在這裏咱們使用 **RxLifecycle**來對Retrofit進行生命週期管理。其使用流程以下:
1.在gradel中添加依賴以下:
compile 'com.trello.rxlifecycle2:rxlifecycle:2.1.0'
compile 'com.trello.rxlifecycle2:rxlifecycle-components:2.1.0'
複製代碼
2.讓咱們的BaseActivity繼承RxAppCompatActivity。 具體代碼以下:
public abstract class BaseActivity extends RxAppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(getLayoutId());
init(savedInstanceState);
}
protected void showToast(String msg) {
ToastUtils.show(msg);
}
protected abstract @LayoutRes int getLayoutId();
protected abstract void init(Bundle savedInstanceState);
}
複製代碼
一樣咱們項目的BaseFragment繼承RxFragment(注意使用繼承V4包下的RxFragment),以下:
public abstract class BaseFragment extends RxFragment {
public View rootView;
public LayoutInflater inflater;
@Nullable
@Override
public final View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
this.inflater = inflater;
if (rootView == null) {
rootView = inflater.inflate(this.getLayoutId(), container, false);
init(savedInstanceState);
}
ViewGroup parent = (ViewGroup) rootView.getParent();
if (parent != null) {
parent.removeView(rootView);
}
return rootView;
}
protected abstract int getLayoutId();
protected abstract void init(Bundle savedInstanceState);
protected void showToast(String msg) {
ToastUtils.show(msg);
}
@Override
public void onResume() {
super.onResume();
}
@Override
public void onPause() {
super.onPause();
}
@Override
public void onDestroyView() {
super.onDestroyView();
}
}
複製代碼
3.使用compose操做符管理Retrofit生命週期了:
myObservable
.compose(bindToLifecycle())
.subscribe();
或者
myObservable
.compose(RxLifecycle.bindUntilEvent(lifecycle, ActivityEvent.DESTROY))
.subscribe();
複製代碼
關於RxLifecycle的詳細使用方法能夠參考 RxLifecycle官網
前面幾節內容講解了如何RxJava進行二次封裝,封裝部分的代碼能夠放在咱們項目的Library模塊中。那麼封裝好以後咱們應該如何在app模塊中使用呢? 1.定義一個接口來存放咱們項目的API
public interface IdeaApiService {
/**
* 此接口服務器響應數據BasicResponse的泛型T應該是List<MeiZi>
* 即BasicResponse<List<MeiZi>>
* @return BasicResponse<List<MeiZi>>
*/
@Headers("Cache-Control: public, max-age=10")//設置緩存 緩存時間爲100s
@GET("福利/10/1")
Observable<List<MeiZi>> getMezi();
/**
* 登陸 接口爲假接口 並不能返回數據
* @return
*/
@POST("login.do")
Observable<LoginResponse> login(@Body LoginRequest request);
/**
* 刷新token 接口爲假接口 並不能返回數據
* @return
*/
@POST("refresh_token.do")
Observable<RefreshTokenResponseBean> refreshToken(@Body RefreshTokenRequest request);
@Multipart
@POST("upload/uploadFile.do")
Observable<BasicResponse> uploadFiles(@Part List<MultipartBody.Part> partList);
}
複製代碼
2.定義一個RetrofitHelper 類,經過IdeaApi來獲取IdeaApiService的實例。
public class RetrofitHelper {
private static IdeaApiService mIdeaApiService;
public static IdeaApiService getApiService(){
return mIdeaApiService;
}
static {
mIdeaApiService= IdeaApi.getApiService(IdeaApiService.class, Constants.API_SERVER_URL);
}
}
複製代碼
3.在Activity或者Fragment中發起網絡請求
/**
* Get請求
* @param view
*/
RetrofitHelper.getApiService()
.getMezi()
.compose(RxUtil.<List<MeiZi>>rxSchedulerHelper(this))
.subscribe(new DefaultObserver<List<MeiZi>>() {
@Override
public void onSuccess(List<MeiZi> response) {
showToast("請求成功,妹子個數爲" + response.size());
}
});
複製代碼
本篇文章主要講解了Rxjava和Retrofit的二次封裝。以上內容也是筆者參考多方面的資料通過長時間的改動優化而來。但鑑於本人能力有限,其中也避免不了出現不當之處。還請你們多多包涵。另外,在投稿郭神公衆號時文章可能還存在不少處理不優雅的地方,好比對響應數據的處理以及對Loading的處理。在投稿被推送後收到了不少小夥伴的建議,所以筆者也參考了你們的意見並作了優化,在此感謝你們。最後若是有疑問歡迎在文章留言評論。