RxJava+Retrofit+MVP封裝(二)

前言

上一篇文章簡單的說了RxJava痛倚,Retrofit,MVP的簡單用法丰刊,這篇我們繼續(xù)上一篇結(jié)尾時說的幾個需要優(yōu)化的地方(封裝),先回顧下之前說的幾個需要優(yōu)化地方:

  • 每次請求都寫一串代碼創(chuàng)建Retrofit
  • 每次訂閱Observable時都有一大串重寫的方法增拥,然而我們只關(guān)心請求成功與否和請求成功后返回的數(shù)據(jù)
  • 每次都需要設(shè)置ObserableObserver在哪個線程里工作
  • 如果請求未完成但是界面卻退出了就會存在內(nèi)存泄漏的風(fēng)險
  • 每次請求添加公共參數(shù)(例如設(shè)備號啄巧,版本信息等等)

先來看下全部封裝完的項目結(jié)構(gòu):

package.png
  • base:放一些基類寻歧,例如:BaseView,BaseModel秩仆,BaseObserver等等码泛。
  • bean:放實體類。
  • callback:回調(diào)接口澄耍。
  • model:M層噪珊。
  • presenter:P層。
  • ui:放置Activity,Fragment等UI界面齐莲。
  • view:V層接口痢站。

下面開始一步步進行封裝:

Retrofit2封裝

創(chuàng)建RetrofitHelper類,主要用于對Retrofit進行初始化选酗,以及每次實例化時都需要寫一串配置信息阵难,以及每次請求時的公共參數(shù)添加

下面直接貼上代碼,都寫上了注釋:

public class RetrofitHelper {

    private static OkHttpClient mOkHttpClient;

    private RetrofitHelper() {
        throw new UnsupportedOperationException("u can't init me");
    }

    static {
        initOkHttpClient();
    }

    //創(chuàng)建一個請求
    public static Api getBoobApi() {
        return createApi(Api.class, "https://api.douban.com/");
    }


    /**
     * 根據(jù)傳入的baseUrl芒填,和api創(chuàng)建retrofit
     */
    private static <T> T createApi(Class<T> clazz, String baseUrl) {
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(baseUrl) //這個一般是接口地址(一定要以/結(jié)尾)
                .client(mOkHttpClient)
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) //集成rxjava
                .addConverterFactory(GsonConverterFactory.create()) //gson解析
                .build();
        return retrofit.create(clazz);
    }


    /**
     * 初始化OKHttpClient,設(shè)置超時時間,設(shè)置打印日志,設(shè)置Request攔截器
     */
    private static void initOkHttpClient() {
        //打印所有okhttp請求日志
        HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
        interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
        if (mOkHttpClient == null) {
            synchronized (RetrofitHelper.class) {
                if (mOkHttpClient == null) {
                    mOkHttpClient = new OkHttpClient.Builder()
                            .addInterceptor(interceptor)//添加網(wǎng)絡(luò)log攔截器
                            .addInterceptor(new RequestInterceptor())//添加網(wǎng)絡(luò)請求攔截器
                            .retryOnConnectionFailure(true) //當(dāng)失敗時重復(fù)請求
                            .connectTimeout(30, TimeUnit.SECONDS)//連接超時時間
                            .writeTimeout(20, TimeUnit.SECONDS)
                            .readTimeout(20, TimeUnit.SECONDS)
                            .build();
                }
            }
        }
    }

    private static class RequestInterceptor implements Interceptor {
        @Override
        public Response intercept(Chain chain) throws IOException {
            Request request = chain.request();
            if (request.method().equals("POST")) {
                //這里分兩種post提交方式
                // FormBoy:以表單方式提交  MultipartBody:是有多個參數(shù)(其中包括文件)
                if (request.body() instanceof FormBody) {
                    request = addPostFormParams(request);
                } else if (request.body() instanceof MultipartBody) {
                    request = addPostMultiParams(request);
                }
            } else if (request.method().equals("GET")) {
                request = addGetParams(chain);
            }
            return chain.proceed(request);
        }
    }

    //post附帶圖片時
    private static Request addPostMultiParams(Request request) {
        // 添加公共參數(shù)
        MultipartBody.Builder builder = new MultipartBody.Builder().addFormDataPart("deviceId", "123456");
        MultipartBody oldBody = (MultipartBody) request.body();
        for (int i = 0; i < oldBody.size(); i++) {
            builder.addPart(oldBody.part(i));
        }
        oldBody = builder.build();
        request = request.newBuilder().post(oldBody).build();
        return request;
    }

    //post參數(shù)時
    private static Request addPostFormParams(Request request) {
        FormBody.Builder bodyBuilder = new FormBody.Builder();
        FormBody formBody = (FormBody) request.body();
        //把原來的參數(shù)添加到新的構(gòu)造器呜叫,(因為沒找到直接添加,所以就new新的)
        for (int i = 0; i < formBody.size(); i++) {
            bodyBuilder.addEncoded(formBody.encodedName(i), formBody.encodedValue(i));
        }
        //添加公共參數(shù)
        formBody = bodyBuilder
                .addEncoded("deviceId", "123456").build();
        request = request.newBuilder().post(formBody).build();
        return request;
    }

    private static Request addGetParams(Interceptor.Chain chain) {
        Request request;
        Request oldRequest = chain.request();
        // 添加公共參數(shù)
        HttpUrl authorizedUrlBuilder = oldRequest.url()
                .newBuilder()
                .addQueryParameter("deviceId", "123456").build();
        request = oldRequest.newBuilder()
                .url(authorizedUrlBuilder)
                .build();
        return request;
    }
}

