封装网络请求之 - 通过Gson转换多级泛型

2,592 阅读10分钟
原文链接: mp.weixin.qq.com
点击上方蓝字关注我
本文主要是为了解决问题,所以大部分代码都比较简单,所以可以看看。(虽然我平时看技术文章也会容易跳过大段大段的代码) 。

内容并没有很深入Retrofit源码,主要是Gson提供了很好的函数给我们调用,大家放心食用。

由于每个人的网络库都不一定一样,所以文章中多是用通用的写法,大家可以参考到自己的网络框架中去。

最近在维护公司项目时,发现老项目用的网络库需要每次获得请求结果后手动对结果转换成数据模型,不支持像 Retrofit 那样能够自动转换请求结果的字符串为实例化对象,也就是传递多级泛型进去并返回实例化对象,例如:

1mApiService.postUserRegister(username, password) 2                .subscribeOn(Schedulers.io()) 3                .observeOn(AndroidSchedulers.mainThread()) 4                .subscribe(new BaseSubscriber<Result<UserModel>>() { 5                    @Override 6                    public void onNext(Result<UserModel> mResult) { 7                        UserModel mUserModel = mResult.getData(); 8                        mLoginView.register(null, mUserModel); 9                        Log.d(TAG, "注册成功: "+mUserModel.getUsername());1011                    }1213                    @Override14                    public void onApiException(ApiException e) {1516                    }17                });

代码中的BaseSubscriber<>接收一个泛型,而 <Result<UserModel>>就是一个多级泛型,而在网络请求的回调中,public void onNext(Result<UserModel> mResult),其中的 mResult就是一个实例化的对象,这是我们很常见的一个操作。

这时候就需要自己去了解如何转换多级泛型,有的同学会觉得,用Gson转换多级泛型很常见啊,有什么特别的,那我们先来看看我们最常见的用法。

最常用的Gson转泛型

我们平时使用Gson来转换泛型的时候,最常见的就是使用以下的例子:

1Gson gson = new Gson();2String jsonStr = "{'username':'Caspar','password':'123456'}";3UserModel userModel = gson.fromJson(jsonStr, UserModel.class);

我们对一个Json字符串进行转换的时候,通常使用gson.fromJson(String,Class<T>)函数,第一个参数接收一个字符串,第二个参数接收一个Class类型,用来将需要转换的类型传递进去。

再回头看看Retrofit的例子,是Result<UserModel>,而不是 Result<UserModel.class>,这样写可不对。

而且上述例子只是简单的转换,而不是多级泛型的转换,接下来看看Gson转换多级泛型。

Gson转换多级泛型

我们平时最常见的转换多级泛型,就是转换List<T>类型,例如现在有一组用户数据 ArrayList<UserModel>需要转换,代码如下:

1Gson gson = new Gson();2String jsonStr = "[" +3                    "{'username':'Java','password':'123456'}," +4                    "{'username':'Python','password':'123456'}" +5                 "]";6ArrayList<UserModel> list = gson.fromJson(jsonStr, new TypeToken<ArrayList<UserModel>>(){}.getType());

我们传入的类型不是ArrayList<UserModel.class>,也不是 ArrayList<UserModel>.class,我们在实际操作中发现这样写会报错,于是就开始上网搜索gson string 转换 list对象,得到的答案就是使用 new TypeToken<ArrayList<UserModel>>(){}.getType()来作为类型传入,就可以得到实例化的List 对象了。

第一次尝试将多级泛型转换应用在网络框架中

现在再回想一下,Retrofit是使用例如:new BaseSubscriber<Result<UserModel>>()方式,就能得到实例化对象,而我们在第一节 最常用的Gson转泛型中所讲的方法只适合单个泛型的转换,不适合多级泛型,再看看Gson转换多级泛型中的方法,好像是能够实现,于是就写了一段:

                                    

1public <T>  void convertJson (String json, Type typeOfT) {2    Gson gson =  new Gson();3    T result = gson.fromJson(json, typeOfT); 4    httpClient.onSuccess(result);5}

这一段转换很简单,其中的httpClient可以理解为网络请求调用了onSuccess回调,将实例化的结果传递回去,这样看好像是实现了多级泛型,但是我们再想想,实际情况我们将会怎样使用这个函数呢?

                                        

1String jsonStr = http.getUserList(); 2convertJson<ArrayList<UserModel>>(jsonStr,new TypeToken<ArrayList<UserModel>>(){}.getType()){ 3    @Override 4    public  void onSuccess(ArrayList<UserModel> result) { 56    } 7})

我们需要调用convertJson,然后传入泛型convertJson<ArrayList<UserModel>>,并且在函数的参数中,还需要再传一次 new TypeToken<ArrayList<UserModel>>(){}.getType()类型,与我们预期的根本不一样,这样每次写网络请求那不累死去?

了解Retrofit是如何实现的

所以接着看了一下RetrofitGsonConverterFactory.class是如何实现的,用过Retrofit的同学应该对GsonConverterFactory.class比较眼熟,我们常在初始化的时候用到它,使得Retrofit和Gson能够配合使用。

我们发现在GsonConverterFactory中有一个函数:

                                                

1@Override 2public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, 3    Retrofit retrofit) {4  TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type)); 5  return  new GsonResponseBodyConverter<>(gson, adapter);6}

发现转换代码接收一个Type类型的参数,然后用 TypeToken.get()转换成TypeAdapter,觉得这里的转换应该是一个重点,于是继续跟进下面的函数,看看使用adapter做了什么:

发现了一个convert方法,使用newJsonReader将value转换为 JsonReader,这里的value就是网络请求的结果,然后用adapter.read(jsonReader)就将结果转换成实例化对象了?Woc?这么简单的吗?!

重点就是如下两行:

1JsonReader jsonReader = gson.newJsonReader(value.charStream());23return adapter.read(jsonReader);

最终实现

既然发现了Gson还有这么神奇的用法,那我们像回推导,现在我们需要TypeAdapter,TypeAdapter是由 gson.getAdapter(TypeToken.get(type))获取的,那就需要TypeToken.get(type),这时候发现,我们需要的就是一个 Type type类型。

应该有很多人在学习和处理泛型这个问题上,回去搜索各种各样的问题,应该见到过一个熟悉的函数:(这段代码熟悉的应该有印象,不熟悉不用深读,没关系的)

1/** 2 * 通过反射,获得定义Class时声明的父类的范型参数的类型. 3 * 如public BookManager extends GenricManager<Book> 4 * 5 * @param clazz clazz The class to introspect 6 * @param index the Index of the generic ddeclaration,start from 0. 7 */ 8public static Class getSuperClassGenricType(Class clazz, int index) throws IndexOutOfBoundsException { 910    Type genType = clazz.getGenericSuperclass();1112    if (!(genType instanceof ParameterizedType)) {13        return Object.class;14    }1516    Type[] params = ((ParameterizedType) genType).getActualTypeArguments();1718    if (index >= params.length || index < 0) {19        return Object.class;20    }21    if (!(params[index] instanceof Class)) {22        if (params[index] instanceof ParameterizedType) {23            try {24                return Class.forName(((ParameterizedType) params[index]).getRawType().toString());25            } catch (ClassNotFoundException e) {26                //e.printStackTrace();27            }28            //return (Class) ((ParameterizedType) params[index]).getActualTypeArguments()[0];29        }30        return Object.class;31    }32    return (Class) params[index];33}

其中Type[] params = ((ParameterizedType) genType).getActualTypeArguments();就能获取到一个 Type[]对象,而params[0]就是我们需要的 Type对象了,于是我们可以通过两行代码得到我们想要的TypeAdapter

                                                                

1    Type[] params = ((ParameterizedType) genType).getActualTypeArguments(); 2    mTypeAdapter = (TypeAdapter<T>) gson.getAdapter(TypeToken.get(params[0]));

在获取到网络请求结果之后,就可以很简单的将结果转换成多级泛型的实例化对象了:

1String json = httpClient.getUserList();2return adapter.fromJson(json);

封装成一个Callback:

1public abstract class HttpCallback<T> { 2    private TypeAdapter<T> mTypeAdapter; 3 4    public HttpCallback() { 5        Type genType = this.getClass().getGenericSuperclass(); 6        Gson gson = new Gson(); 7        Type[] params = ((ParameterizedType) genType).getActualTypeArguments(); 8        mTypeAdapter = (TypeAdapter<T>) gson.getAdapter(TypeToken.get(params[0])); 9    }1011    public T convertData(Response response) {12        String json = response.body();13        T data = mTypeAdapter.fromJson(json);14        onSuccess(data, response);15    }1617    public abstract void onSuccess(T data, Response response);18    public abstract void onError(Response response, Exception e);19    public abstract void onFinish();2021}

让自己的网络库中接入HttpCallback,调用 convertData()方法,就可以将多级泛型转换,并且调用onSuccess()将结果回调给调用者。

在外部使用可参考:

1http.getUserList(HttpCallback<ArrayList<UserModel>>(){2    @Override3    public void onSuccess(ArrayList<UserModel> data) {45    }6})

就可以实现和Retrofit一样的使用方法了。

将网络框架这样封装后,终于对维护老项目没有那么抵触了,舒坦!

再啰嗦一遍

本文主要是为了解决问题,所以大部分代码都比较简单,所以可以看看。(虽然我平时看技术文章也会容易跳过大段大段的代码)。

内容并没有很深入Retrofit源码,主要是Gson提供了很好的函数给我们调用,大家放心食用。

由于每个人的网络库都不一定一样,所以文章中多是用通用的写法,大家可以参考到自己的网络框架中去。