前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >魔改 TypeAdapterFactory

魔改 TypeAdapterFactory

作者头像
HelloVass
发布2018-09-12 10:25:14
1.9K0
发布2018-09-12 10:25:14
举报
文章被收录于专栏:Hellovass 的博客Hellovass 的博客

前言

感慨:Retrofit2 虽好,但是,有时候总感觉 Java 这门语言还是美中不足啊!

恼人的问题

想象一下,有这么几个 Api:

代码语言:javascript
复制

// 获取手机验证码
@POST("/user/sms") Observable<ServerResult<SmsCode>> fetchSmsCode(@Body PhoneNum phoneNum);

// 获取 AuthCode
@POST("/authcode") Observable<ServerResult<AuthCode>> fetchAuthCode(@Body SmsCodeAndOtherParams smsCodeAndOtherParams)

为什么很烦呢?

需要起名字

因为需要用 Gson 解析来解析 json,所以我们需要按照 server 返回的 json 来定义我们的请求体(被 @Body 注解的参数)以及响应体。然而,我这人最烦的就是命名了,本来词汇量就匮乏,还要想名字?

需要新建 class

上面两个 Api,我们需要定义 SmsCode、PhoneNum、AuthCode、SmsCodeAndOtherParams 四个类,然后在类里按照 json 的 key 定义对应的成员变量。

调用 Api 的时候,代码好脏啊

我们需要 new 参数,举栗:调用 fetchSmsCode 前,我们需要 new PhoneNum() 并且给它的成员变量赋值,当然如果我们是爱干净的好孩子的话可能会用 Builder 模式来做,但是谁来写这么多的 builder 呢?这可是体力活啊

解决方案

问个问题,如果是 JS 的话,这段代码该怎么写呢?

我想,大概是这样的:

代码语言:javascript
复制
var smsCode = etchSmsCode({"phoneNumber":"159XXXXXXXX"})

哇,真爽,不用新建 class,不用费力想名字,毕竟我们做 POST 请求的时候,只是想 POST 一个匿名的 JsonObject 而已,新建 class 什么的,真的没必要。

理想的 Api
代码语言:javascript
复制

/**
    * 获取验证码
    */
   @POST("/user/sms") Observable<Response<ServerResult<Params>>> fetchSmsCode(
       @Body Params params);

   /**
    * 获取 AuthCode
    */
   @POST("/authcode") Observable<Response<ServerResult<Params>>> fetchAuthCode(
       @Body Params params);
期望的调用
代码语言:javascript
复制

// 获取验证码
PeroRetrofitWrapperStore.getInstance()
        .provideApi(PeroApi.UserApi.class)
        .fetchSmsCode(new Params.Builder().put("phoneNumber", phoneNum).create())
        // ... 省略若干代码

虽然没有 JS 那么简洁,但是看起来已经没有那么恼人了。那么问题来了,该怎么魔改,实现这样的愿望呢?

利用 TypeAdapterFactory 这个接口

如果对我上篇文章有印象的小伙伴可能会留意下这段代码:

代码语言:javascript
复制

@Override public Converter.Factory createGsonConverterFactory() {

    Gson gson =
        new GsonBuilder().registerTypeAdapterFactory(provideParamsAdapterFactory()).create();

    return GsonConverterFactory.create(gson);
  }

这段代码里,我创建了一个 ParamsAdapterFactory (继承自 TypeAdapterFactory),并注册到了 GsonBuilder 中。那这有什么用呢?其实非常有用,TypeAdapterFactory 内部会创建一个 ParamsAdapter,接管了 json 的序列化和反序列化!

来看看我们的 ParamsAdapter 做了什么?代码不长,挑重要的分析下。因为我们接管了 json 的序列化过程,在执行 POST 请求的时候,fetchSmsCode(@Body parmas) 方法里接受的 params 参数就会走这段代码:

代码语言:javascript
复制

   @Override public void write(JsonWriter jsonWriter, Params params) throws IOException {

     if (params == null) {

       jsonWriter.nullValue();
       return;
     }

     jsonWriter.beginObject();

     Map<String, String> map = params.getExtrasMap();

     for (String key : map.keySet()) {

       jsonWriter.name(key);
       jsonWriter.value(map.get(key));
     }

     jsonWriter.endObject();
}

其实也就是序列化过程,最后 params 会转换成我们想要的 {"phoneNumber":"159XXXXXXX"} json 格式 ,接下来的事情就交给 Retrofit(像往常一样)。

同理可得,反序列化过程,就是将服务器返回的 json 解析为,我们期望的 params,代码如下:

代码语言:javascript
复制