以上便是對Retrofit的封裝殿衰。

對Observer和實體類封裝

創(chuàng)建BaseObserver類朱庆,主要是用來對返回的實體類進行處理。
創(chuàng)建BaseBaseResult類闷祥,對返回的數(shù)據(jù)處理(這里的處理是對一些返回的公共部分做一個提取椎工,下面會有圖片說明)

以此Demo為例,請求https://api.douban.com/v2/book/search?q=西游記時返回數(shù)據(jù)類型如下圖:

json.png

  這里面公共的參數(shù)就是count蜀踏,start维蒙,total,books果覆,所以我們只需要對這些參數(shù)進行封裝(還有參數(shù)不全颅痊,參數(shù)錯誤等情況去請求服務(wù)器,服務(wù)器返回的錯誤code等等)局待,這里我們只對books感興趣斑响,那么我們只需要對count判斷就能得出是否搜索到書籍。

下面我們看看這個BaseResult代碼:

public class BaseResult<T> {

    private int count;
    private int start;
    private int total;
    private T books;

    public int getCount() {
        return count;
    }

    public void setCount(int count) {
        this.count = count;
    }

    public int getStart() {
        return start;
    }

    public void setStart(int start) {
        this.start = start;
    }

    public int getTotal() {
        return total;
    }

    public void setTotal(int total) {
        this.total = total;
    }

    public T getBooks() {
        return books;
    }

    public void setBooks(T books) {
        this.books = books;
    }
}

注(大坑):這里的books是返回的json數(shù)組的名字钳榨,而不是固定死的(之前用別人的data舰罚,然而發(fā)現(xiàn)一直是null,原來是gson解析時沒找到這個json字段的名字)
  
  封裝完BaseResult那么就要來封裝Observer了薛耻,畢竟數(shù)據(jù)是在Observer里面回調(diào)的营罢,真正對數(shù)據(jù)處理的是Observer這個大佬,數(shù)據(jù)到了這想要走,還不得一頓判斷饲漾,以此為例蝙搔,當(dāng)count為0的時候說明沒有搜索到那么我們需要處理下返回提示信息,當(dāng)count>0時我們把搜索到的書籍返回考传,當(dāng)搜索出錯時走到onError時我們也要返回個提示信息吃型,當(dāng)然這里還能做許多操作(加些提示框,加載框等等)這里就不一一實現(xiàn)了僚楞。

看下BaseObserver代碼:

public abstract class BaseObserver<T> implements Observer<BaseResult<T>> {

    @Override
    public void onSubscribe(@NonNull Disposable d) {
        //訂閱剛開始的時候
    }

    @Override
    public void onNext(@NonNull BaseResult<T> tBaseResult) {
        //這里面就用BaseResult包裹著實體類
        if (tBaseResult.getCount() > 0) {
            handleData(tBaseResult.getBooks());
        } else {
            errorMsg("未搜索到");
        }
    }

    @Override
    public void onError(@NonNull Throwable e) {
        if (e instanceof ConnectException)
            errorMsg("無網(wǎng)絡(luò)");
        errorMsg(e.getMessage());
    }

    @Override
    public void onComplete() {
      //完成的時候
    }

    //處理過的數(shù)據(jù)
    public abstract void handleData(T t);
    //錯誤信息
    public abstract void errorMsg(String msg);
}

