前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >是时候客观评价Retrofit了,这几点你必须明白

是时候客观评价Retrofit了,这几点你必须明白

作者头像
开发者技术前线
发布2020-11-23 11:22:43
1.3K0
发布2020-11-23 11:22:43
举报
文章被收录于专栏:开发者技术前线

是时候客观评价下Retrofit了,Retrofit客观存在的问题的你必须要知道!在用Retrofit开发很久的朋友或多或少踩了巨坑,阅读源码和实践后发现并不是我们认为的那么灵活!

优势

  • 编程思想:减少解耦,降低耦合,让我的接口开发灵活,不同api之间互相不干扰,
  • 代码风格:使用注解方式,代码简洁,易懂,易上手
  • 设计思想:采用建造者模式,开发构建简便!

具体优势读者请阅读之前系列文章,显而易见!那今天就来吐槽一下不足,至少我觉得Egg Pains的地方!

常规问题归总

1 url被转义

代码语言:javascript
复制
   http://api.myapi.com/http%3A%2F%2Fapi.mysite.com%2Fuser%2Flist

请将@path改成@url

代码语言:javascript
复制
   public interface APIService {
   @GET Call<Users> getUsers(@Url String url);}

或者:

代码语言:javascript
复制
  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等

代码语言:javascript
复制
   public interface APIService {  @GET Call<Users> getUsers(@Url String url);}

Url编码不对,@fieldMap parameters must be use FormUrlEncoded

如果用fieldMap加上FormUrlEncoded编码

代码语言:javascript
复制
@POST()
@FormUrlEncoded
Observable<ResponseBody> login(       
       @FieldMap Map<String, Object> maps);

上层需要转换将自己的map转换为FieldMap

代码语言:javascript
复制
 @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

如果你是这样的:

代码语言:javascript
复制
 @GETCall<DataResponse> getOrder(@Url String url,
@Path("id") int id);

请在你的url指定占位符,url可以这样:

代码语言:javascript
复制
www.mylist.com/get{Id}

不支持或缺陷

Url不能为空

由于我的需求场景是固定的域是动态的吗,有时候我用www.myapi.com,有时候是www.youapi.com. 因此我决定在构建retrofit时候不加入baseUrl;

代码语言:javascript
复制
Retrofit retrofit = new Retrofit.Builder()        .addConverterFactory(GsonConverterFactory.create())
       .build();

结果报异常了

Base URL required

源码中发现构建时候check Url,如果为空就异常

代码语言:javascript
复制
public Retrofit build() {  
  if (baseUrl == null) {    
   throw new IllegalStateException("Base URL required.");
 }

后来虽然对动态改Url也能很好解决(下面代码片段),用@url代替,但我怎么也不明白为何加入此要限制!

代码语言:javascript
复制
 @GET Call<DataResponse> getOrder(@Url String url,
                                 @Path("id") int id);

Delete不支持body

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

使用retrofit进行delete请求时,后台接口定会了以body的格式! 于是乎我开心的定义了一下接口:

代码语言:javascript
复制
@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可以这么写

代码语言:javascript
复制
@HTTP(method = "DELETE",path = "/user/delete",hasBody = true)Call<Void> remove (@Body HashMap<String,String> content);

接口实例不支持T

我们每次用retrofit去执行一次网络请求,必定要定义一个ApiServie,而制定的接口必须要加入一个具体实例!

代码语言:javascript
复制
public interface ApiService {@GETCall<DataResponse> get(@Url String url,
                          @Query("id") int id);}

接着就去构建apiService实例!

代码语言:javascript
复制
Retrofit retrofit = new Retrofit.Builder()
       .baseUrl("http://localhost:8080/")
       .addConverterFactory(GsonConverterFactory.create())
       .build();

构建Api

代码语言:javascript
复制
ApiServicer apiService = retrofit.create(ApiService.class);

开发者很多时候遇到接口众多情况下 想写个一个baseApiService,然后不同模块的api去继承这个baseApiService,那么会去按常规的aop思想去继承构建一个baseService, 其他他的子类实现这个方法,看看下面方法,具体返回对象被写成T,是没毛病!

代码语言:javascript
复制
public interface BaseApiService {@GET Call<T> get(@Url String url,
                @Path("id") int id);}

当我遇到一个登录和一个退出场景时候,不想写到一个ApiService中,很有可能想去构建一个loginApiService和LoginOutApiService:

代码语言:javascript
复制
public class loginApiService implements BaseApiService {@GETCall<User> get(@Url String url,
                  @Query("id") int id)   {
            // ......
      }  
}ApiServicer apiService = retrofit.create(
loginApiService.class);

结果出问题了,我的天哪! 我这有错吗 我写个接口,用实现类去执行,java告诉我这样不行了吗。蒙蔽了,抛异常了!

API declarations must be interfaces.

源码:

代码语言:javascript
复制
static <T> void validateServiceInterface(Class<T> service) {
if (!service.isInterface()) {  
throw new IllegalArgumentException("API declarations must be interfaces.");
}

好的 ,意思很明显,必须要用接口类型,你说用接口,好 我照着做!

代码语言:javascript
复制
public interface loginApiService extends BaseApiService {
@GETCall<T> get(@Url String url,
               @Query("id") int id)      
}

结果:

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

我一定要解决, 我强制更改了父类get函数的返回值,以为能通过!

代码语言:javascript
复制
public interface loginApiService extends BaseApiService
代码语言:javascript
复制
   @GETCall<User> get(@Url String url,
                      @Query("id") int id)      
}

结果都编译不过,我的天哪! 不能用泛型,我开始蒙逼了,难道让我每个请求接口都写一个Api方法,虽然通过九牛二虎之力,用反射解决了,但我我真想说 :NND

为了写个通用接口我不得不:

代码语言:javascript
复制
@GETCall<ResponseBody> get(@Url String url,
                      @Map<String, String> mapsid)      
}

这样我的登录登出可以用一个接口,但每次返回的实体需要我自己解析,于是乎反射用上了

代码语言:javascript
复制
  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成功的的回调中反序列化实体地方

代码语言:javascript
复制
 User user = 
    new Gson().fromJson(ResponseBody.body.toString(), mType);

mType就是我用反射出来的上层传入的user对象,尼玛呀 我真不知道作者为何这么设计,egg pains

参数不支持空

上面的问题我不说啥,现在到了我无法忍受的地方,比如我们定义一个api

代码语言:javascript
复制
@GET("/path")Call<ResponseBody> get(
                      @QueryMap<String, String> mapsid)      
}

我设计本意是上层可以动态传惨,而且这个参数可能不固定

构建参数时:

代码语言:javascript
复制
 Map<String, String> parameters = new HashMap<>();
    parameters.put("apikey", "27b6fb21f2b42e9d70cd722b2ed038a9");
    parameters.put("Accept", "application/json");

运行程序,api 结果没啥问题,到此我以为所有的参数都可以这么加入,于是我下一个免登陆场景使用了此方案,token是服务器返回的字符串。每次请求加上去,如果本地没有就不加,首次肯定是没有的;构建参数:

代码语言:javascript
复制
   Map<String, String> parameters = new HashMap<>();
     parameters.put("token", getToken());
     parameters.put("Accept", "application/json");

构建:

代码语言:javascript
复制
  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还不能加入空,我不得不用下面方式构建参数,

代码语言:javascript
复制
   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 Throwe的Msg信息。结果和后端一起自定义的协议码都变为了空谈!

结尾无耻的广告又来了 点击原文可查看链接:


RX系列导读:

---完---

Tamic开发社区

专业高水准的移动社区

Android & iOS

长按二维码关注

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2016-12-13,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 开发者技术前线 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 优势
  • 常规问题归总
  • 不支持或缺陷
    • Url不能为空
      • Delete不支持body
        • 接口实例不支持T
          • 参数不支持空
            • 拦截默认异常
            相关产品与服务
            云服务器
            云服务器(Cloud Virtual Machine,CVM)提供安全可靠的弹性计算服务。 您可以实时扩展或缩减计算资源,适应变化的业务需求,并只需按实际使用的资源计费。使用 CVM 可以极大降低您的软硬件采购成本,简化 IT 运维工作。
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档