Retrofit2 完全解析 探索与okhttp之间的关系(三)
来源:互联网 发布:大学蹭课软件 编辑:程序博客网 时间:2024/05/16 07:53
五、retrofit中的各类细节
(1)上传文件中使用的奇怪value值
第一个问题涉及到文件上传,还记得我们在单文件上传那里所说的吗?有种类似于hack的写法,上传文件是这么做的?
public interface ApiInterface { @Multipart @POST ("/api/Accounts/editaccount") Call<User> editUser (@Part("file_key\"; filename=\"pp.png"),@Part("username") String username); }
首先我们一点明确,因为这里使用了@ Multipart
,那么我们认为@Part
应当支持普通的key-value,以及文件。
对于普通的key-value是没问题的,只需要这样@Part("username") String username
。
那么对于文件,为什么需要这样呢?@Part("file_key\"; filename=\"pp.png")
这个value设置的值不用看就会觉得特别奇怪,然而却可以正常执行,原因是什么呢?
原因是这样的:
当上传key-value的时候,实际上对应这样的代码:
builder.addPart(Headers.of("Content-Disposition", "form-data; name=\"" + key + "\""), RequestBody.create(null, params.get(key)));
也就是说,我们的@Part
转化为了
Headers.of("Content-Disposition", "form-data; name=\"" + key + "\"")
这么一看,很随意,只要把key放进去就可以了。
但是,retrofit2并没有对文件做特殊处理,文件的对应的字符串应该是这样的
Headers.of("Content-Disposition", "form-data; name="filekey";filename="filename.png");
与键值对对应的字符串相比,多了个;filename="filename.png
,就因为retrofit没有做特殊处理,所以你现在看这些hack的做法
`@Part("file_key\"; filename=\"pp.png")`拼接Content-Disposition", "form-data; name=\"" + key + "\"结果:Content-Disposition", "form-data; name=file_key\"; filename=\"pp.png\"
ok,到这里我相信你已经理解了,为什么要这么做,而且为什么这么做可以成功!
恩,值得一提的事,因为这种方式文件名写死了,我们上文使用的的是@Part MultipartBody.Part file
,可以满足文件名动态设置,这个方式貌似也是2.0.1的时候支持的。
上述相关的源码:
#ServiceMethodif (annotation instanceof Part) { if (!isMultipart) { throw parameterError(p, "@Part parameters can only be used with multipart encoding."); } Part part = (Part) annotation; gotPart = true; String partName = part.value(); Headers headers = Headers.of("Content-Disposition", "form-data; name=\"" + partName + "\"", "Content-Transfer-Encoding", part.encoding());}
可以看到呢,并没有对文件做特殊处理,估计下个版本说不定@Part
会多个isFile=true|false
属性,甚至修改对应形参,然后在这里做简单的处理。
ok,最后来到关键的ConverterFactory
了~
五、自定义Converter.Factory
(1)responseBodyConverter
关于Converter.Factory
,肯定是通过addConverterFactory
设置的
Retrofit retrofit = new Retrofit.Builder() .addConverterFactory(GsonConverterFactory.create()) .build();
该方法接受的是一个Converter.Factory factory
对象
该对象呢,是一个抽象类,内部包含3个方法:
abstract class Factory { public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) { return null; } public Converter<?, RequestBody> requestBodyConverter(Type type, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) { return null; } public Converter<?, String> stringConverter(Type type, Annotation[] annotations, Retrofit retrofit) { return null; } }
可以看到呢,3个方法都是空方法而不是抽象的方法,也就表明了我们可以选择去实现其中的1个或多个方法,一般只需要关注requestBodyConverter
和responseBodyConverter
就可以了。
ok,我们先看如何自定义,最后再看GsonConverterFactory.create
的源码。
先来个简单的,实现responseBodyConverter
方法,看这个名字很好理解,就是将responseBody进行转化就可以了。
ok,这里呢,我们先看一下上述中我们使用的接口:
package com.zhy.retrofittest.userBiz;public interface IUserBiz{ @GET("users") Call<List<User>> getUsers(); @POST("users") Call<List<User>> getUsersBySort(@Query("sort") String sort); @GET("{username}") Call<User> getUser(@Path("username") String username); @POST("add") Call<List<User>> addUser(@Body User user); @POST("login") @FormUrlEncoded Call<User> login(@Field("username") String username, @Field("password") String password); @Multipart @POST("register") Call<User> registerUser(@Part("photos") RequestBody photos, @Part("username") RequestBody username, @Part("password") RequestBody password); @Multipart @POST("register") Call<User> registerUser(@PartMap Map<String, RequestBody> params, @Part("password") RequestBody password); @GET("download") Call<ResponseBody> downloadTest();}
不知不觉,方法还蛮多的,假设哈,我们这里去掉retrofit构造时的GsonConverterFactory.create
,自己实现一个Converter.Factory
来做数据的转化工作。
首先我们解决responseBodyConverter
,那么代码很简单,我们可以这么写:
public class UserConverterFactory extends Converter.Factory{ @Override public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) { //根据type判断是否是自己能处理的类型,不能的话,return null ,交给后面的Converter.Factory return new UserConverter(type); }}public class UserResponseConverter<T> implements Converter<ResponseBody, T>{ private Type type; Gson gson = new Gson(); public UserResponseConverter(Type type) { this.type = type; } @Override public T convert(ResponseBody responseBody) throws IOException { String result = responseBody.string(); T users = gson.fromJson(result, type); return users; }}
使用的时候呢,可以
Retrofit retrofit = new Retrofit.Builder() .callFactory(new OkHttpClient()) .baseUrl("http://192.168.1.102:8080/springmvc_users/user/")// .addConverterFactory(GsonConverterFactory.create()) .addConverterFactory(new UserConverterFactory()) .build();
ok,这样的话,就可以完成我们的ReponseBody
到List<User>
或者User
的转化了。
可以看出,我们这里用的依然是Gson,那么有些同学肯定不希望使用Gson就能实现,如果不使用Gson的话,一般需要针对具体的返回类型,比如我们针对返回List<User>
或者User
你可以这么写:
package com.zhy.retrofittest.converter;/** * Created by zhy on 16/4/30. */public class UserResponseConverter<T> implements Converter<ResponseBody, T>{ private Type type; Gson gson = new Gson(); public UserResponseConverter(Type type) { this.type = type; } @Override public T convert(ResponseBody responseBody) throws IOException { String result = responseBody.string(); if (result.startsWith("[")) { return (T) parseUsers(result); } else { return (T) parseUser(result); } } private User parseUser(String result) { JSONObject jsonObject = null; try { jsonObject = new JSONObject(result); User u = new User(); u.setUsername(jsonObject.getString("username")); return u; } catch (JSONException e) { e.printStackTrace(); } return null; } private List<User> parseUsers(String result) { List<User> users = new ArrayList<>(); try { JSONArray jsonArray = new JSONArray(result); User u = null; for (int i = 0; i < jsonArray.length(); i++) { JSONObject jsonObject = jsonArray.getJSONObject(i); u = new User(); u.setUsername(jsonObject.getString("username")); users.add(u); } } catch (JSONException e) { e.printStackTrace(); } return users; }}
这里简单读取了一个属性,大家肯定能看懂,这样就能满足返回值是Call<List<User>>
或者Call<User>
.
这里郑重提醒:如果你针对特定的类型去写Converter
,一定要在UserConverterFactory#responseBodyConverter
中对类型进行检查,发现不能处理的类型return null
,这样的话,可以交给后面的Converter.Factory
处理,比如本例我们可以按照下列方式检查:
public class UserConverterFactory extends Converter.Factory{ @Override public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) { //根据type判断是否是自己能处理的类型,不能的话,return null ,交给后面的Converter.Factory if (type == User.class)//支持返回值是User { return new UserResponseConverter(type); } if (type instanceof ParameterizedType)//支持返回值是List<User> { Type rawType = ((ParameterizedType) type).getRawType(); Type actualType = ((ParameterizedType) type).getActualTypeArguments()[0]; if (rawType == List.class && actualType == User.class) { return new UserResponseConverter(type); } } return null; }}
好了,到这呢responseBodyConverter
方法告一段落了,谨记就是将reponseBody->返回值返回中的实际类型,例如Call<User>中的User
;还有对于该converter不能处理的类型一定要返回null。
(2)requestBodyConverter
ok,上面接口一大串方法呢,使用了我们的Converter之后,有个方法我们现在还是不支持的。
@POST("add")Call<List<User>> addUser(@Body User user);
ok,这个@Body
需要用到这个方法,叫做requestBodyConverter
,根据参数转化为RequestBody
,下面看下我们如何提供支持。
public class UserRequestBodyConverter<T> implements Converter<T, RequestBody>{ private Gson mGson = new Gson(); @Override public RequestBody convert(T value) throws IOException { String string = mGson.toJson(value); return RequestBody.create(MediaType.parse("application/json; charset=UTF-8"),string); }}
然后在UserConverterFactory中复写requestBodyConverter方法,返回即可:
public class UserConverterFactory extends Converter.Factory{ @Override public Converter<?, RequestBody> requestBodyConverter(Type type, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) { return new UserRequestBodyConverter<>(); }}
这里偷了个懒,使用Gson将对象转化为json字符串了,如果你不喜欢使用框架,你可以选择拼接字符串,或者反射写一个支持任何对象的,反正就是对象->json字符串的转化。最后构造一个RequestBody返回即可。
ok,到这里,我相信如果你看的细致,自定义Converter.Factory
是干嘛的,但是我还是要总结下:
responseBodyConverter 主要是对应
@Body
注解,完成ResponseBody到实际的返回类型的转化,这个类型对应Call<XXX>
里面的泛型XXX,其实@Part
等注解也会需要responseBodyConverter,只不过我们的参数类型都是RequestBody
,由默认的converter处理了。requestBodyConverter 完成对象到RequestBody的构造。
一定要注意,检查type如果不是自己能处理的类型,记得return null (因为可以添加多个,你不能处理return null ,还会去遍历后面的converter).
六、值得学习的API
其实一般情况下看源码呢,可以让我们更好的去使用这个库,当然在看的过程中如果发现了一些比较好的处理方式呢,是非常值得记录的。如果每次看别人的源码都能吸取一定的精华,比你单纯的去理解会好很多,因为你的记忆力再好,源码解析你也是会忘的,而你记录下来并能够使用的优越的代码,可能用久了就成为你的代码了。
我举个例子:比如retrofit2中判断当前运行的环境代码如下,如果下次你有这样的需求,你也可以这么写,甚至源码中根据不同的运行环境还提供了不同的Executor
都很值得记录:
class Platform { private static final Platform PLATFORM = findPlatform(); static Platform get() { return PLATFORM; } private static Platform findPlatform() { try { Class.forName("android.os.Build"); if (Build.VERSION.SDK_INT != 0) { return new Android(); } } catch (ClassNotFoundException ignored) { } try { Class.forName("java.util.Optional"); return new Java8(); } catch (ClassNotFoundException ignored) { } try { Class.forName("org.robovm.apple.foundation.NSObject"); return new IOS(); } catch (ClassNotFoundException ignored) { } return new Platform(); }
- Retrofit2 完全解析 探索与okhttp之间的关系(三)
- Retrofit2 完全解析 探索与okhttp之间的关系(三)
- Retrofit2 完全解析 探索与okhttp之间的关系
- Retrofit2 完全解析 探索与okhttp之间的关系
- Retrofit2 完全解析 探索与okhttp之间的关系
- Retrofit2 完全解析 探索与okhttp之间的关系
- Retrofit2 完全解析 探索与okhttp之间的关系(一)
- Retrofit2 完全解析 探索与okhttp之间的关系
- Retrofit2 完全解析 探索与okhttp之间的关系
- Retrofit2 完全解析 探索与okhttp之间的关系
- Retrofit2 完全解析 探索与okhttp之间的关系
- Retrofit2 完全解析 探索与okhttp之间的关系
- Retrofit2 完全解析 探索与okhttp之间的关系
- 【精】Retrofit2 完全解析 探索与okhttp之间的关系
- Retrofit2 完全解析 探索与okhttp之间的关系
- Retrofit2 完全解析 探索与okhttp之间的关系
- Retrofit2 完全解析 探索与okhttp之间的关系
- Retrofit2 完全解析 探索与okhttp之间的关系
- 开发中遇到的问题
- 通过Util类随意调用Spring管理的Bean
- 自媒体时代?哪个才是“真爱”,如何利用自媒体营销让流量爆起来
- 三种强大的物体识别算法——SIFT/SURF、haar特征、广义hough变换的特性对比分析
- 如何居中对齐一个UICollectionView的 item (虽然略看了一下,但是代码是有效果的)
- Retrofit2 完全解析 探索与okhttp之间的关系(三)
- 关于对方法实例化的相关感悟以及unity的50个技巧
- 【ny-oj】-108-士兵杀敌(一)(树状数组,线段树,基础)
- AnimationSet.setRepeatCount无效问题
- ButterKnife框架原理
- HDU Problem 1166 敌兵布阵 【树状数组 & 线段树】
- Redis和Memcache的区别
- 使用chrome浏览器查看android在线源码
- 隐藏状态栏和操作栏