天天看點

Android Rxjava+Retrofit網絡請求架構封裝(二)

Android Rxjava+Retrofit網絡請求架構封裝(二)

一、前言

二、BaseResponse與BaseObserver的封裝

三、RxHelper排程類封裝

四、Retrofit初始化封裝

五、細節完善

1、服務錯誤資訊處理

2、添加“正在加載”彈窗

3、Retorfit請求方法彙總

4、送出參數方式彙總(可忽略)

六、總結

七、Demo位址

八、内容推薦

一、前言

由于《Rxjava+Retrofit網絡請求架構封裝(一)》篇幅太長、防止朋友們看的太累,産生視覺疲勞。是以把基礎部分和封裝部分-分開寫。這篇主要是實作如何更簡單的去實作網絡請求,提高項目後期優化和維護效率。當然有更多的好處,自己細細體會。

作者不善言語,隻做粗略描述,見諒!見諒!

二、BaseResponse與BaseObserver的封裝

BaseResponse是個人自定義命名的一個類,主要用來對傳回資料進行抽象。

BaseObserver是對傳回資料的基礎資料部分進行統一處理。

為什麼要對傳回資料進行抽象呢?

大部分公司背景接口傳回資料都遵循一定的規範:個人粗略了解分為:基礎資料與需求資料

基于上一篇的基礎介紹,我們可以擷取到背景請求資料如下。

Android Rxjava+Retrofit網絡請求架構封裝(二)

簡單了解就是基礎資料部分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();
           
Android Rxjava+Retrofit網絡請求架構封裝(二)

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'}
           

對傳回資料的處理寫到這裡就結束了、不知道朋友看懂沒。

看不懂?  沒關系,敲代碼實作一下好了解

還是看不懂? 那就多敲幾遍。。。。再看不懂  去把作者拉出來溜溜

Android Rxjava+Retrofit網絡請求架構封裝(二)

三、RxHelper排程類封裝

忘了從哪裡抄來的,這裡簡單介紹一下。

RxHelper主要是對執行線程和綁定生命周期幾個方法進行封裝,

Android Rxjava+Retrofit網絡請求架構封裝(二)

大緻實作如下:

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;
        }
    }
}
           
使用方式:
Android Rxjava+Retrofit網絡請求架構封裝(二)
compose(RxHelper.observableIO2Main(this))
           

四、Retrofit初始化封裝

這部分才是重點應該寫在最前面,被我遺漏。尴尬---

Android Rxjava+Retrofit網絡請求架構封裝(二)

之前的調用方式:

Android Rxjava+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類
Android Rxjava+Retrofit網絡請求架構封裝(二)

調用:

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 請朋友們手下留情

Android Rxjava+Retrofit網絡請求架構封裝(二)

五、細節完善

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();
        }
    }
}
           

使用方式:

Android Rxjava+Retrofit網絡請求架構封裝(二)

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不是個對象 而是數組咋辦  不用慌 
Android Rxjava+Retrofit網絡請求架構封裝(二)
其他相關的地方也要加上  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);
            }
        });
           
輸出如下:
Android Rxjava+Retrofit網絡請求架構封裝(二)
附上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";
           

最後目錄如下:

Android Rxjava+Retrofit網絡請求架構封裝(二)

七、Demo位址

https://github.com/DayorNight/RxjavaRetrofit2

八、内容推薦

簡書:《Android Rxjava+Retrofit網絡請求架構封裝(二)》

《Android Rxjava+Retrofit網絡請求架構封裝(一)》

《Android 仿微信全局字型大小調整》

《Android JUnit單元測試》

《Android Log日志封裝》

如果你覺得我寫的不錯或者對您有所幫助的話

不妨頂一個【微笑】,别忘了點贊、收藏、加關注哈

看在我花了這麼多時間整理寫成文章分享給大家的份上,記得手下留情哈

您的每個舉動都是對我莫大的支援

Android Rxjava+Retrofit網絡請求架構封裝(二)