1. 程式人生 > >是時候客觀評價Retrofit了,Retrofit這幾點你必須明白!

是時候客觀評價Retrofit了,Retrofit這幾點你必須明白!

是時候客觀評價下Retrofit了,retrofit客觀存在的問題的你必須要知道!在用retrofit開發很久的朋友或多或少採了巨坑,閱讀原始碼和實踐後發現並不是我們認為的那麼靈活!

無恥的廣告又來了:

導讀:

優勢

  • 程式設計思想:減少解耦,降低耦合,讓我的介面開發靈活,不同api之間互相不干擾,

  • 程式碼風格:使用註解方式,程式碼簡潔,易懂,易上手

  • 設計思想:採用建造者模式,開發構建簡便!

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

常規問題歸總

1 url被轉義

   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);

上層需要轉換將自己的map轉換為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);

請在你的url指定佔位符.url:

www.mylist.com/get{Id}

不支援或缺陷

Url不能為空

由於我的需求場景是固定的域是動態的嗎,有時候我用www.myapi.com,有時候是www.youapi.com. 因此我決定在構建retrofit時候不加入baseUrl;

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

結果報異常了

Base URL required

原始碼中發現構建時候check Url,如果為空就異常

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

後來雖然對動態改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進行delete請求時,後臺介面定會了以body的格式!
於是乎我開心的定義了一下介面:

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

結果一個異常矇蔽了:

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

最後官網發現其並不支援向伺服器傳body,會報這個異常java.lang.IllegalArgumentException:Non-body HTTP method cannot contain @Body

gtihub作者也表示不支援body,最後發現了答案 用自定義註解,如需向伺服器傳body可以這麼寫

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

介面例項不支援T

我們每次用retrofit去執行一次網路請求,必定要定義一個ApiServie,而制定的介面必須要加入一個具體是例項!

public interface ApiService {

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

接著就去構建apiService例項!

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

構建Api

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

開發者很多時候遇到介面眾多情況下 想寫個一個baseApiService,然後不同模組的api去繼承這個baseApiService,那麼會去按常規的aop思想去繼承構建一個baseService, 其他他的子類實現這個方法,看看下面方法,具體返回物件被寫成T,是沒毛病!

public interface BaseApiService {

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

當我遇到一個登入和一個退出場景時候,不想寫到一個ApiService中,很有可能想去構建一個loginApiService和LoginOutApiService:

public class loginApiService implements BaseApiService {

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

 }

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

結果出問題了,我的天哪! 我這有錯嗎 我寫個介面,用實現類去執行,java告訴我這樣不行了嗎。矇蔽了,拋異常了!

API declarations must be interfaces.

這裡寫圖片描述

原始碼:

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)      
 }

結果:

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

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

public interface loginApiService extends BaseApiService {

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

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

這裡寫圖片描述

為了寫個通用介面我不得不:

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

這樣我的登入登出可以用一個介面,但每次返回的實體需要我自己解析,於是乎反射用上了

  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成功的的回撥中反序列化實體:

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

mType就是我用反射出來的上層傳入的user物件,尼瑪呀 我真不知道作者為何這麼設計,egg pains

這裡寫圖片描述

引數不支援空

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

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

我設計本意是上層可以動態傳慘,而且這個引數可能不固定

構建引數時:

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

執行程式,api 結果沒啥問題,到此我以為所有的引數都可以這麼加入,於是我下一個免登陸場景使用了此方案,token是伺服器返回的字串。每次請求加上去,如果本地沒有就不加,首次肯定是沒有的;構建引數:

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

構建:

  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) {


   }

結果執行,我擦磊,這樣也報錯,顯示token不能為空,難道我在不確定一個值的時候value還不能加入空,我不得不用下面方式構建引數,

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

最後讀取原始碼發現了@QueryMap k-v不能為空,好吧我醉了!

攔截預設異常

Retrofit攔截Okhttp預設error,如果web端預設在code在200或者300時候是正常msg資訊,走onResponse()。

如果web定義的成功碼如果是在< 200 並且 > 300時候,就不走成功 。並且伺服器如果已定義的結果碼和系統的預設int衝突情況,自定義的msg也無法回撥到onError()中,結果被retrofit主動獲取了super Throw的Msg資訊。

這裡寫圖片描述

歡迎關注個人公眾號:

這裡寫圖片描述