一、前言
二、BaseResponse與BaseObserver的封裝
三、RxHelper排程類封裝
四、Retrofit初始化封裝
五、細節完善
1、服務錯誤資訊處理
2、添加“正在加載”彈窗
3、Retorfit請求方法彙總
4、送出參數方式彙總(可忽略)
六、總結
七、Demo位址
八、内容推薦
一、前言
由于《Rxjava+Retrofit網絡請求架構封裝(一)》篇幅太長、防止朋友們看的太累,産生視覺疲勞。是以把基礎部分和封裝部分-分開寫。這篇主要是實作如何更簡單的去實作網絡請求,提高項目後期優化和維護效率。當然有更多的好處,自己細細體會。
作者不善言語,隻做粗略描述,見諒!見諒!
二、BaseResponse與BaseObserver的封裝
BaseResponse是個人自定義命名的一個類,主要用來對傳回資料進行抽象。
BaseObserver是對傳回資料的基礎資料部分進行統一處理。
為什麼要對傳回資料進行抽象呢?
大部分公司背景接口傳回資料都遵循一定的規範:個人粗略了解分為:基礎資料與需求資料
基于上一篇的基礎介紹,我們可以擷取到背景請求資料如下。
簡單了解就是基礎資料部分key值不會改變
而需求資料部分也就是Demo裡面的資料會根據不同的需求而改變
BaseResponse就是對基礎資料進行封裝處理。
實作步驟:
1、根據基礎資料定義BaseResponse
2、修改API接口傳回資料類型
3、對基礎資料統一處理
1、根據基礎資料定義BaseResponse
public class BaseResponse<T> {
private int res_code;
private String err_msg;
private T demo;
public int getRes_code() {
return res_code;
}
public void setRes_code(int res_code) {
this.res_code = res_code;
}
public String getErr_msg() {
return err_msg;
}
public void setErr_msg(String err_msg) {
this.err_msg = err_msg;
}
public T getDemo() {
return demo;
}
public void setDemo(T demo) {
this.demo = demo;
}
}
當然我們需求資料也需重新定義
public class Demo {
@Override
public String toString() {
return "Demo{" + "id='" + id + '\'' +
", appid='" + appid + '\'' +
", name='" + name + '\'' +
", showtype='" + showtype + '\'' +
'}';
}
private String id;
private String appid;
private String name;
private String showtype;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getAppid() {
return appid;
}
public void setAppid(String appid) {
this.appid = appid;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getShowtype() {
return showtype;
}
public void setShowtype(String showtype) {
this.showtype = showtype;
}
}
2、修改API接口傳回資料類型
// @GET(Constans.retrofit)
// Observable<Bean> getRetrofit1();
// 把原先的Bean類分成BaseResponse基礎資料與Demo需求資料兩個類
@GET(Constans.retrofit)
Observable<BaseResponse<Demo>> getRetrofit2();
3、對基礎資料統一處理
/**
* 建立Base抽象類實作Observer
*/
public abstract class BaseObserver<T> implements Observer<BaseResponse<T>> {
private static final String TAG = "BaseObserver";
@Override
public void onSubscribe(Disposable d) {
Log.e(TAG, "onSubscribe: " );
}
@Override
public void onNext(BaseResponse<T> response) {
//在這邊對 基礎資料 進行統一處理 舉個例子:
if(response.getRes_code()==200){
onSuccess(response.getDemo());
}else{
onFailure(null,response.getErr_msg());
}
}
@Override
public void onError(Throwable e) {
Log.e(TAG, "Throwable: " + e.getMessage());
}
@Override
public void onComplete() {
Log.e(TAG, "onComplete: " );
}
public abstract void onSuccess(T demo);
public abstract void onFailure(Throwable e,String errorMsg);
}
請求網絡資料
retrofit.create(ApiUrl.class)
.getRetrofit2()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
//綁定生命周期
.compose(bindUntilEvent(ActivityEvent.DESTROY))
.subscribe(new BaseObserver<Demo>(){
@Override
public void onSuccess(Demo demo) {
Log.e(TAG, "onSuccess: "+demo);
}
@Override
public void onFailure(Throwable e, String errorMsg) {
Log.e(TAG, "onFailure: "+errorMsg);
}
});
//列印結果: onSuccess: Demo{id='1001', appid='1021', name='sss', showtype='text'}
對傳回資料的處理寫到這裡就結束了、不知道朋友看懂沒。
看不懂? 沒關系,敲代碼實作一下好了解
還是看不懂? 那就多敲幾遍。。。。再看不懂 去把作者拉出來溜溜
三、RxHelper排程類封裝
忘了從哪裡抄來的,這裡簡單介紹一下。
RxHelper主要是對執行線程和綁定生命周期幾個方法進行封裝,
大緻實作如下:
app builde配置
android {
.......
compileOptions {
targetCompatibility 1.8
sourceCompatibility 1.8
}
}
/**
* 排程類
*/
public class RxHelper {
public static <T> ObservableTransformer<T, T> observableIO2Main(final Context context) {
return upstream -> {
Observable<T> observable = upstream.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
return composeContext(context, observable);
};
}
public static <T> ObservableTransformer<T, T> observableIO2Main(final RxFragment fragment) {
return upstream -> upstream.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()).compose(fragment.<T>bindToLifecycle());
}
public static <T> FlowableTransformer<T, T> flowableIO2Main() {
return upstream -> upstream
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
private static <T> ObservableSource<T> composeContext(Context context, Observable<T> observable) {
if(context instanceof RxActivity) {
return observable.compose(((RxActivity) context).bindUntilEvent(ActivityEvent.DESTROY));
} else if(context instanceof RxFragmentActivity){
return observable.compose(((RxFragmentActivity) context).bindUntilEvent(ActivityEvent.DESTROY));
}else if(context instanceof RxAppCompatActivity){
return observable.compose(((RxAppCompatActivity) context).bindUntilEvent(ActivityEvent.DESTROY));
}else {
return observable;
}
}
}
使用方式:
compose(RxHelper.observableIO2Main(this))
四、Retrofit初始化封裝
這部分才是重點應該寫在最前面,被我遺漏。尴尬---
之前的調用方式:
我們不可能每次要請求網絡就重複去建立初始化Retrofit。是以我們需要對Retrofit進行單例封裝。
import android.support.annotation.NonNull;
import com.jakewharton.retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
import java.util.concurrent.TimeUnit;
import okhttp3.OkHttpClient;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
/**
* Retrofit封裝
*/
public class RetrofitUtils {
private static final String TAG = "RetrofitUtils";
private static ApiUrl mApiUrl;
/**
* 單例模式
*/
public static ApiUrl getApiUrl() {
if (mApiUrl == null) {
synchronized (RetrofitUtils.class) {
if (mApiUrl == null) {
mApiUrl = new RetrofitUtils().getRetrofit();
}
}
}
return mApiUrl;
}
private RetrofitUtils(){}
public ApiUrl getRetrofit() {
// 初始化Retrofit
ApiUrl apiUrl = initRetrofit(initOkHttp()) .create(ApiUrl.class);
return apiUrl;
}
/**
* 初始化Retrofit
*/
@NonNull
private Retrofit initRetrofit(OkHttpClient client) {
return new Retrofit.Builder()
.client(client)
.baseUrl(Constans.BaseUrl)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.build();
}
/**
* 初始化okhttp
*/
@NonNull
private OkHttpClient initOkHttp() {
return new OkHttpClient().newBuilder()
.readTimeout(Constans.DEFAULT_TIME, TimeUnit.SECONDS)//設定讀取逾時時間
.connectTimeout(Constans.DEFAULT_TIME, TimeUnit.SECONDS)//設定請求逾時時間
.writeTimeout(Constans.DEFAULT_TIME,TimeUnit.SECONDS)//設定寫入逾時時間
.addInterceptor(new LogInterceptor())//添加列印攔截器
.retryOnConnectionFailure(true)//設定出現錯誤進行重新連接配接。
.build();
}
}
若未看懂單例請參考《JAVA 設計模式——單例模式》
使用方式:
ApiUrl類
調用:
RetrofitUtils.getApiUrl().getDemo()
.compose(RxHelper.observableIO2Main(this))
.subscribe(new BaseOberver<Demo>(){
@Override
public void onSuccess(Demo demo) {
Log.e(TAG, "onSuccess: "+demo);
}
@Override
public void onFailure(Throwable e, String errorMsg) {
Log.e(TAG, "onFailure: "+errorMsg);
}
});
執行順序分别是:初始化Retrofit——>調用請求接口——>調用執行線程——>輸出結果
到這裡差不多了,我已經封裝不下去了。。。 能力有限止步于此T-T 請朋友們手下留情
五、細節完善
1、服務錯誤資訊處理
BaseObserver 對請求成功數進行了統一處理 ,但并未對伺服器傳回錯誤進行處理。
這裡從某個大神Copy了個工具類RxExceptionUtils來對錯誤資訊進行處理。
具體代碼如下:
import org.json.JSONException;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.text.ParseException;
import retrofit2.HttpException;
/**
* 異常處理
*/
public class RxExceptionUtil {
public static String exceptionHandler(Throwable e){
String errorMsg = "未知錯誤";
if (e instanceof UnknownHostException) {
errorMsg = "網絡不可用";
} else if (e instanceof SocketTimeoutException) {
errorMsg = "請求網絡逾時";
} else if (e instanceof HttpException) {
HttpException httpException = (HttpException) e;
errorMsg = convertStatusCode(httpException);
} else if (e instanceof ParseException || e instanceof JSONException
|| e instanceof JSONException) {
errorMsg = "資料解析錯誤";
}
return errorMsg;
}
private static String convertStatusCode(HttpException httpException) {
String msg;
if (httpException.code() >= 500 && httpException.code() < 600) {
msg = "伺服器處理請求出錯";
} else if (httpException.code() >= 400 && httpException.code() < 500) {
msg = "伺服器無法處理請求";
} else if (httpException.code() >= 300 && httpException.code() < 400) {
msg = "請求被重定向到其他頁面";
} else {
msg = httpException.message();
}
return msg;
}
}
請在BaseObserver類裡面的onError方法裡面調用
@Override
public void onError(Throwable e) {//伺服器錯誤資訊處理
onFailure(e, RxExceptionUtil.exceptionHandler(e));
}
2、添加“正在加載”彈窗
import android.app.ProgressDialog;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.widget.Toast;
import io.reactivex.disposables.Disposable;
/**
* Observer加入加載框
* @param <T>
*/
public abstract class MyObserver<T> extends BaseObserver<T> {
private boolean mShowDialog;
private ProgressDialog dialog;
private Context mContext;
private Disposable d;
public MyObserver(Context context, Boolean showDialog) {
mContext = context;
mShowDialog = showDialog;
}
public MyObserver(Context context) {
this(context,true);
}
@Override
public void onSubscribe(Disposable d) {
this.d = d;
if (!isConnected(mContext)) {
Toast.makeText(mContext,"未連接配接網絡",Toast.LENGTH_SHORT).show();
if (d.isDisposed()) {
d.dispose();
}
} else {
if (dialog == null && mShowDialog == true) {
dialog = new ProgressDialog(mContext);
dialog.setMessage("正在加載中");
dialog.show();
}
}
}
@Override
public void onError(Throwable e) {
if (d.isDisposed()) {
d.dispose();
}
hidDialog();
super.onError(e);
}
@Override
public void onComplete() {
if (d.isDisposed()) {
d.dispose();
}
hidDialog();
super.onComplete();
}
public void hidDialog() {
if (dialog != null && mShowDialog == true)
dialog.dismiss();
dialog = null;
}
/**
* 是否有網絡連接配接,不管是wifi還是資料流量
* @param context
* @return
*/
public static boolean isConnected(Context context)
{
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo info = cm.getActiveNetworkInfo();
if (info == null)
{
return false;
}
boolean available = info.isAvailable();
return available;
}
/**
* 取消訂閱
*/
public void cancleRequest(){
if (d!=null&&d.isDisposed()) {
d.dispose();
hidDialog();
}
}
}
使用方式:
3、Retorfit請求方法彙總
ApiUrl類
import io.reactivex.Observable;
import okhttp3.MultipartBody;
import okhttp3.RequestBody;
import okhttp3.ResponseBody;
import retrofit2.Call;
import retrofit2.http.Body;
import retrofit2.http.DELETE;
import retrofit2.http.Field;
import retrofit2.http.FieldMap;
import retrofit2.http.FormUrlEncoded;
import retrofit2.http.GET;
import retrofit2.http.Header;
import retrofit2.http.HeaderMap;
import retrofit2.http.Headers;
import retrofit2.http.Multipart;
import retrofit2.http.POST;
import retrofit2.http.PUT;
import retrofit2.http.Part;
import retrofit2.http.PartMap;
import retrofit2.http.Path;
import retrofit2.http.Query;
import retrofit2.http.QueryMap;
import retrofit2.http.Streaming;
import retrofit2.http.Url;
public interface ApiUrl {
@GET(Constans.retrofit)
Call<Bean> getRetrofit();
@GET(Constans.retrofit)
Observable<BaseResponse<Demo>> getDemo();
/**
* TODO Get請求
*/
//第一種方式:GET不帶參數
@GET("retrofit.txt")
Observable<BaseResponse<Demo>> getUser();
@GET
Observable<Demo> getUser(@Url String url);
@GET
Observable<Demo> getUser1(@Url String url); //簡潔方式 直接擷取所需資料
//第二種方式:GET帶參數
@GET("api/data/{type}/{count}/{page}")
Observable<Demo> getUser(@Path("type") String type, @Path("count") int count, @Path("page") int page);
//第三種方式:GET帶請求參數:https://api.github.com/users/whatever?client_id=xxxx&client_secret=yyyy
@GET("users/whatever")
Observable<Demo> getUser(@Query("client_id") String id, @Query("client_secret") String secret);
@GET("users/whatever")
Observable<Demo> getUser(@QueryMap Map<String, String> info);
/**
* TODO POST請求
*/
//第一種方式:@Body
@Headers("Accept:application/json")
@POST("login")
Observable<Demo> postUser(@Body RequestBody body);
//第二種方式:@Field
@Headers("Accept:application/json")
@POST("auth/login")
@FormUrlEncoded
Observable<Demo> postUser(@Field("username") String username, @Field("password") String password);
//多個參數
Observable<Demo> postUser(@FieldMap Map<String, String> map);
/**
* TODO DELETE
*/
@DELETE("member_follow_member/{id}")
Observable<Demo> delete(@Header("Authorization") String auth, @Path("id") int id);
/**
* TODO PUT
*/
@PUT("member")
Observable<Demo> put(@HeaderMap Map<String, String> headers,
@Query("nickname") String nickname);
/**
* TODO 檔案上傳
*/
@Multipart
@POST("upload")
Observable<ResponseBody> upload(@Part("description") RequestBody description, @Part MultipartBody.Part file);
//親測可用
@Multipart
@POST("member/avatar")
Observable<Demo> uploadImage(@HeaderMap Map<String, String> headers, @Part MultipartBody.Part file);
/**
* 多檔案上傳
*/
@Multipart
@POST("register")
Observable<ResponseBody> upload(@PartMap Map<String, RequestBody> params, @Part("description") RequestBody description);
//Observable<ResponseBody> upload(@Part() List<MultipartBody.Part> parts);
@Multipart
@POST("member/avatar")
Observable<Demo> uploadImage1(@HeaderMap Map<String, String> headers, @Part List<MultipartBody.Part> file);
/**
* 來自https://blog.csdn.net/impure/article/details/79658098
* @Streaming 這個注解必須添加,否則檔案全部寫入記憶體,檔案過大會造成記憶體溢出
*/
@Streaming
@GET
Observable<ResponseBody> download(@Header("RANGE") String start, @Url String url);
}
4、送出參數方式彙總(可忽略)
/**
* 送出參數方式
*/
public class RequestUtils {
/**
* Get 請求demo
* @param context
* @param observer
*/
public static void getDemo(RxAppCompatActivity context, MyObserver<Demo> observer){
RetrofitUtils.getApiUrl()
.getDemo().compose(RxHelper.observableIO2Main(context))
.subscribe(observer);
}
/**
* Post 請求demo
* @param context
* @param consumer
*/
public static void postDemo(RxAppCompatActivity context, String name, String password, Observer<Demo> consumer){
RetrofitUtils.getApiUrl()
.postUser(name,password).compose(RxHelper.observableIO2Main(context))
.subscribe(consumer);
}
/**
* Put 請求demo
* @param context
* @param consumer
*/
public static void putDemo(RxFragment context, String access_token,Observer<Demo> consumer){
Map<String, String> headers = new HashMap<String, String>();
headers.put("Accept","application/json");
headers.put("Authorization",access_token);
RetrofitUtils.getApiUrl()
.put(headers,"廈門").compose(RxHelper.observableIO2Main(context))
.subscribe(consumer);
}
/**
* Delete 請求demo
* @param context
* @param consumer
*/
public static void deleteDemo(RxFragment context, String access_token,Observer<Demo> consumer){
RetrofitUtils.getApiUrl()
.delete(access_token,1).compose(RxHelper.observableIO2Main(context))
.subscribe(consumer);
}
/**
* 上傳圖檔
* @param context
* @param observer
*/
public static void upImagView(RxFragment context, String access_token,String str, Observer<Demo> observer){
File file = new File(str);
// File file = new File(imgPath);
Map<String,String> header = new HashMap<String, String>();
header.put("Accept","application/json");
header.put("Authorization",access_token);
// File file =new File(filePath);
RequestBody reqFile = RequestBody.create(MediaType.parse("image/*"), file);
// RequestBody requestFile =
// RequestBody.create(MediaType.parse("multipart/form-data"), file);
MultipartBody.Part body =
MultipartBody.Part.createFormData("file", file.getName(), reqFile);
RetrofitUtils.getApiUrl().uploadImage(header,body).compose(RxHelper.observableIO2Main(context))
.subscribe(observer);
}
/**
* 上傳多張圖檔
* @param files
*/
public static void upLoadImg(RxFragment context,String access_token,List<File> files, Observer<Demo> observer1){
Map<String,String> header = new HashMap<String, String>();
header.put("Accept","application/json");
header.put("Authorization",access_token);
MultipartBody.Builder builder = new MultipartBody.Builder()
.setType(MultipartBody.FORM);//表單類型
for (int i = 0; i < files.size(); i++) {
File file = files.get(i);
RequestBody photoRequestBody = RequestBody.create(MediaType.parse("image/*"), file);
builder.addFormDataPart("file", file.getName(), photoRequestBody);
}
List<MultipartBody.Part> parts = builder.build().parts();
RetrofitUtils.getApiUrl().uploadImage1(header,parts).compose(RxHelper.observableIO2Main(context))
.subscribe(observer1);
}
}
六、總結
如若加上RequestUtils則代碼中請求網絡方式如下:
RequestUtils.getDemo(this, new MyObserver<Demo>(this) {
@Override
public void onSuccess(Demo result) {
tv_retrofit.setText(result.toString());
}
@Override
public void onFailure(Throwable e, String errorMsg) {
tv_retrofit.setText(errorMsg);
}
});
若背景傳回的Demo不是個對象 而是數組咋辦 不用慌
其他相關的地方也要加上 List<>
RequestUtils.getDemoList(this, new MyObserver<List<Demo>>(this) {
@Override
public void onSuccess(List<Demo> result) {
for (Demo demo:result){
Log.e(TAG, "onSuccess: "+demo.toString() );
}
tv_retrofit.setText(result.toString());
}
@Override
public void onFailure(Throwable e, String errorMsg) {
tv_retrofit.setText(errorMsg);
}
});
輸出如下:
附上Url連結:
public final static String BaseUrl = "http://120.78.186.81/api/";
public final static String retrofit = "values/5";
public final static String retrofitList = "values";
最後目錄如下:
七、Demo位址
https://github.com/DayorNight/RxjavaRetrofit2
八、内容推薦
簡書:《Android Rxjava+Retrofit網絡請求架構封裝(二)》
《Android Rxjava+Retrofit網絡請求架構封裝(一)》
《Android 仿微信全局字型大小調整》
《Android JUnit單元測試》
《Android Log日志封裝》
如果你覺得我寫的不錯或者對您有所幫助的話
不妨頂一個【微笑】,别忘了點贊、收藏、加關注哈
看在我花了這麼多時間整理寫成文章分享給大家的份上,記得手下留情哈
您的每個舉動都是對我莫大的支援