@Override public Params read(JsonReader jsonReader) throws IOException {

      if (jsonReader.peek() == JsonToken.NULL) {

        jsonReader.nextNull();
        return null;
      }

      Params.Builder builder = new Params.Builder();

      jsonReader.beginObject();

      while (jsonReader.hasNext()) {

        String key = jsonReader.nextName();
        String value = jsonReader.nextString();

        builder.put(key, value);
      }

      jsonReader.endObject();

      return builder.create();
    }

反序列化完毕之后,我们就可以从 params 里拿到自己想要的值,例如:String authCode = params.get("authCode"),这里的 Params 就相当于一个 Map,事实上它内部确实是用一个 Map<String,String> 来存取键值对的。

代码语言:javascript
复制

public final class Params {

  private Map<String, String> mExtrasMap;

  private Params(Builder builder) {

    mExtrasMap = builder.mExtrasMap;
  }

  public String get(String key) {

    return mExtrasMap.get(key);
  }

  public Map<String, String> getExtrasMap() {

    return mExtrasMap;
  }

  @Override public String toString() {

    return "Params{" + "mExtrasMap=" + mExtrasMap + '}';
  }

  public static class Builder {

    private Map<String, String> mExtrasMap;

    public Builder() {

      mExtrasMap = new HashMap<>();
    }

    public Builder put(String key, String value) {

      mExtrasMap.put(key, value);
      return this;
    }

    public Params create() {

      return new Params(this);
    }
  }
}

代码灰常简单,对不对?到这里,魔改原理就差不多解释清楚了。

当然,可能会有人质疑,那 up 你的意思是劳资不用自己费力写 POJO,全用你的 Params 来替代?(如果我回答不是,你会不会一棒子打过来?)

使用场景

这个,我一开始也没提。如果到了不是非常有必要定义 POJO 的时候,比如,你只是想要 POST 一个 phoneNumber 或者 authCode 的时候,真的没必要为此定义 POJO,多累呢!遇到这种情况,能创建一个匿名的 params 就创建呗,省下来的时间还能陪陪学妹,何乐而不为!

也有童鞋会说,老板,你把序列化和反序列化全部接管了,如果我没有用你的 Params ,会不会解析异常?(这点我肯定考虑到了好吧,不然菊花肯定都没了)

类型检查

这里我们判断 rawType,如果是 Params 的话,就返回我们的 ParamsAdaper,否则返回 null,表示不支持。

代码语言:javascript
复制

@Override public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {

    Class<T> rawType = (Class<T>) typeToken.getRawType();

    if (Params.class.isAssignableFrom(rawType)) {

      return (TypeAdapter<T>) new ParamsAdapter();
    }

    return null;
  }
完整的 TypeAdapterFactory 代码
代码语言:javascript
复制

public final class ParamsAdapterFactory
    implements TypeAdapterFactory {

  @Override public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {

    Class<T> rawType = (Class<T>) typeToken.getRawType();

    if (Params.class.isAssignableFrom(rawType)) {

      return (TypeAdapter<T>) new ParamsAdapter();
    }

    return null;
  }

  private static final class ParamsAdapter extends TypeAdapter<Params> {

    @Override public void write(JsonWriter jsonWriter, Params params) throws IOException {

      if (params == null) {

        jsonWriter.nullValue();
        return;
      }

      jsonWriter.beginObject();

      Map<String, String> map = params.getExtrasMap();

      for (String key : map.keySet()) {

        jsonWriter.name(key);
        jsonWriter.value(map.get(key));
      }

      jsonWriter.endObject();
    }

    @Override public Params read(JsonReader jsonReader) throws IOException {

      if (jsonReader.peek() == JsonToken.NULL) {

        jsonReader.nextNull();
        return null;
      }

      Params.Builder builder = new Params.Builder();

      jsonReader.beginObject();

      while (jsonReader.hasNext()) {

        String key = jsonReader.nextName();
        String value = jsonReader.nextString();

        builder.put(key, value);
      }

      jsonReader.endObject();

      return builder.create();
    }
  }
}

PS:灵感来自 auto-value-factory

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2017-04-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 恼人的问题
    • 为什么很烦呢?
      • 需要起名字
      • 需要新建 class
      • 调用 Api 的时候,代码好脏啊
    • 解决方案
      • 问个问题,如果是 JS 的话,这段代码该怎么写呢?
      • 理想的 Api
      • 期望的调用
      • 利用 TypeAdapterFactory 这个接口
      • 完整的 TypeAdapterFactory 代码
相关产品与服务
验证码
腾讯云新一代行为验证码(Captcha),基于十道安全栅栏, 为网页、App、小程序开发者打造立体、全面的人机验证。最大程度保护注册登录、活动秒杀、点赞发帖、数据保护等各大场景下业务安全的同时,提供更精细化的用户体验。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档