當(dāng)然請求的接口也改變了(其實也就在外面套了個BaseResult)

public interface Api {
    @GET("v2/book/search")
    Observable<BaseResult<List<Book>>> searchBook(@Query("q") String bookName);
}

線程調(diào)度

每次請求時候的時候都需要.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())這樣不是很麻煩勤晚?特別是在Android中只能在主線程更新UI這個規(guī)矩,那么數(shù)據(jù)返回要在主線程中咯泉褐,這樣我們就封裝下兩個就不用每次都寫了赐写。

代碼如下:

public class BaseRx {

    private BaseRx() {
       throw new RuntimeException("u con't instantiate me");
    }

    public static <T> ObservableTransformer<T, T> io4main() {
        return new ObservableTransformer<T, T>() {
            @Override
            public ObservableSource<T> apply(@NonNull Observable<T> upstream) {
                return upstream.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).unsubscribeOn(Schedulers.io());
            }
        };
    }
}

這樣我們?nèi)绻怯玫皆贗O線程中操作,在主線程中取數(shù)據(jù)兴枯,就可以用到此工具類血淌,只需要RxJava的操作符compose就可以了(下面有完整例子)。

下面我們把封裝好的全部來跑一遍

RetrofitHelper.getBoobApi().searchBook(mEtBook.getText().toString().trim())
                .compose(BaseRx.<BaseResult<List<Book>>>io4main()) //使用compose轉(zhuǎn)換線程
                .subscribe(new BaseObserver<List<Book>>() {
            @Override
            public void handleData(List<Book> books) {
                for (int i = 0; i < books.size(); i++) {
                    if(!TextUtils.isEmpty(books.get(i).getPublisher())){
                        mStringBuilder.append(books.get(i).getPublisher() + "\n");
                    }
                    mBook.setText(mStringBuilder);
                }
            }

            @Override
            public void errorMsg(String msg) {
                mBook.setText(msg);
            }
        });

運行下效果:

request.gif

  是不是感覺封裝完看著都很爽财剖,代碼少悠夯,邏輯清晰,當(dāng)然這還沒有結(jié)合MVP一起使用躺坟,結(jié)合起來也是一大殺器沦补,擼碼猶如神助,好啦咪橙,下次再說與MVP封裝夕膀,明天又是周末,美滋滋美侦。

RxJava+Retrofit+MVP封裝(一)

RxJava+Retrofit+MVP封裝(二)

RxJava+Retrofit+MVP封裝(三)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末产舞,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子菠剩,更是在濱河造成了極大的恐慌易猫,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件具壮,死亡現(xiàn)場離奇詭異准颓,居然都是意外死亡,警方通過查閱死者的電腦和手機棺妓,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進店門攘已,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人怜跑,你說我怎么就攤上這事样勃。” “怎么了?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵彤灶,是天一觀的道長看幼。 經(jīng)常有香客問我批旺,道長幌陕,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任汽煮,我火速辦了婚禮搏熄,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘暇赤。我一直安慰自己心例,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布鞋囊。 她就那樣靜靜地躺著止后,像睡著了一般。 火紅的嫁衣襯著肌膚如雪溜腐。 梳的紋絲不亂的頭發(fā)上译株,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天,我揣著相機與錄音挺益,去河邊找鬼歉糜。 笑死,一個胖子當(dāng)著我的面吹牛望众,可吹牛的內(nèi)容都是我干的匪补。 我是一名探鬼主播,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼烂翰,長吁一口氣:“原來是場噩夢啊……” “哼夯缺!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起甘耿,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤踊兜,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后棵里,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體润文,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年殿怜,在試婚紗的時候發(fā)現(xiàn)自己被綠了典蝌。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡头谜,死狀恐怖骏掀,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤截驮,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布笑陈,位于F島的核電站,受9級特大地震影響葵袭,放射性物質(zhì)發(fā)生泄漏涵妥。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一坡锡、第九天 我趴在偏房一處隱蔽的房頂上張望蓬网。 院中可真熱鬧,春花似錦鹉勒、人聲如沸帆锋。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽锯厢。三九已至,卻和暖如春脯倒,著一層夾襖步出監(jiān)牢的瞬間实辑,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工盔憨, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留徙菠,地道東北人。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓郁岩,卻偏偏與公主長得像婿奔,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子问慎,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,901評論 2 345

推薦閱讀更多精彩內(nèi)容