1. 程式人生 > >RxJava2+Retrofit2+okHttp的二次封裝

RxJava2+Retrofit2+okHttp的二次封裝

專案Demo已經上傳至github

連結

本人android小白一枚,在學習android的過程中,我感受很深,在做了這麼多練手的專案之後,我覺得android就像一塊畫板,畫板是白紙,作為android的開發人員,要做的就是運用一切可能,去在畫板上畫出讓人看的懂並且美觀的畫。說起來,我們卻也屬於藝術家這一行了。。。

在我看來,android是為了展示資料的,這裡就要分兩個步驟了,第一就是怎樣去展示,這部分是相當於UI的工作,,第二就是資料的來源,資料從哪來,怎麼來,來的是什麼,有什麼規律,這些都需要我們去考慮,一般來說,就現在的開發模式來說,資料都是儲存在資料庫中,後臺的工作人員通過各種語言去實現對資料庫的增刪改查,然後將資料庫中的資料進行加工處理,部署在伺服器上,android端就需要通過訪問伺服器去獲取並解析資料,這裡就涉及到獲取並解析資料了。不涉及到網路的應用現如今基本上是不復存在的了,一款App,沒有強大的後臺作為其支撐,縱然你將介面做的再花哨華麗,終究只能算是“花瓶”,“雞肋”。

所以在開始開發一個專案之前,選取一款優秀的網路框架是非常有必要的。RxJava和Retrofit出現也很久了,之前是一直使用okHttp去封裝一個請求類,這樣雖然能夠正確的請求到資料,但是,程式碼不僅複雜繁多,遇到多請求問題就會讓邏輯不清晰,思維很容易進入混亂,說再多都無法表達多請求問題的恐怖,親身經歷才能夠感受它的複雜。

RxJava的概念其實很模糊,我對它的理解就是一個給你方便處理非同步問題的框架,到底有多方便,體會過才知道。。。

Retrofit就是對okhttp做了一層封裝。把網路請求都交給給了Okhttp,我們只需要通過簡單的配置就能使用retrofit來進行網路請求了,Retrofit 除了提供了傳統的 Callback 形式的 API,還有 RxJava 版本的 Observable 形式 API,在我看來,retrofit有點像javaee裡的springboot框架,通過xml檔案配置好一切後,直接就可以在方法頭前添加註釋來執行網路請求,實在是不能再方便了。

使用方法:build.gradle檔案中引入幾個jar

compile 'io.reactivex.rxjava2:rxjava:2.0.7'
compile 'io.reactivex.rxjava2:rxandroid:2.0.1'
compile 'com.squareup.retrofit2:retrofit:2.1.0'
compile 'com.squareup.retrofit2:adapter-rxjava2:2.2.0'
compile 'com.squareup.retrofit2:converter-gson:2.1.0'
compile 'com.squareup.okhttp3:okhttp:3.5.0'
compile 'com.squareup.okhttp3:logging-interceptor:3.6.0' compile 'com.jakewharton:butterknife:7.0.1' compile 'com.trello.rxlifecycle2:rxlifecycle:2.1.0' compile 'com.trello.rxlifecycle2:rxlifecycle-components:2.1.0'

說再多,不如一個例子來的簡單,我們現在定義好一個場景,來練練手。

假設現在有這麼一組json資料

results是一個json資料。它的請求url是http://gank.io/api/data/福利/10/1。因為本篇重點就是在講運用網路框架去取資料,所以在這裡,只要取到資料就可以了,至於怎樣去展示,不是本篇重點,所以不做討論。

1.首先觀察json資料的規律,要自定義一個實體,負責將json資料解析成一個個實體類,方便去呼叫。

總體類:

public class BaseResult<T> {

    private int code;
    private String message;
    private T results;
    private boolean error;
    public T getResults() {
        return results;
}

    public void setResults(T results) {
        this.results = results;
}

    public boolean isError() {
        return error;
}

    public void setError(boolean error) {
        this.error = error;
}

    public int getCode() {
        return code;
}

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

    public String getMessage() {
        return message;
}

    public void setMessage(String message) {
        this.message = message;
}
}
results中的資料類:Meizi
public class MeiZi {
    /**
     * _id : 59cd9b53421aa9727fdb25eb
     * createdAt : 2017-09-29T09:01:07.894Z
     * desc : 9-29
     * publishedAt : 2017-09-29T11:21:16.116Z
     * source : chrome
     * type : 福利
     * url : https://ws1.sinaimg.cn/large/610dc034ly1fk05lf9f4cj20u011h423.jpg
     * used : true
     * who : daimajia
     */
private String _id;
    private String createdAt;
    private String desc;
    private String publishedAt;
    private String source;
    private String type;
    private String url;
    private boolean used;
    private String who;
    public String get_id() {
        return _id;
}

    public void set_id(String _id) {
        this._id = _id;
}

    public String getCreatedAt() {
        return createdAt;
}

    public void setCreatedAt(String createdAt) {
        this.createdAt = createdAt;
}

    public String getDesc() {
        return desc;
}

    public void setDesc(String desc) {
        this.desc = desc;
}

    public String getPublishedAt() {
        return publishedAt;
}

    public void setPublishedAt(String publishedAt) {
        this.publishedAt = publishedAt;
}

    public String getSource() {
        return source;
}

    public void setSource(String source) {
        this.source = source;
}

    public String getType() {
        return type;
}

    public void setType(String type) {
        this.type = type;
}

    public String getUrl() {
        return url;
}

    public void setUrl(String url) {
        this.url = url;
}

    public boolean isUsed() {
        return used;
}

    public void setUsed(boolean used) {
        this.used = used;
}

    public String getWho() {
        return who;
}

    public void setWho(String who) {
        this.who = who;
}
}
2.資料的容器準備好之後,就開始去獲取資料了,使用RxJava2與Retrofit2框架去進行網路請求,我覺得定義Retrofit訪問的介面

這個是最好想的,所以先編寫這個介面。

public interface ApiService {
    /**
     * 網路請求超時時間毫秒
     */
int DEFAULT_TIMEOUT = 20000;
String HOST = "http://gank.io/";
String API_SERVER_URL = HOST + "api/data/";
@GET("福利/10/1")
    Observable<BaseResult<List<MeiZi>>> getMezi();
/**
     * @param page
* @param number
* @return
*/
@Headers("Cache-Control: public, max-age=100")//設定快取 快取時間為100s
@GET("everySay/selectAll.do")
    Observable<BaseResult<List<MeiZi>>> lookBack(@Query("page") int page, @Query("rows") int number);
@POST("upload/uploadFile.do")
    Observable<BaseResult> uploadFiles(@Part("filename") String description,
@Part("pic\"; filename=\"image1.png") RequestBody imgs1,
@Part("pic\"; filename=\"image2.png") RequestBody imgs2);
@POST("upload/uploadFile.do")
    Observable<BaseResult> uploadFiles(@Part("filename") String description, @PartMap() Map<String, RequestBody> maps);
}
3.就是對自己的Retrofit去進行封裝設定

(1)retrofit是基於okhttp的加強版,所以第一步去自定義一個okhttpclient,裡面設定兩個攔截器,一個是日誌攔截器,用於對日誌的篩選,還一個就是網路攔截器,用於對網路請求頭的總體設定。

//日誌攔截器
HttpLoggingInterceptor interceptor=new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() {
    @Override
public void log(String message) {
        try {
            String text = URLDecoder.decode(message, "utf-8");
LogUtils.e(text);
} catch (UnsupportedEncodingException e) {
            e.printStackTrace();
LogUtils.e(message);
}
    }
});
class HttpNetWorkInterceptor implements Interceptor {
    @Override
public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        if (!NetUtil.checkNetWork(BaseApplication.getmContext())) {
            request = request.newBuilder()
                    .cacheControl(CacheControl.FORCE_CACHE)
                    .build();
LogUtils.d("no network");
}

        Response originalResponse = chain.proceed(request);
        if (NetUtil.checkNetWork(BaseApplication.getmContext())) {
            //有網的時候讀介面上的@Headers裡的配置,可以在這裡進行統一的設定
String cacheControl = request.cacheControl().toString();
            return originalResponse.newBuilder()
                    .header("Cache-Control", cacheControl)
                    .removeHeader("Pragma")
                    .build();
} else {
            return originalResponse.newBuilder()
                    .header("Cache-Control", "public, only-if-cached, max-stale=2419200")
                    .removeHeader("Pragma")
                    .build();
}
    }
}
(2)建立一個okhttpclient
//初始化一個OKhttpClient
OkHttpClient okHttpClient=new OkHttpClient.Builder()
        .addInterceptor(interceptor)
        .readTimeout(8, TimeUnit.SECONDS)
        .connectTimeout(8,TimeUnit.SECONDS)
        .addNetworkInterceptor(new HttpNetWorkInterceptor())
        .cache(cache)
        .build();
(3)建立一個Retrofit
Gson gson=new GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss").serializeNulls().create();
Retrofit retrofit=new Retrofit.Builder()
        .client(okHttpClient)
        .addConverterFactory(GsonConverterFactory.create(gson))
        .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
        .baseUrl(ApiService.API_SERVER_URL)
        .build();
service=retrofit.create(ApiService.class);
(4)建立一個觀察者基類,在其中進行對請求錯誤的封裝和對進度條顯示與消失的封裝。
public abstract class DefaultObserver<T extends BaseResult> implements Observer<T> {

    private Activity activity;
//  Activity 是否在執行onStop()時取消訂閱
private boolean isAddInStop = false;
    private CommonDialogUtils dialogUtils;
    public DefaultObserver(Activity activity) {
        this.activity = activity;
dialogUtils=new CommonDialogUtils();
dialogUtils.showProgress(activity);
}

    public DefaultObserver(Activity activity, boolean isShowLoading) {
        this.activity = activity;
dialogUtils=new CommonDialogUtils();
        if (isShowLoading) {
            dialogUtils.showProgress(activity,"Loading...");
}
    }
    @Override
public void onSubscribe(@NonNull Disposable d) {

    }

    @Override
public void onNext(@NonNull T t) {
        dismissProgress();
        if (!t.isError()) {
            onSuccess(t);
} else {
            onFail(t);
}
    }

    private void dismissProgress(){
        if(dialogUtils!=null){
            dialogUtils.dismissProgress();
}
    }


    @Override
public void onError(@NonNull Throwable e) {
        LogUtils.e(e.getMessage());
dismissProgress();
        if (e instanceof HttpException) {     //   HTTP錯誤
onException(ExceptionReason.BAD_NETWORK);
} else if (e instanceof ConnectException
                || e instanceof UnknownHostException) {   //   連線錯誤
onException(CONNECT_ERROR);
} else if (e instanceof InterruptedIOException) {   //  連線超時
onException(CONNECT_TIMEOUT);
} else if (e instanceof JsonParseException
                || e instanceof JSONException
                || e instanceof ParseException) {   //  解析錯誤
onException(PARSE_ERROR);
} else {
            onException(UNKNOWN_ERROR);
}
    }

    @Override
public void onComplete() {

    }

    /**
     * 請求成功
     *
     * @param response 伺服器返回的資料
     */
abstract public void onSuccess(T response);
/**
     * 伺服器返回資料,但響應碼不為200
     *
     * @param response 伺服器返回的資料
     */
public void onFail(T response) {
        String message = response.getMessage();
        if (TextUtils.isEmpty(message)) {
            ToastUtils.show(R.string.response_return_error);
} else {
            ToastUtils.show(message);
}
    }

    /**
     * 請求異常
     *
     * @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,
}
}
(5)對BaseActivity的封裝,通過rxlifecycle對Rxjava的生命週期進行控制
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);
}
(6)對BaseFragment的封裝
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();
}
}
(7)請求資料的方法
public void getData() {
    IdeaApi.getSingleHolder()
            .getMezi()
            .compose(this.<BaseResult<List<MeiZi>>>bindToLifecycle())
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(new DefaultObserver<BaseResult<List<MeiZi>>>(this) {
                @Override
public void onSuccess(BaseResult<List<MeiZi>> response) {
                    List<MeiZi> results = response.getResults();
showToast("請求成功,妹子個數為"+results.size());
}
            });
}