Tamic/CSDN
尊重原创:http://blog.csdn.net/sk719887916/article/details/53613263
是时候客观评价下Retrofit了,retrofit客观存在的问题的你必须要知道!在用retrofit开发很久的朋友或多或少采了巨坑,阅读源码和实践后发现并不是我们认为的那么灵活!
无耻的广告又来了:
导读:
- Retrofit 2.0(一) 超能实践,完美支持Https传输
- Retrofit2.0(二) 完美同步Cookie实现免登录
- Retrofit 2.0 超能实践(三),轻松实现文件/图片上传
- Retrofit 2.0 超能实践(四),完成大文件断点下载
- 基于Retrofit2.0+RxJava 封装的超好用的RetrofitClient工具类(六)
- 玩转IOC,教你徒手实现自定义的Retrofit框架(七)
- Retrofit,Okhttp对每个Request统一动态添加header和参数(五)
- Rxjava和Retrofit 需要掌握的几个实用技巧,缓存问题和统一对有无网络处理问题(八)
- Novate:对Retrofit2.0的又一次完美改进加强!(九)
优势
- 编程思想:减少解耦,降低耦合,让我的接口开发灵活,不同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信息。
Tamic/CSDN
尊重原创:http://blog.csdn.net/sk719887916/article/details/53613263
欢迎关注个人公众号: