看我開發(fā)干貨集中營App(三)~Retroft2+RxJava封裝使用

個(gè)人原因贿讹,此系列更新已停止千诬,抱歉静袖。

上一篇《看我開發(fā)干貨集中營App(二) ~ APP初始化》中講述了此APP所依賴的一些第三方庫脏里,包括Retrofit2,Okhttp3,RxJava,Gilde等,對(duì)于使用第三方依賴庫球切,我自己一直都秉承著封裝一層在使用的原則。這樣做的好處除了方便實(shí)際使用外绒障,就是萬一需要替換對(duì)應(yīng)依賴庫不用滿世界去修改代碼吨凑,只需要在封裝入口處修改就好了。關(guān)于使用第三方開源項(xiàng)目户辱,大家可以參考下這篇文字:如何正確使用開源項(xiàng)目鸵钝?

接下來介紹下上面幾個(gè)庫的基本封裝使用,提醒下以下內(nèi)容將會(huì)基于你已經(jīng)了解過對(duì)應(yīng)的開源項(xiàng)目庐镐,而且知道如何使用恩商,所以如果還未了解過相關(guān)庫的請(qǐng)自行Google了解一番再回來看哦。

OK必逆,先預(yù)覽下《干貨精選》的目錄結(jié)構(gòu):


目錄結(jié)構(gòu)

關(guān)于Android App目錄結(jié)構(gòu)怠堪,個(gè)人比較推薦按模塊分類。直接看看包名下的目錄:

  • commonui: 通用UI封裝類存放目錄
  • core: 存放底層核心的封裝類名眉,如retrofit粟矿,rxjava等
  • modules: 按模塊存放文件目錄,主要是mvp開發(fā)模式的文件损拢,如Google Todo-Mvp分支陌粹,

Retrofit2封裝

創(chuàng)建工具類:core/retofit/RetrofitHelper

RetrofitHelper特性:

  1. 單例形式創(chuàng)建Retrofit實(shí)例;
  2. 使用okhttp3作為請(qǐng)求客戶端福压;
  3. 使用gson作為數(shù)據(jù)轉(zhuǎn)換器掏秩;
  4. 使用RxJava優(yōu)化異步請(qǐng)求流程或舞;
  5. 開啟數(shù)據(jù)緩存,無網(wǎng)絡(luò)時(shí)可從緩存讀取數(shù)據(jù)蒙幻;
  6. 輔助類靜態(tài)方法獲取Retrofit Service實(shí)例映凳。

節(jié)省篇幅,上代碼看注釋:

public class RetrofitHelper {

    private volatile static Retrofit retrofitInstance = null;

    /**
     * 創(chuàng)建Retrofit請(qǐng)求Api
     * @param clazz   Retrofit Api接口
     * @return api實(shí)例
     */
    public static <T> T createApi(Class<T> clazz){
        return getInstance().create(clazz);
    }


    // ===============================================================
    // private methods =================================================
    /**
     * 獲取Retrofit實(shí)例
     * @return Retrofit
     */
    private static Retrofit getInstance(){
        if(null == retrofitInstance){
            synchronized (Retrofit.class){
                if(null == retrofitInstance){ // 雙重檢驗(yàn)鎖,僅第一次調(diào)用時(shí)實(shí)例化
                    retrofitInstance = new Retrofit.Builder()
                            // baseUrl總是以/結(jié)束杆煞,@URL不要以/開頭
                            .baseUrl(BuildConfig.API_SERVER_URL)
                            // 使用OkHttp Client
                            .client(buildOKHttpClient())
                            // 集成RxJava處理
                            .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                            // 集成Gson轉(zhuǎn)換器
                            .addConverterFactory(buildGsonConverterFactory())
                            .build();
                }
            }
        }
        return retrofitInstance;
    }

    /**
     * 構(gòu)建OkHttpClient
     * @return OkHttpClient
     */
    private static OkHttpClient buildOKHttpClient(){
        // 添加日志攔截器魏宽,非debug模式不打印任何日志
        HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
        loggingInterceptor.setLevel(AppUtil.isDev() ? HttpLoggingInterceptor.Level.HEADERS : HttpLoggingInterceptor.Level.NONE) ;

        return new OkHttpClient.Builder()
                .addInterceptor(loggingInterceptor)                       // 添加日志攔截器
                //.addInterceptor(buildTokenInterceptor())                // 添加token攔截器
                .addNetworkInterceptor(buildCacheInterceptor())           // 添加網(wǎng)絡(luò)緩存攔截器
                .cache(getCache())                                        // 設(shè)置緩存文件
                .retryOnConnectionFailure(true)                           // 自動(dòng)重連
                .connectTimeout(15, TimeUnit.SECONDS)                     // 15秒連接超時(shí)
                .readTimeout(20, TimeUnit.SECONDS)                        // 20秒讀取超時(shí)
                .writeTimeout(20, TimeUnit.SECONDS)                       // 20秒寫入超時(shí)
                .build();
    }

    /**
     * 獲取緩存對(duì)象
     * @return Cache
     */
    private static Cache getCache(){
        // 獲取緩存目標(biāo),SD卡
        File cacheFile = new File(AppUtil.getContext().getCacheDir(), ResourceUtil.getString(R.string.app_name_en));
        // 創(chuàng)建緩存對(duì)象,最大緩存50m
        return new Cache(cacheFile, 1024*1024*20);
    }

    /**
     * 構(gòu)建緩存攔截器
     * @return Interceptor
     */
    private static Interceptor buildCacheInterceptor(){
        return new Interceptor() {
            @Override
            public Response intercept(Chain chain) throws IOException {
                Request request = chain.request();
                // 無網(wǎng)絡(luò)連接時(shí)請(qǐng)求從緩存中讀取
                if (!AppUtil.isNetworkConnected()) {
                    request = request.newBuilder()
                            .cacheControl(CacheControl.FORCE_CACHE)
                            .build();
                }

                // 響應(yīng)內(nèi)容處理
                // 在線時(shí)緩存5分鐘
                // 離線時(shí)緩存4周
                Response response = chain.proceed(request);
                if (AppUtil.isNetworkConnected()) {
                    int maxAge = 300;
                    response.newBuilder()
                            .header("Cache-Control", "public, max-age=" + maxAge)
                            .removeHeader("Pragma")// 清除頭信息,因?yàn)榉?wù)器如果不支持决乎,會(huì)返回一些干擾信息队询,不清除下面無法生效
                            .build();
                }else {
                    // 無網(wǎng)絡(luò)時(shí),設(shè)置超時(shí)為4周
                    int maxStale = 60 * 60 * 24 * 28;
                    response.newBuilder()
                            .header("Cache-Control", "public, only-if-cached, max-stale=" + maxStale)
                            .removeHeader("Pragma")
                            .build();
                }
                return response;
            }
        };
    }

    /**
     * 構(gòu)建GSON轉(zhuǎn)換器
     * @return GsonConverterFactory
     */
    private static GsonConverterFactory buildGsonConverterFactory(){
        GsonBuilder builder = new GsonBuilder();
        builder.setLenient();

        // 注冊(cè)類型轉(zhuǎn)換適配器
        builder.registerTypeAdapter(Date.class, new JsonDeserializer<Date>() {
            @Override
            public Date deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
                return null == json ? null : new Date(json.getAsLong());
            }
        });

        Gson gson = builder.create();
        return GsonConverterFactory.create(gson);
    }
}

其中有幾點(diǎn)需要說明下:

  1. AppUtil是另外一個(gè)工具類构诚,封裝了APP常用的一些方法蚌斩,如判斷網(wǎng)絡(luò)狀態(tài),獲取App版本范嘱、緩存路徑等送膳。
  2. API_SERVER_URL 是定義在 build.gradle 中的一個(gè)常量(干貨集中營api基本URL),實(shí)際產(chǎn)品研發(fā)中可以使用同樣方法定義不同開發(fā)環(huán)境(本地開發(fā)丑蛤、測(cè)試叠聋、正式環(huán)境)的服務(wù)器地址。
  3. 緩存設(shè)置如果服務(wù)器響應(yīng)Header不支持緩存,貌似緩存不起作用受裹,歡迎大家?guī)兔y(cè)試碌补。

使用方法:

假設(shè)有Retrofit Service Api如下:

public interface ArticleServiceApi {

    @GET("day/{year}/{month}/{day}")
    Observable<List<Article>> fetchByDate(@Path("year") String year, @Path("month") String month,
                                    @Path("day") String day);
}

執(zhí)行api請(qǐng)求如下:

RetrofitHelper.createApi(ArticleServiceApi.class)
    .fetchByDate("2016", "09", "17")
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(new Subscriber<List<Article>>() {
        @Override
        public void onCompleted() {}

        @Override
        public void onError(Throwable e) {}

        @Override
        public void onNext(List<Article> articles) {}
    });

是不是比原始的請(qǐng)求簡單不少。接下來對(duì)RxJava封裝一層棉饶,使上面的請(qǐng)求更加簡單點(diǎn)厦章。

RxJava封裝

再Retrofit中使用RxJava,需要封裝的地方僅有2個(gè)地方照藻,

  1. 切換線程操作
  2. 響應(yīng)結(jié)果處理:onComplete()方法可選袜啃,通用錯(cuò)誤響應(yīng),保留onSuccess()供用戶調(diào)用

從干貨集中營API返回結(jié)果可知幸缕,響應(yīng)格式如下:

{
    error: false,
    results: [object, object]
}

// 或者

{
    error: false,
    category: [String,String...],
    results: {
        "Android": [{}, {}],
        "IOS": [{}, {}]
    }
}

為此群发,首先創(chuàng)建響應(yīng)結(jié)果的實(shí)體基類 core/response/ResponseData

public class ResponseData {

    private boolean error;           // 是否請(qǐng)求錯(cuò)誤

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

然后建立干貨的實(shí)體類: modules/articles/entity/Article,以及API響應(yīng)結(jié)果實(shí)體類modules/articles/entity/DailyArticles发乔。

基于以上響應(yīng)實(shí)體類也物,我們創(chuàng)建RxJava訂閱者的封裝基類:core/rx/RxSubscriber

public abstract class RxSubscriber<T> extends Subscriber<T> {

    @Override
    public void onCompleted() {
        // 忽略操作,需要可覆寫該方法
    }

    @Override
    public void onError(Throwable e) {
        if(e instanceof HttpException){
            String errNetwork = ResourceUtil.getString(R.string.err_network);
            LogUtil.error(e, "onError: " + errNetwork);
            ToastUtil.show(errNetwork);
        }

        // TODO: 處理其它通用錯(cuò)誤
        // 覆寫此方法自行處理異常列疗,通用異常使用super.onError(e)保留
        e.printStackTrace();
    }

    @Override
    public void onNext(T t) {
        if(t instanceof ResponseData){
            ResponseData response = (ResponseData) t;
            // 判斷是否請(qǐng)求錯(cuò)誤滑蚯,出錯(cuò)直接轉(zhuǎn)到onError()
            if(response.isError()){
                Throwable e = new Throwable(ResourceUtil.getString(R.string.err_default));
                this.onError(e);
                return;
            }
        }
        onSuccess(t);
    }

    /**
     * 請(qǐng)求成功回調(diào)
     * @param t 最終響應(yīng)結(jié)果
     */
     public abstract void onSuccess(T t);
}

這里封裝并沒有做太多處理,主要是對(duì)公共的異常做了一次處理,保留onSuccess()更多關(guān)注業(yè)務(wù)處理告材。這樣封裝感覺還不是很好坤次,比如錯(cuò)誤處理可以通過事件方式傳遞給View等,不過因?yàn)闀簳r(shí)對(duì)Android研究不深斥赋,還沒有更好的想法缰猴。

接下來,對(duì)線程切換操作再做一個(gè)封裝:core/rx/RxJavaHelper疤剑。

對(duì)線程切換的封裝需要用到RxJava的 compose() 方法滑绒,如果不太了解它的使用,可以參考下:給 Android 開發(fā)者的 RxJava 詳解隘膘。

public class RxJavaHelper {

    /**
     * 切換線程操作
     * @return Observable轉(zhuǎn)換器
     */
    public static <T> Observable.Transformer<T, T> observeOnMainThread() {
        return new Observable.Transformer<T, T>() {
            @Override
            public Observable<T> call(Observable<T> tObservable) {
                return tObservable
                        .subscribeOn(Schedulers.io())                       // 在io線程中請(qǐng)求
                        .observeOn(AndroidSchedulers.mainThread());         // 請(qǐng)求完成后返回主線程處理
            }
        };
    }
}

封裝基本完畢疑故,再來看看請(qǐng)求api代碼:

RetrofitHelper.createApi(ArticleServiceApi.class)
        .fetchByDate("2016", "09", "01")
        .compose(RxJavaHelper.<DailyArticles>observeOnMainThread())
        .subscribe(new RxSubscriber<DailyArticles>() {
            @Override
            public void onSuccess(DailyArticles dailyArticles) {
                LogUtil.debug("onSuccess: " + dailyArticles.getCategory().toString());
            }
        });

是不是有所改善~~

來看看請(qǐng)求結(jié)果:

D/OkHttp: --> GET http://gank.io/api/day/2016/09/01 http/1.1
D/OkHttp: --> END GET
D/OpenGLRenderer: Use EGL_SWAP_BEHAVIOR_PRESERVED: true
                  [ 09-21 20:50:57.557  2870: 2870 D/         ]
                  HostConnection::get() New Host Connection established 0xa230c570, tid 2870
                  [ 09-21 20:50:57.625  2870: 2918 D/         ]
                  HostConnection::get() New Host Connection established 0xae427510, tid 2918
