Retrofit開發(fā)踩的巨坑!

是時候客觀評價下Retrofit了,retrofit客觀存在的問題的你必須要知道!在用retrofit開發(fā)很久的朋友或多或少采了巨坑,閱讀源碼和實(shí)踐后發(fā)現(xiàn)并不是我們認(rèn)為的那么靈活!

優(yōu)勢

  • 編程思想:減少解耦,降低耦合,讓我的接口開發(fā)靈活,不同api之間互相不干擾,

  • 代碼風(fēng)格:使用注解方式,代碼簡潔,易懂,易上手

  • 設(shè)計思想:采用建造者模式,開發(fā)構(gòu)建簡便!

    具體優(yōu)勢讀者請閱讀之前系列文章,顯而易見!那今天就來吐槽一下不足,至少我覺得egg pains的地方!

常規(guī)問題歸總

1 url被轉(zhuǎn)義

   http://api.myapi.com/
   http%3A%2F%2Fapi.mysite.com%2Fuser%2Flist

請將@path改成@url

   public interface APIService { 
    @GET Call<Users> getUsers(@Url String url);}

或者:

  public interface APIService {
    @GET("{fullUrl}")
    Call<Users> getUsers(@Path(value = "fullUrl", encoded = true) String fullUrl);
}

Method方法找不到

java.lang.IllegalArgumentException: Method must not be null

請指定具體請求類型@get @post等

   public interface APIService { 

   @GET Call<Users> getUsers(@Url String url);
}

Url編碼不對,@fieldMap parameters must be use FormUrlEncoded

如果用fieldMap加上FormUrlEncoded編碼

@POST()
@FormUrlEncoded
Observable<ResponseBody> executePost (@FieldMap Map<String, Object> maps);

上層需要轉(zhuǎn)換將自己的map轉(zhuǎn)換為FieldMap

 @FieldMap(encoded = true) Map<String, Object> parameters,

4 paht和url一起使用

Using @Path and @Url paramers together with retrofit2

java.lang.IllegalArgumentException: @Path parameters may not be used with @Url. (parameter #4

如果你是這樣的:

 @GET
Call<DataResponse> getOrder(@Url String url, @Path("id") int id);

請?jiān)谀愕膗rl指定占位符.url:

www.mylist.com/get{Id}

不支持或缺陷

Url不能為空

由于我的需求場景是固定的域是動態(tài)的嗎,有時候我用www.myapi.com,有時候是www.youapi.com. 因此我決定在構(gòu)建retrofit時候不加入baseUrl;

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

結(jié)果報異常了

Base URL required

源碼中發(fā)現(xiàn)構(gòu)建時候check Url,如果為空就異常

public Retrofit build() {
  if (baseUrl == null) {
    throw new IllegalStateException("Base URL required.");
  }

后來雖然對動態(tài)改Url很好解決,用@url代替,但我我怎么也不明白為何要限制!

@GET 
Call getOrder(@Url String url,  @Path(“id”) int id);

Delete不支持body

Retrofit @Delete with body,Non-body HTTP method cannot contain @Body ##

使用retrofit進(jìn)行delete請求時,后臺接口定會了以body的格式!
于是乎我開心的定義了一下接口:

@DELETE("/user/delete")
Call<Void> remove (@Body HashMap<String,String> content);

結(jié)果一個異常蒙蔽了:

java.lang.IllegalArgumentException:Non-body HTTP method cannot contain @Body

最后官網(wǎng)發(fā)現(xiàn)其并不支持向服務(wù)器傳body,會報這個異常java.lang.IllegalArgumentException:Non-body HTTP method cannot contain @Body ,gtihub作者也表示不支持body,最后發(fā)現(xiàn)了答案 用自定義注解,如需向服務(wù)器傳body可以這么寫

@HTTP(method = "DELETE",path = "/user/delete",hasBody = true)
Call<Void> remove (@Body HashMap<String,String> content);

接口實(shí)例不支持T

我們每次用retrofit去執(zhí)行一次網(wǎng)絡(luò)請求,必定要定義一個ApiServie,而制定的接口必須要加入一個具體是實(shí)例!

public interface ApiService {

@GET
Call<DataResponse> get(@Url String url, @Query("id") int id);
}

接著就去構(gòu)建apiService實(shí)例!

Retrofit retrofit = new Retrofit.Builder()
        .baseUrl("http://localhost:8080/")
        .addConverterFactory(GsonConverterFactory.create())
        .build();

構(gòu)建Api

ApiServicer apiService = retrofit.create(
ApiService.class);

開發(fā)者很多時候遇到接口眾多情況下 想寫個一個baseApiService,然后不同模塊的api去繼承這個baseApiService,那么會去按常規(guī)的aop思想去繼承構(gòu)建一個baseService, 其他他的子類實(shí)現(xiàn)這個方法,看看下面方法,具體返回對象被寫成T,是沒毛??!

public interface BaseApiService {

  @GET
  Call<T> get(@Url String url,  @Path("id") int id);

}

當(dāng)我遇到一個登錄和一個退出場景時候,不想寫到一個ApiService中,很有可能想去構(gòu)建一個loginApiService和LoginOutApiService:

public class loginApiService implements BaseApiService {

  @GET
  Call<User> get(@Url String url, @Query("id") int id)   {
  // ......
  }   

 }

ApiServicer apiService = retrofit.create(loginApiService.class);

結(jié)果出問題了,我的天哪! 我這有錯嗎 我寫個接口,用實(shí)現(xiàn)類去執(zhí)行,java告訴我這樣不行了嗎。蒙蔽了,拋異常了!

API declarations must be interfaces.

image.png

源碼:

static <T> void validateServiceInterface(Class<T> service) {
if (!service.isInterface()) {
  throw new IllegalArgumentException("API declarations must be interfaces.");
}

好的 作者意圖很明顯 用接口類型,你說用接口,好 我照著做!

public interface loginApiService extends BaseApiService {

  @GET
  Call<T> get(@Url String url,@Query("id") int id)      

 }

結(jié)果:

T is not a valid response body type. Did you mean ResponseBody?

我感覺我一定要解決,我強(qiáng)制更改了父類的返回值,以為能通過!

public interface loginApiService extends BaseApiService {

  @GET
  Call<User> get(@Url String url, @Query("id") int id)      
 }

結(jié)果都編譯不過,我的天哪!不用泛型,我開始蒙逼了,難道讓我每個請求接口都寫一個Api方法,雖然通過九牛二虎之力,用反射解決了,但我我真想說 :nnD

image.png

為了寫個通用接口我不得不:

  @GET
  Call<ResponseBody> get(@Url String url, @Map<String, String> mapsid)      
 

這樣我的登錄登出可以用一個接口,但每次返回的實(shí)體需要我自己解析,于是乎反射用上了

  private List<Type> MethodHandler(Type[] types) {
    Log.d(TAG, "types size: " + types.length);
    List<Type> needtypes = new ArrayList<>();
    Type needParentType = null;
    for (Type paramType : types) {
        // if Type is T
        if (paramType instanceof ParameterizedType) {
            Type[] parentypes = ((ParameterizedType) paramType).getActualTypeArguments();

            for (Type childtype : parentypes) {
                needtypes.add(childtype);
                if (childtype instanceof ParameterizedType) {
                    Type[] childtypes = ((ParameterizedType) childtype).getActualTypeArguments();
                    for (Type type : childtypes) {
                        needtypes.add(type);
                        //needChildType = type;
                        Log.d(TAG, "type:" + childtype);
                    }
                }
            }
        }
    }
    return types;
}

接著我在Retroift成功的的回調(diào)中反序列化實(shí)體:

 User user = new Gson().fromJson(ResponseBody.body.toString(), mType);

mType就是我用反射出來的上層傳入的user對象,尼瑪呀 我真不知道作者為何這么設(shè)計,egg pains

image.png

參數(shù)不支持空

上面的問題我不說啥,現(xiàn)在到了我無法忍受的地方,比如我們定義一個api

@GET("/path")
Call<ResponseBody> get (@QueryMap Map<String, String> mapsid)      

我設(shè)計本意是上層可以動態(tài)傳慘,而且這個參數(shù)可能不固定

構(gòu)建參數(shù)時:

 Map<String, String> parameters = new HashMap<>();
    parameters.put("apikey", "27b6fb21f2b42e9d70cd722b2ed038a9");
    parameters.put("Accept", "application/json");

運(yùn)行程序,api 結(jié)果沒啥問題,到此我以為所有的參數(shù)都可以這么加入,于是我下一個免登陸場景使用了此方案,token是服務(wù)器返回的字符串。每次請求加上去,如果本地沒有就不加,首次肯定是沒有的;構(gòu)建參數(shù):

    Map<String, String> parameters = new HashMap<>();
    parameters.put("token", getToken());
    parameters.put("Accept", "application/json");

構(gòu)建:

  Call<LoginResult> call = apiService.get(parameters);
  call.enqueue(new Callback<User>() {
   @Override
   public void onResponse(Call<User> call, Response<LoginResult> response) {

   }

   @Override
   public void onFailure(Call<user> call, Throwable t) {

   }

結(jié)果運(yùn)行,我擦磊,這樣也報錯,顯示token不能為空,難道我在不確定一個值的時候value還不能加入空,我不得不用下面方式構(gòu)建參數(shù),

   Map<String, String> parameters = new HashMap<>();
    parameters.put("token", getToken() == Null?gettoken() :" " );
    parameters.put("Accept", "application/json");

最后讀取源碼發(fā)現(xiàn)了@QueryMap k-v不能為空,好吧我醉了!

攔截默認(rèn)異常

Retrofit攔截Okhttp默認(rèn)error,如果web端默認(rèn)在code在200或者300時候是正常msg信息,走onResponse()。

如果web定義的成功碼如果是在< 200 并且 > 300時候,就不走成功 。并且服務(wù)器如果已定義的結(jié)果碼和系統(tǒng)的默認(rèn)int沖突情況,自定義的msg也無法回調(diào)到onError()中,結(jié)果被retrofit主動獲取了super Throw的Msg信息。

image.png

文章轉(zhuǎn)自:是時候客觀評價Retrofit了,Retrofit這幾點(diǎn)你必須明白
Tamic/CSDN 尊重原創(chuàng):http://blog.csdn.net/sk719887916/article/details/53613263

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

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

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