《一個Android工程的從零開始》-8、base(七) Retrofit的封裝

先扯兩句

隔了這么長時間,深深的負罪感終于督促我寫下了這篇博客,當然,之前的時間也不全是在玩,參加了一個面試,進行了我人生中的第一次霸面,對方有個要求就是完成他們的demo,就可以得到面試機會,結(jié)果我完成以后單純的就去了,再然后就沒有然后了。
不過至少在這次demo中,我的基本框架得到了應(yīng)用,還是讓自己很欣慰的,另外還使用了一些博客后續(xù)將要與大家分享的內(nèi)容,算是一次提前的實戰(zhàn)吧,效果自認為還算滿意。
如果關(guān)注我博客的大家應(yīng)該知道,我前段時間寫的就是Retrofit的內(nèi)容,所以今天就從Retrofit的封裝開始寫起吧。
閑言少敘,老規(guī)矩還是先上我的Git庫,然后開始正文。
MyBaseApplication (https://github.com/BanShouWeng/MyBaseApplication)
另外,也把我做的這個小demo也發(fā)到的了庫中,也沒太深的東西內(nèi)容,大家如果感興趣的話也而已去看看:
https://github.com/BanShouWeng/IYuBaTestApplication
Retrofit的Header封裝部分放在了《一個Android工程的從零開始》階段總結(jié)與修改1-base,如有需要的朋友可以去其中查看。

正文

關(guān)于Retrofit的相關(guān)內(nèi)容呢,如果大家有什么不太清楚的地方,可以看一下我的上一篇博客Android開發(fā)相關(guān)——Retrofit+RxJava+OkHttp(下)使用,或許有人會疑問,為什么給了下,沒給上,主要還是因為下才是使用,上具體是什么,好奇的也可以去看一下Android開發(fā)相關(guān)——Retrofit+RxJava+OkHttp(上)閑扯,雖然我估計有一部分人,看到“閑扯”二字,或許就沒興趣了,不過剛步入android世界中的大家還是可以去看看的,或許會有收獲也說不定。
下面就正是開始封裝的部分:

分析

其是封裝,說白了就是為了我們在運用的時候能夠更方便,從我個人的角度出發(fā)還是兩個字——偷懶!
從《Android開發(fā)相關(guān)——Retrofit+RxJava+OkHttp(下)使用》中,大家或許也知道了,Retrofit網(wǎng)絡(luò)訪問框架需要的東西:

  1. 訪問數(shù)據(jù)回調(diào)接口
  2. 訪問方法

所以想要封裝,我們需要做的事,自然就是從這兩大塊入手,看看其中有哪些是可以直接復(fù)用的內(nèi)容,而這些內(nèi)容就是我們可以拿來偷懶的點:

訪問數(shù)據(jù)回調(diào)接口

 public interface Movie2Service {
        @GET("top250") 
        Observable<ResponseBody> getTop250(@Query("start") int start, @Query("count")int count);
    }

從上面的接口中,我們可以看得出來,其中我們可以操作的點,有三個:

  1. 尾址
  2. 參數(shù)
  3. 返回數(shù)據(jù)model(也就是Bean對象)

當然,如果你一定要說接口名也算的話,我也不反對。至少,在我一會說的“封裝方法一”中是不會反對的,具體為什么,我這里先賣個關(guān)子,一會再聊。而這三個可操作的點呢,在“封裝方法二”中才會用到,所以依然賣個關(guān)子,我們一會再聊,大家只需要暫時知道這三個點一會可以操作就行。

訪問方法

    private void getMovie2() {
        String baseUrl = "https://api.douban.com/v2/movie/";

        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(baseUrl)
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .build();

        Movie2Service movieService = retrofit.create(Movie2Service.class);

        movieService.getTopMovie(10, 10)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Observer<ResponseBody>() {
                    @Override
                    public void onSubscribe(@NonNull Disposable d) {

                    }

                    @Override
                    public void onNext(@NonNull ResponseBody responseBody) {
                        try {
                            String responseString = responseBody.string();
                            Log.i("responseString", responseString);
                            LogUtil.info("response", responseString);
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }

                    @Override
                    public void onError(@NonNull Throwable e) {

                    }

                    @Override
                    public void onComplete() {

                    }
                });
    }

這個訪問方法中,可以修改的點,同樣顯而易見:

  1. baseUrl
  2. 結(jié)果回調(diào),也就是subscribe傳入的接口

至于其他的部分,比如解析方式是json還是xml啊,是否使用RxJava啊,又或者線程的方式之類的參數(shù)自然也可以動態(tài)設(shè)置,只是對于一個工程項目而言,除非十分必要的情況下,考慮到開發(fā)難度以及開發(fā)周期等等諸多因素,很少會故意難為開發(fā)人員,而是采用一種萬能的模式即可,所以這里我就將這些因素都忽略掉了,如果你真的遇到一個這么變態(tài)的產(chǎn)品,我只能在這里為你默默祈福了。
而這兩部分中,baseUrl也算是比較特殊的存在,一般情況下,比較小的項目中基本只適用一個baseUrl就可以結(jié)束戰(zhàn)斗,哪怕大的項目,最多也就是每個模塊一個baseUrl,如果再大的,暫時還沒接觸到,但是想來也不會多多少。畢竟涉及到域名、端口等等問題,當然,在我看來這些都不是原因,主要還是后臺的戰(zhàn)友們,也懶??!
好吧,以上都是玩笑話,不過baseUrl很少有在網(wǎng)絡(luò)封裝的方法中體現(xiàn)的,無腦一點的方法就是封裝Utils包下的Const類中的靜態(tài)常量中,而相對正式點的玩法則是封裝到app mudule的build.grade中,并且可以去BuildConfig文件中查找,具體的玩法說來也簡單,不過誰讓我懶,還是直接上代碼以及BuildConfig目錄位置。

build.grade配置信息

signingConfigs {
        debug {
            storeFile file("./bsw.keystore")
            storePassword "bswbsw"
            keyAlias "wsbsw"
            keyPassword "wsbswhhh"
        }

        release {
            storeFile file("./bsw.keystore")
            storePassword "bswbsw"
            keyAlias "wsbsw"
            keyPassword "wsbswhhh"
        }
    }

    buildTypes {
        release {
            debug{
                minifyEnabled false
                proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
                buildConfigField "String", "BASE_URL_NAME", "\"baseUrl地址\""
                signingConfig signingConfigs.debug
            }
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            buildConfigField "String", "BASE_URL_NAME", "\"baseUrl地址\""
            signingConfig signingConfigs.release
        }
    }

BuildConfig位置

這里寫圖片描述

BuildConfig內(nèi)信息

package com.banshouweng.mybaseapplication;

public final class BuildConfig {
      public static final boolean DEBUG = Boolean.parseBoolean("true");
      public static final String APPLICATION_ID = "com.banshouweng.mybaseapplication";
      public static final String BUILD_TYPE = "debug";
      public static final String FLAVOR = "";
      public static final int VERSION_CODE = 1;
      public static final String VERSION_NAME = "1.0";
  // Fields from build type: debug
      public static final String BASE_URL_NAME = "baseUrl地址";
}

// Fields from build type: debug下的內(nèi)容是我們自行添加的信息

build.grade配置信息
其中minifyEnabled 與proguardFiles 是在創(chuàng)建項目的時候自帶的,分別代表是否混淆,而proguardFiles 太專業(yè)了,我也說不明白,大家找自己看一下吧

proguardFiles這部分有兩段,前一部分代表系統(tǒng)默認的android程序的混淆文件,該文件已經(jīng)包含了基本的混淆聲明,免去了我們很多事,這個文件的目錄在 /tools/proguard/proguard-android.txt , 后一部分是我們項目里的自定義的混淆文件,目錄就在 app/proguard-rules.txt , 如果你用Studio 1.0創(chuàng)建的新項目默認生成的文件名是 proguard-rules.pro , 這個名字沒關(guān)系,在這個文件里你可以聲明一些第三方依賴的一些混淆規(guī)則。

而其他部分則是我們都需要添加的了,buildConfigField中就是我們要添加的baseUrl,格式已經(jīng)給出,但是需要切記的一點是,這里添加的都是字符串,例如:

//輸入
  buildConfigField "String", "BASE_URL_NAME", "\"baseUrl地址\""
  buildConfigField "int", "COUNT", "0"
  buildConfigField "boolean", "ISOK", "true"

//顯示
// Fields from build type: debug
  public static final String BASE_URL_NAME = "baseUrl地址";
  public static final boolean ISOK = true;
  public static final int COUNT = 0;

//引用
  String url = DebugConfig.BASE_URL_NAME;
  int count = DebugConfig.COUNT;
  boolean isOk= DebugConfig.ISOK;

可以看得出來,如果我們賦值的內(nèi)容都需要傳遞的是字符串,顯示的才是我們需要的內(nèi)容,或者說AS用的是更無腦的玩法,那就是去掉最外層的引號,所以當創(chuàng)建String類型的參數(shù)時,需要使用的是""baseUrl地址""的形式(加粗斜體的所有部分),,如果只加一層引號的話,就會出現(xiàn)如下效果:

這里寫圖片描述

當然,如果我們將int或boolean的引號去掉,又會是另一個效果:

這里寫圖片描述

ssigningConfig signingConfigs.release這行就是以上的內(nèi)容,將會在何時觸發(fā)加載到BuildConfig文件中,我這里是分兩種情況:1、debug(自行調(diào)試的時候);2、release(發(fā)行的時候)。
而ssigningConfig 中則是對應(yīng)兩種情況的簽名信息:

  1. storeFile file("./bsw.keystore"):簽名文件位置(../xxx.keystore(或者xxx.jks))
  2. storePassword "bswbsw"簽名文件密碼
  3. keyAlias "wsbsw" 簽名別名
  4. keyPassword "wsbswhhh" 別名密碼

這些全都配置好,我們點擊Sync Now的時候,編譯結(jié)束,在BuildConfig中才會有我們要的內(nèi)容,如果只添加了release而不添加debug,那么就只有在發(fā)行包中才會在BuildConfig文件下生成對應(yīng)靜態(tài)常量,而我們平時開所處的環(huán)境是debug狀態(tài),所以這個時候,是我們是無法在BuildConfig中看到對應(yīng)的靜態(tài)常量的,所以開發(fā)時也找不到,調(diào)試時也用也會報錯,所以開發(fā)時一定要對應(yīng)添加debug和release才可以

這里寫圖片描述

關(guān)于build.gradle,文件自然還有許多其他的妙用,這里就先不列舉了,我們還是回歸到正文,也就是說,baseUrl通過這上述的Const或者DebugConfig這兩種方法集成即可,就不需要額外花時間了。
所以重頭戲也就都在結(jié)果回調(diào)的接口上了。

封裝方法1——最無腦的封裝

看了這哥標題一定會有人問,什么叫最無腦的封裝,很好理解啊,那就是封裝起來特別簡單,用起來,相當麻煩。又有人會問,既然麻煩為什么還要去這么封裝,其實很簡單,這種封裝的用途就是為了應(yīng)付那些腦洞打開的產(chǎn)品的,萬一他們真想出來什么喪心病狂的需求,我們還無法不去完成的情況下,自然就需要用這種麻煩的封裝了。
至于為什么不直接使用Retrofit,一定還要封裝一下,自然是萬一產(chǎn)品下次又爆發(fā)了一個相類似的腦洞的情況下,我們可以稍做修改,便能直接拿來用,一旦直接使用Retrofit了,下次還得重新寫,或者再去找之前加到哪里了,麻煩!
而這種封裝所需要的包(對包的部分不太清楚的參見《一個Android工程的從零開始》-1前期準備)便是apimagager以及service。
service自不必說,看名字也能知道,它肯定是存放回調(diào)接口的,而回調(diào)接口我們自然也能加一定的處理,從Retrofit官網(wǎng)中,我們可以發(fā)現(xiàn)其中有兩種玩法很有趣,可以拿來用一下,我們先來看一下效果:

public interface Movie2Service {
    @GET("{action}")
    Observable<ResponseBody> getTopMovie(@Path("action") String action, @QueryMap Map<String, String> params);
}

顯而易見,就是@Path以及@QueryMap,先說@QueryMap,其實對比一下之前的@Query就會發(fā)現(xiàn),它只不過是在后面加了一個Map,至于功能,還是傳遞的參數(shù),只不過原本的玩法需要傳遞一個名字為name的String參數(shù)John,那就要先定義一個String變量,命名name,然后賦值。這樣原本來說也不麻煩,可是一旦讓傳遞大量參數(shù)的時候,就顯得有些不便了,所以就使用了Map去傳遞,同樣的傳遞John,只需要一個map.put("name", "john");即可,大量數(shù)據(jù)的時候,繼續(xù)put就好,什么時候都put好了,將這個map傳遞進來就達到了目的。
接下來就是@Path,先看看官方的說法:

A request URL can be updated dynamically using replacement blocks and parameters on the method. A replacement block is an alphanumeric string surrounded by { and }. A corresponding parameter must be annotated with @Path using the same string.

嗯,除了看不懂沒有別的缺點了!所以我也不打算逐字去翻譯了,其實看例子也能看出來,這就是一個占位符,用“{占位符名}”站好位,然后@Path("占位符名")按照其中的占位符名,將傳遞進來的內(nèi)容內(nèi)容放置在對應(yīng)的占位符名所占的位置上。所以傳進來的action就會被當做我們的尾址來使用。而我們的返回Bean則依照要求取就好,我這里是使用的OKHttp的ResponseBody。如此,Service就完成了。

apimagager,這部分就完整看產(chǎn)品的需求了,基本框架在上面,自行添加修改一下就好,當然返回值是肯定需要處理一下的,不過這部分我會在下一個封裝方法中詳細說明,這里只管調(diào)用即可。

封裝方法2

這次沒給額外的說明,也是我最后的一種封裝方法,就是打算弄一個一招鮮吃遍天的玩法,雖然封裝起來會麻煩許多,但是有點也很明顯,用著方便,至于方便什么,必然是方便偷懶了。
先說說我給這個封裝方法找的位置,作為一個懶人,雖然這個方法也可以放置在ApiMageger中,但是調(diào)用的時候,竟然還得讓我new個對象,有這時間我給自己new個女朋友好不?;蛘哒f是用靜態(tài)方法,不過說實話,靜態(tài)的方法或者常量變量還是盡量減少使用,畢竟靜態(tài)內(nèi)存也不富裕,再說不考慮這個,我們不還是需要ApiMageger.getXXX嗎。有這時間,我研究要追那個妹子好不。
總之,在偷懶心理作祟下,我選擇了將這個網(wǎng)絡(luò)訪問的封裝放置在了BaseActivity以及BaseFragment中,用的時候直接掉方法即可。
不過如果遇到個別的需要在封裝的Adapter中掉訪問網(wǎng)絡(luò)的方法時就比較尷尬,需要多費些周折,例如發(fā)個EvenBus之類的,說起來也不算麻煩。
具體是封裝在APIManager中還是封裝在BaseActivity/BaseFragment就看你個人的需求情況,我下面的內(nèi)容就先按照封裝在BaseActivity/BaseFragment進行,相信提出來放在APIManager的操作,對大家來說還是小菜一碟的。

數(shù)據(jù)回調(diào)1


    //網(wǎng)絡(luò)訪問
    public <T extends BaseBean> void get1(final String action, final Class<T> cls) {

        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(baseUrl)
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .build();

        GetService1 getService = retrofit.create(GetService1.class);
        if (params == null) {
            params = new HashMap<>();
        }
        getService.get(action, params)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Observer<ResponseBody>() {
                    @Override
                    public void onSubscribe(@NonNull Disposable d) {

                    }

                    @Override
                    public void onNext(@NonNull ResponseBody responseBody) {
                        try {
                            T t = new Gson().fromJson(responseBody.string(), cls);
                            success(action, t);
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }

                    @Override
                    public void onError(@NonNull Throwable e) {
                        error(action, e);
                    }

                    @Override
                    public void onComplete() {

                    }
                });
    }

    //成功方法
    public void success(String action, BaseBean baseBean) {

    }
    //失敗方法
    public void error(String action, Throwable e) {

    }

在上述方法之前,先創(chuàng)建和一個共有的Map類進行傳參,由于是繼承的我們的BaseActivity/BaseFragment,所以這里不需要我們傳遞Map參數(shù)了,直接調(diào)用即可。因為這里我們要使用一個萬能的封裝法,很遺憾的是,在使用Retrofit的時候,直接使用泛型T傳遞的時候,會報錯Observer< T >不能直接使用,所以我暫時的解決方法只能是傳遞OkHttp中的ResponseBody類,然后通過string()方法獲取其中的json串,再通過GSON解析成我們需要的對應(yīng)類。并將其傳遞到success方法中,在訪問的Activity重寫該方法即可,至于為什么要將action也傳遞回來,主要是在進行多次網(wǎng)絡(luò)請求的時候,用來分辨對應(yīng)區(qū)分是誰發(fā)出的請求,隨后做出對應(yīng)的處理。
不過這個方法的缺點就是:1、我們需要額外重寫成功或者失敗的方法,還是有點麻煩;2、success方法中的參數(shù)是BaseBean(參見附錄1),而不是我們想要生成的Bean對象,還需要強轉(zhuǎn)一下。
當然以上屬于吹毛求疵,不過為了偷懶,所以我就想到了下面的方法2。

封裝方法2

這次沒給額外的說明,也是我最后的一種封裝方法,就是打算弄一個一招鮮吃遍天的玩法,雖然封裝起來會麻煩許多,但是有點也很明顯,用著方便,至于方便什么,必然是方便偷懶了。
先說說我給這個封裝方法找的位置,作為一個懶人,雖然這個方法也可以放置在ApiMageger中,但是調(diào)用的時候,竟然還得讓我new個對象,有這時間我給自己new個女朋友好不。或者說是用靜態(tài)方法,不過說實話,靜態(tài)的方法或者常量變量還是盡量減少使用,畢竟靜態(tài)內(nèi)存也不富裕,再說不考慮這個,我們不還是需要ApiMageger.getXXX嗎。有這時間,我研究要追那個妹子好不。
總之,在偷懶心理作祟下,我選擇了將這個網(wǎng)絡(luò)訪問的封裝放置在了BaseNetActivity以及BaseNetFragment中,用的時候直接掉方法即可。
不過如果遇到個別的需要在封裝的Adapter中掉訪問網(wǎng)絡(luò)的方法時就比較尷尬,需要多費些周折,例如發(fā)個EvenBus之類的,說起來也不算麻煩。
具體是封裝在APIManager中還是封裝在BaseNetActivity/BaseNetFragment就看你個人的需求情況,我下面的內(nèi)容就先按照封裝在BaseNetActivity/BaseNetFragment進行,相信提出來放在APIManager的操作,對大家來說還是小菜一碟的。

對于封裝,第一件事就是需要我們創(chuàng)建一個BaseNetActivity/BaseNetFragment,至于為什么沒有按照本篇博客修改之前放到BaseActivity/BaseFragment中,主要是有一些界面還是不需要網(wǎng)絡(luò)訪問的,雖然少一些,但是集成這些東西還是很好資源的,另外就是之前我們的BaseActivity中已經(jīng)放了很多內(nèi)容,如果網(wǎng)絡(luò)訪問的內(nèi)容也放到其中難免有寫太冗雜了,所以網(wǎng)絡(luò)訪問的部分就單獨拿出來了一個BaseNetActivity/BaseNetFragment。當然,與之前的Activity/Fragment不同的是,BaseNetActivity/BaseNetFragment不需要我們關(guān)聯(lián)布局文件,因為它的工作只是網(wǎng)絡(luò)訪問而已,BaseNetActivity/BaseNetFragment繼承BaseActivity/BaseFragment這樣就可以使用到網(wǎng)絡(luò)與布局的雙封裝了。
看了前面的封裝方法一,很顯然,它的作用就是對應(yīng)每一個創(chuàng)建對應(yīng)Service,既然如此,大家一定猜到了,這部分的封裝就是一個通用的方法了,也就是無論你想要用什么樣子的Bean都隨你心情。
先上Service代碼:

public interface RetrofitGetService {
    @GET("{action}")
    Observable<ResponseBody> getResult(@Path("action") String action, @QueryMap Map<String, String> params);
}

大家可以看得出來,這個部分與我們上面的方法一舉例用的是完全相同的,而為什么值用ResponseBody,主要還是為了其提供的string()方法可以獲得JSON串,方便我們自行轉(zhuǎn)換。
再就是創(chuàng)建初始化retrofit的方法:

private void initBaseData() {
        OkHttpClient.Builder builder = new OkHttpClient.Builder();
        builder.connectTimeout(5, TimeUnit.SECONDS);
        retrofit = new Retrofit.Builder()
                .client(builder.build())
                .baseUrl(baseUrl)
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .build();
    }

其中OkHttpClient的作用就是connectTimeout方法,用于設(shè)置5s鏈接超時的處理。

隨后就是結(jié)果回調(diào)的接口,用于將得到的結(jié)果傳回去 ,這部分其實用單純的方法也可以,在Activity中重寫就可以得到結(jié)果參數(shù),可是懶人我實在懶得去記需要重寫的方法名,用接口的就可以很好的回避掉這點了。

public interface ResultCallBack<T extends BaseBean> {
        void success(String action, T t);

        void error(String action, Throwable e);
 }

萬事俱備,下面就該進行正式的封裝部分了

    /**
     * Get請求
     *
     * @param action   請求接口的尾址,如“top250”
     * @param clazz    要轉(zhuǎn)換的Bean類型(需繼承BaseBean)
     * @param callBack 結(jié)果回調(diào)接口
     * @param <T>      用于繼承BaseBean的占位變量
     */
    public <T extends BaseBean> void get(final String action, final Class<T> clazz, final ResultCallBack callBack) {

        if (getService == null) {
            getService = retrofit.create(RetrofitGetService.class);
        }
        if (params == null) {
            params = new HashMap<>();
        }
        getService.getResult(action, params)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Observer<ResponseBody>() {
                    @Override
                    public void onSubscribe(@NonNull Disposable d) {

                    }

                    @Override
                    public void onNext(@NonNull ResponseBody responseBody) {
                        try {
                            callBack.success(action, new Gson().fromJson(responseBody.string(), clazz));
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }

                    @Override
                    public void onError(@NonNull Throwable e) {
                        callBack.error(action, e);
                    }

                    @Override
                    public void onComplete() {

                    }
                });
    }

    /**
     * Post請求
     *
     * @param action   請求接口的尾址,如“top250”
     * @param clazz    要轉(zhuǎn)換的Bean類型(需繼承BaseBean)
     * @param callBack 結(jié)果回調(diào)接口
     * @param <T>      用于繼承BaseBean的占位變量
     */
    public <T extends BaseBean> void post(final String action, final Class<T> clazz, final ResultCallBack callBack) {
        if (postService == null) {
            postService = retrofit.create(RetrofitPostService.class);
        }

        if (params == null) {
            params = new HashMap<>();
        }
        postService.postResult(action, params)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Observer<ResponseBody>() {
                    @Override
                    public void onSubscribe(@NonNull Disposable d) {

                    }

                    @Override
                    public void onNext(@NonNull ResponseBody responseBody) {
                        try {
                            callBack.success(action, new Gson().fromJson(responseBody.string(), clazz));
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }

                    @Override
                    public void onError(@NonNull Throwable e) {
                        callBack.error(action, e);
                    }

                    @Override
                    public void onComplete() {

                    }
                });
    }

可以看到,JSON轉(zhuǎn)換成Bean使用的是Gson解析,其余沒有變化,如果有一些各個接口都需要添加的參數(shù),就可以對應(yīng)的方法中直接添加,以免重復(fù)操作。

疑問##

雖然我的封裝達到了目的,可是Retrofit本身不應(yīng)該出現(xiàn)前面出現(xiàn)的問題,我這里也有一個疑問,究竟是我還沒有找到Retrofit的正確使用方式,還是Retrofit自身卻是存在這個漏洞,也希望大家有所發(fā)現(xiàn)能為我指點迷津,在此先謝過各位了。BaseNetActivity完整代碼參見附錄2

附錄

附錄1

BaseBean:
其實也沒什么特殊的部分,其中都是一些基礎(chǔ)的部分,比如網(wǎng)絡(luò)訪問是否成功之類的處理,具體大家可以參考各種errorCode,就比如打擊熟悉的404就是其中的一種,當然,這個錯誤碼肯定與404不同,而是后臺定義的錯誤碼,常用的場合就是登錄時,用戶沒有注冊、賬戶密碼不正確之類的錯誤情況判斷。
再有一點就是,將BaseBean序列號,便于Activity與Activity之間,或者Activity與Fragment之間傳值。

public class BaseBean implements Serializable {}

這里實現(xiàn)序列號的方法是實現(xiàn)Serializable 接口,當然還有一種玩法就是實現(xiàn)Parcelable接口,至于兩種的區(qū)別,請參見Serializable 和 Parcelable 兩種序列化

附錄2

public class BaseNetActivity extends BaseActivity {
    private String baseUrl = "https://api.douban.com/v2/movie/";
    private RetrofitGetService getService;
    private RetrofitPostService postService;
    private Retrofit retrofit;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        initBaseData();
    }

    private void initBaseData() {
        OkHttpClient.Builder builder = new OkHttpClient.Builder();
        builder.connectTimeout(5, TimeUnit.SECONDS);
        retrofit = new Retrofit.Builder()
                .client(builder.build())
                .baseUrl(baseUrl)
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .build();
    }

    /**
     * Get請求
     *
     * @param action   請求接口的尾址,如“top250”
     * @param clazz    要轉(zhuǎn)換的Bean類型(需繼承BaseBean)
     * @param callBack 結(jié)果回調(diào)接口
     * @param <T>      用于繼承BaseBean的占位變量
     */
    public <T extends BaseBean> void get(final String action, final Class<T> clazz, final ResultCallBack callBack) {

        if (getService == null) {
            getService = retrofit.create(RetrofitGetService.class);
        }
        if (params == null) {
            params = new HashMap<>();
        }
        params.put("start", "0");
        params.put("count", "10");

        getService.getResult(action, null, params)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Observer<ResponseBody>() {
                    @Override
                    public void onSubscribe(@NonNull Disposable d) {

                    }

                    @Override
                    public void onNext(@NonNull ResponseBody responseBody) {
                        try {
                            String responseString = responseBody.string();
                            Log.i("responseString", "responseString get  " + responseString);
                            callBack.success(action, new Gson().fromJson(responseBody.string(), clazz));
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }

                    @Override
                    public void onError(@NonNull Throwable e) {
                        callBack.error(action, e);
                    }

                    @Override
                    public void onComplete() {

                    }
                });
    }

    /**
     * Post請求
     *
     * @param action   請求接口的尾址,如“top250”
     * @param clazz    要轉(zhuǎn)換的Bean類型(需繼承BaseBean)
     * @param callBack 結(jié)果回調(diào)接口
     * @param <T>      用于繼承BaseBean的占位變量
     */
    public <T extends BaseBean> void post(final String action, final Class<T> clazz, final ResultCallBack callBack) {
        if (postService == null) {
            postService = retrofit.create(RetrofitPostService.class);
        }

        if (params == null) {
            params = new HashMap<>();
        }
        params.put("start", "0");
        params.put("count", "10");

        postService.postResult(action, null, params)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Observer<ResponseBody>() {
                    @Override
                    public void onSubscribe(@NonNull Disposable d) {

                    }

                    @Override
                    public void onNext(@NonNull ResponseBody responseBody) {
                        try {
                            String responseString = responseBody.string();
                            Log.i("responseString", "responseString post  " + responseString);
                            callBack.success(action, new Gson().fromJson(responseBody.string(), clazz));
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }

                    @Override
                    public void onError(@NonNull Throwable e) {
                        callBack.error(action, e);
                    }

                    @Override
                    public void onComplete() {

                    }
                });
    }

    public interface ResultCallBack<T extends BaseBean> {
        void success(String action, T t);

        void error(String action, Throwable e);
    }
}

附錄3

《一個Android工程的從零開始》- 目錄

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容