I/OpenGLRenderer: Initialized EGL, version 1.4
D/OkHttp: <-- 200 OK http://gank.io/api/day/2016/09/01 (250ms)
D/OkHttp: Server: nginx/1.4.6 (Ubuntu)
D/OkHttp: Date: Wed, 21 Sep 2016 12:50:57 GMT
D/OkHttp: Content-Type: application/json
D/OkHttp: Content-Length: 4945
D/OkHttp: Connection: keep-alive
D/OkHttp: <-- END HTTP
D/GankEssence: ╔════════════════════════════════════════════════════════════════════════════════════════
D/GankEssence: ║ RxSubscriber.onNext  (RxSubscriber.java:51)
D/GankEssence: ║    MainActivity$1.onSuccess  (MainActivity.java:23)
D/GankEssence: ╟────────────────────────────────────────────────────────────────────────────────────────
D/GankEssence: ║ onSuccess: [瞎推薦, Android, 福利, 休息視頻, iOS]
D/GankEssence: ╚════════════════════════════════════════════════════════════════════════════════════════

OK,還可以弯菊,Retrofit2 + RxJava 封裝就暫時(shí)這樣了纵势。如果使用過程有什么問題再修改修改!管钳!

本來還想再把Logger以及Glide的封裝再說說钦铁,不過發(fā)現(xiàn)篇幅好長啊,而且Glide本身應(yīng)實(shí)在夠好了才漆,不需要太多封裝牛曹,只需封裝個(gè)工具類方便我們自己使用就好了,Logger也是一樣醇滥。大家有需要可以直接看看源碼:ImageUtilLogUtil黎比。

本篇文章主要是簡單說了第三方開源庫的封裝使用,其它的沒說腺办。對(duì)于Activity焰手、Fragment等各類View組件使用糟描,后面肯定會(huì)有封裝一些適當(dāng)?shù)幕惢澈恚貏e是RecyclerView,會(huì)在后面開發(fā)過程提到的船响。


預(yù)告躬拢,下一篇《看我開發(fā)干貨集中營App(四)~ 首頁開發(fā)》將開始構(gòu)建APP應(yīng)用,功能點(diǎn)有RecyclerView使用见间、下拉刷新聊闯、上拉加載更多、HTML內(nèi)容的解析等等米诉。

Tags: 看我開發(fā) Android開發(fā) 干貨集中營 開放api Retroft2封裝 RxJava封裝 Glide封裝

系列文章:

  1. 看我開發(fā)干貨集中營App(一)~ 開篇
  2. 看我開發(fā)干貨集中營App(二)~ APP初始化
  3. 看我開發(fā)干貨集中營App(三)~ 首頁開發(fā)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末菱蔬,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌拴泌,老刑警劉巖魏身,帶你破解...
    沈念sama閱讀 206,482評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異蚪腐,居然都是意外死亡箭昵,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門回季,熙熙樓的掌柜王于貴愁眉苦臉地迎上來家制,“玉大人,你說我怎么就攤上這事泡一〔梗” “怎么了?”我有些...
    開封第一講書人閱讀 152,762評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵瘾杭,是天一觀的道長诅病。 經(jīng)常有香客問我,道長粥烁,這世上最難降的妖魔是什么贤笆? 我笑而不...
    開封第一講書人閱讀 55,273評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮讨阻,結(jié)果婚禮上芥永,老公的妹妹穿的比我還像新娘。我一直安慰自己钝吮,他們只是感情好埋涧,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,289評(píng)論 5 373
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著奇瘦,像睡著了一般棘催。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上耳标,一...
    開封第一講書人閱讀 49,046評(píng)論 1 285
  • 那天醇坝,我揣著相機(jī)與錄音,去河邊找鬼次坡。 笑死呼猪,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的砸琅。 我是一名探鬼主播宋距,決...
    沈念sama閱讀 38,351評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼症脂!你這毒婦竟也來了谚赎?” 一聲冷哼從身側(cè)響起淫僻,我...
    開封第一講書人閱讀 36,988評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎壶唤,沒想到半個(gè)月后嘁傀,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,476評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡视粮,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,948評(píng)論 2 324
  • 正文 我和宋清朗相戀三年细办,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蕾殴。...
    茶點(diǎn)故事閱讀 38,064評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡笑撞,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出钓觉,到底是詐尸還是另有隱情茴肥,我是刑警寧澤,帶...
    沈念sama閱讀 33,712評(píng)論 4 323
  • 正文 年R本政府宣布荡灾,位于F島的核電站瓤狐,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏批幌。R本人自食惡果不足惜础锐,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,261評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望荧缘。 院中可真熱鬧皆警,春花似錦、人聲如沸截粗。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽绸罗。三九已至意推,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間珊蟀,已是汗流浹背菊值。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評(píng)論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留系洛,地道東北人俊性。 一個(gè)月前我還...
    沈念sama閱讀 45,511評(píng)論 2 354
  • 正文 我出身青樓略步,卻偏偏與公主長得像描扯,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子趟薄,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,802評(píng)論 2 345

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