是時候客觀評價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資訊。
歡迎關注個人公眾號: