Retrofit学习笔记
来源:互联网 发布:怎样才能不沉迷网络 编辑:程序博客网 时间:2024/06/07 09:22
添加依赖
最新版本
compile 'com.squareup.retrofit2:retrofit:2.2.0' compile 'com.squareup.retrofit2:converter-gson:2.2.0' compile 'com.squareup.retrofit2:converter-scalars:2.2.0'
定义Http请求接口
Retrofit将网络请求转换为了Java接口,所以接下来定义网络访问接口:
public interface RetrofitInterface { @GET("api/Class/GetAllClass")//注意此处url不是以“/”开头的 Call<Demo> getStates(@Query("DeviceCode") String deviceCode); @GET("api/Class/GetAllClass") Call<String> getStatesString(@Query("DeviceCode") String deviceCode);}
这里用到的是我项目中的一个接口,将设备号传给后台,后台返回设备信息,两个请求是一样的,一个用到了Gson解析,一个返回原生的String信息。
接下来初始化一个Retrofit:
public static Retrofit getRetrofit() { if (mRetrofit == null) { mRetrofit = new Retrofit.Builder().baseUrl(URL)//注意此处url以“/”结尾 // .addConverterFactory(GsonConverterFactory.create()) .addConverterFactory(ScalarsConverterFactory.create()) .build(); } return mRetrofit; }
请求可以是同步请求(call.excute()),也可以是异步请求(call.enquene())
void simpleTest() { Call<Demo> demoCall = Utils.getRetrofit().create(RetrofitInterface.class).getStates("1001") ; demoCall.enqueue(new Callback<Demo>() { @Override public void onResponse(Call<Demo> call, Response<Demo> response) { Demo demo = response.body() ; Log.e(TAG,demo.toString()) ; } @Override public void onFailure(Call<Demo> call, Throwable t) { Log.e(TAG,t.toString() + "--" + call.toString()) ; } }); }
Retrofit注解详解
第一类:HTTP请求方法
引用网友的一张图片,详细说明了几种请求方法。
以上表格中的除HTTP以外都对应了HTTP标准中的请求方法,而HTTP注解则可以代替以上方法中的任意一个注解,有3个属性:method、path,hasBody。
public interface BlogService { /** * method 表示请的方法,不区分大小写 * path表示路径 * hasBody表示是否有请求体 */ @HTTP(method = "get", path = "blog/{id}", hasBody = false) Call<ResponseBody> getFirstBlog(@Path("id") int id);}
第二类:标记类
public interface BlogService { /** * {@link FormUrlEncoded} 表明是一个表单格式的请求(Content-Type:application/x-www-form-urlencoded) * <code>Field("username")</code> 表示将后面的 <code>String name</code> 中name的取值作为 username 的值 */ @POST("/form") @FormUrlEncoded Call<ResponseBody> testFormUrlEncoded1(@Field("username") String name, @Field("age") int age); /** * Map的key作为表单的键 */ @POST("/form") @FormUrlEncoded Call<ResponseBody> testFormUrlEncoded2(@FieldMap Map<String, Object> map); /** * {@link Part} 后面支持三种类型,{@link RequestBody}、{@link okhttp3.MultipartBody.Part} 、任意类型 * 除 {@link okhttp3.MultipartBody.Part} 以外,其它类型都必须带上表单字段({@link okhttp3.MultipartBody.Part} 中已经包含了表单字段的信息), */ @POST("/form") @Multipart Call<ResponseBody> testFileUpload1(@Part("name") RequestBody name, @Part("age") RequestBody age, @Part MultipartBody.Part file); /** * PartMap 注解支持一个Map作为参数,支持 {@link RequestBody } 类型, * 如果有其它的类型,会被{@link retrofit2.Converter}转换,如后面会介绍的 使用{@link com.google.gson.Gson} 的 {@link retrofit2.converter.gson.GsonRequestBodyConverter} * 所以{@link MultipartBody.Part} 就不适用了,所以文件只能用<b> @Part MultipartBody.Part </b> */ @POST("/form") @Multipart Call<ResponseBody> testFileUpload2(@PartMap Map<String, RequestBody> args, @Part MultipartBody.Part file); @POST("/form") @Multipart Call<ResponseBody> testFileUpload3(@PartMap Map<String, RequestBody> args); }
@Streaming@GETCall<ResponseBody> downloadFileWithDynamicUrlAsync(@Url String fileUrl);ResponseBody body = response.body();long fileSize = body.contentLength();InputStream inputStream = body.byteStream();
第三类:参数类
注1:{占位符}和PATH尽量只用在URL的path部分,url中的参数使用Query和QueryMap 代替,保证接口定义的简洁
注2:Query、Field和Part这三者都支持数组和实现了Iterable接口的类型,如List,Set等,方便向后台传递数组。
public class Example03 { public interface BlogService { /** * {@link FormUrlEncoded} 表明是一个表单格式的请求(Content-Type:application/x-www-form-urlencoded) * <code>Field("username")</code> 表示将后面的 <code>String name</code> 中name的取值作为 username 的值 */ @POST("/form") @FormUrlEncoded Call<ResponseBody> testFormUrlEncoded1(@Field("username") String name, @Field("age") int age); /** * Map的key作为表单的键 */ @POST("/form") @FormUrlEncoded Call<ResponseBody> testFormUrlEncoded2(@FieldMap Map<String, Object> map); /** * {@link Part} 后面支持三种类型,{@link RequestBody}、{@link okhttp3.MultipartBody.Part} 、任意类型 * 除 {@link okhttp3.MultipartBody.Part} 以外,其它类型都必须带上表单字段({@link okhttp3.MultipartBody.Part} 中已经包含了表单字段的信息), */ @POST("/form") @Multipart Call<ResponseBody> testFileUpload1(@Part("name") RequestBody name, @Part("age") RequestBody age, @Part MultipartBody.Part file); /** * PartMap 注解支持一个Map作为参数,支持 {@link RequestBody } 类型, * 如果有其它的类型,会被{@link retrofit2.Converter}转换,如后面会介绍的 使用{@link com.google.gson.Gson} 的 {@link retrofit2.converter.gson.GsonRequestBodyConverter} * 所以{@link MultipartBody.Part} 就不适用了,所以文件只能用<b> @Part MultipartBody.Part </b> */ @POST("/form") @Multipart Call<ResponseBody> testFileUpload2(@PartMap Map<String, RequestBody> args, @Part MultipartBody.Part file); @POST("/form") @Multipart Call<ResponseBody> testFileUpload3(@PartMap Map<String, RequestBody> args); } public static void main(String[] args) { Retrofit retrofit = new Retrofit.Builder() .baseUrl("http://localhost:4567/") .build(); BlogService service = retrofit.create(BlogService.class); // 演示 @FormUrlEncoded 和 @Field Call<ResponseBody> call1 = service.testFormUrlEncoded1("怪盗kidou", 24); ResponseBodyPrinter.printResponseBody(call1); //=================================================== // 演示 @FormUrlEncoded 和 @FieldMap // 实现的效果与上面想同 Map<String, Object> map = new HashMap<>(); map.put("username", "怪盗kidou"); map.put("age", 24); Call<ResponseBody> call2 = service.testFormUrlEncoded2(map); ResponseBodyPrinter.printResponseBody(call2); //=================================================== MediaType textType = MediaType.parse("text/plain"); RequestBody name = RequestBody.create(textType, "怪盗kidou"); RequestBody age = RequestBody.create(textType, "24"); RequestBody file = RequestBody.create(MediaType.parse("application/octet-stream"), "这里是模拟文件的内容"); // 演示 @Multipart 和 @Part MultipartBody.Part filePart = MultipartBody.Part.createFormData("file", "test.txt", file); Call<ResponseBody> call3 = service.testFileUpload1(name, age, filePart); ResponseBodyPrinter.printResponseBody(call3); //=================================================== // 演示 @Multipart 和 @PartMap // 实现和上面同样的效果 Map<String, RequestBody> fileUpload2Args = new HashMap<>(); fileUpload2Args.put("name", name); fileUpload2Args.put("age", age); //这里并不会被当成文件,因为没有文件名(包含在Content-Disposition请求头中),但上面的 filePart 有 //fileUpload2Args.put("file", file); Call<ResponseBody> call4 = service.testFileUpload2(fileUpload2Args, filePart); //单独处理文件 ResponseBodyPrinter.printResponseBody(call4); //=================================================== // 还有一种比较hack的方式可以实现文件上传, // 上面说过被当成文件上传的必要条件就是 Content-Disposition 请求头中必须要有 filename="xxx" 才会被当成文件 // 所有我们在写文件名的时候可以拼把 filename="XXX" 也拼接上去, // 即文件名变成 表单键名"; filename="文件名 (两端的引号会自动加,所以这里不加)也可以实现,但是不推荐方式 Map<String, RequestBody> fileUpload3Args = new HashMap<>(); fileUpload3Args.put("name",name); fileUpload3Args.put("age",age); fileUpload3Args.put("file\"; filename=\"test.txt",file); Call<ResponseBody> testFileUpload3 = service.testFileUpload3(fileUpload3Args); ResponseBodyPrinter.printResponseBody(testFileUpload3); }}
Gson与Converter
Retrofit提供了六种转换器,并且可以自定义转换器。
常用的有:
Gson: com.squareup.retrofit2:converter-gson
Simple XML: com.squareup.retrofit2:converter-simplexml
Scalars (primitives, boxed, and String): com.squareup.retrofit2:converter-scalars
自定义实现方式:新建一个类继承Converter.Factory类,并在构建Retrofit实例时传入转换器实例。
在默认情况下Retrofit只支持将HTTP的响应体转换换为ResponseBody,
这也是什么我在前面的例子接口的返回值都是 Call,
但如果响应体只是支持转换为ResponseBody的话何必要引用泛型呢,
返回值直接用一个Call就行了嘛,既然支持泛型,那说明泛型参数可以是其它类型的,
而Converter就是Retrofit为我们提供用于将ResponseBody转换为我们想要的类型,
有了Converter之后我们就可以写把我们的第一个例子的接口写成这个样子了:
public interface BlogService { @GET("blog/{id}") //这里的{id} 表示是一个变量 Call<Result<Blog>> getFirstBlog(/** 这里的id表示的是上面的{id} */@Path("id") int id);}
RxJava与CallAdapter
引入RxJava支持:
compile 'com.squareup.retrofit2:adapter-rxjava:2.0.2'
通过RxJavaCallAdapterFactory为Retrofit添加RxJava支持:
Retrofit retrofit = new Retrofit.Builder() .baseUrl("http://localhost:4567/") .addConverterFactory(GsonConverterFactory.create()) .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) .build();
接口设计:
public interface BlogService { @POST("/blog") Observable<Result<List<Blog>>> getBlogs();}
使用:
BlogService service = retrofit.create(BlogService.class);service.getBlogs(1) .subscribeOn(Schedulers.io()) .subscribe(new Subscriber<Result<List<Blog>>>() { @Override public void onCompleted() { System.out.println("onCompleted"); } @Override public void onError(Throwable e) { System.err.println("onError"); } @Override public void onNext(Result<List<Blog>> blogsResult) { System.out.println(blogsResult); } });
像上面的这种情况最后我们无法获取到返回的Header和响应码的,如果我们需要这两者,提供两种方案:
1、用Observable 《Response《T》 代替Observable《T》 ,这里的Response指retrofit2.Response
2、用Observable《Result《T》》 代替Observable《T》,这里的Result是指retrofit2.adapter.rxjava.Result,这个Result中包含了Response的实例
自定义Converter
这里以返回格式为Call为例。
在此之前先了解一下Converter接口及其作用:
public interface Converter<F, T> { // 实现从 F(rom) 到 T(o)的转换 T convert(F value) throws IOException; // 用于向Retrofit提供相应Converter的工厂 abstract class Factory { // 这里创建从ResponseBody其它类型的Converter,如果不能处理返回null // 主要用于对响应体的处理 public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) { return null; } // 在这里创建 从自定类型到ResponseBody 的Converter,不能处理就返回null, // 主要用于对Part、PartMap、Body注解的处理 public Converter<?, RequestBody> requestBodyConverter(Type type, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) { return null; } // 这里用于对Field、FieldMap、Header、Path、Query、QueryMap注解的处理 // Retrfofit对于上面的几个注解默认使用的是调用toString方法 public Converter<?, String> stringConverter(Type type, Annotation[] annotations, Retrofit retrofit) { return null; } }}
我们要想从Call 转换为 Call 那么对应的F和T则分别对应ResponseBody和String,我们定义一个StringConverter并实现Converter接口。
public static class StringConverter implements Converter<ResponseBody, String> { public static final StringConverter INSTANCE = new StringConverter(); @Override public String convert(ResponseBody value) throws IOException { return value.string(); }}
我们需要一个Fractory来向Retrofit注册StringConverter
public static class StringConverterFactory extends Converter.Factory { public static final StringConverterFactory INSTANCE = new StringConverterFactory(); public static StringConverterFactory create() { return INSTANCE; } // 我们只关实现从ResponseBody 到 String 的转换,所以其它方法可不覆盖 @Override public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) { if (type == String.class) { return StringConverter.INSTANCE; } //其它类型我们不处理,返回null就行 return null; }}
使用Retrofit.Builder.addConverterFactory向Retrofit注册我们StringConverterFactory:
Retrofit retrofit = new Retrofit.Builder() .baseUrl("http://localhost:4567/") // 如是有Gson这类的Converter 一定要放在其它前面 .addConverterFactory(StringConverterFactory.create()) .addConverterFactory(GsonConverterFactory.create()) .build();
注:addConverterFactory是有先后顺序的,如果有多个ConverterFactory都支持同一种类型,那么就是只有第一个才会被使用,而GsonConverterFactory是不判断是否支持的,所以这里交换了顺序还会有一个异常抛出,原因是类型不匹配。
只要返回值类型的泛型参数就会由我们的StringConverter处理,不管是Call还是Observable
自定义CallAdapter
先看一下CallAdapter接口定义及各方法的作用:
public interface CallAdapter<T> { // 直正数据的类型 如Call<T> 中的 T // 这个 T 会作为Converter.Factory.responseBodyConverter 的第一个参数 // 可以参照上面的自定义Converter Type responseType(); <R> T adapt(Call<R> call); // 用于向Retrofit提供CallAdapter的工厂类 abstract class Factory { // 在这个方法中判断是否是我们支持的类型,returnType 即Call<Requestbody>和`Observable<Requestbody>` // RxJavaCallAdapterFactory 就是判断returnType是不是Observable<?> 类型 // 不支持时返回null public abstract CallAdapter<?> get(Type returnType, Annotation[] annotations, Retrofit retrofit); // 用于获取泛型的参数 如 Call<Requestbody> 中 Requestbody protected static Type getParameterUpperBound(int index, ParameterizedType type) { return Utils.getParameterUpperBound(index, type); } // 用于获取泛型的原始类型 如 Call<Requestbody> 中的 Call // 上面的get方法需要使用该方法。 protected static Class<?> getRawType(Type type) { return Utils.getRawType(type); } }}
了解了CallAdapter的结构和其作用之后,我们就可以开始自定义我们的CallAdapter了,本节以CustomCall为例。
在此我们需要定义一个CustomCall,不过这里的CustomCall作为演示只是对Call的一个包装,并没有实际的用途。
public static class CustomCall<R> { public final Call<R> call; public CustomCall(Call<R> call) { this.call = call; } public R get() throws IOException { return call.execute().body(); }}
有了CustomCall,我们还需要一个CustomCallAdapter来实现 Call 到 CustomCall的转换,这里需要注意的是最后的泛型,是我们要返回的类型。
public static class CustomCallAdapter implements CallAdapter<CustomCall<?>> { private final Type responseType; // 下面的 responseType 方法需要数据的类型 CustomCallAdapter(Type responseType) { this.responseType = responseType; } @Override public Type responseType() { return responseType; } @Override public <R> CustomCall<R> adapt(Call<R> call) { // 由 CustomCall 决定如何使用 return new CustomCall<>(call); }}
提供一个CustomCallAdapterFactory用于向Retrofit提供CustomCallAdapter:
public static class CustomCallAdapterFactory extends CallAdapter.Factory { public static final CustomCallAdapterFactory INSTANCE = new CustomCallAdapterFactory(); @Override public CallAdapter<?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) { // 获取原始类型 Class<?> rawType = getRawType(returnType); // 返回值必须是CustomCall并且带有泛型 if (rawType == CustomCall.class && returnType instanceof ParameterizedType) { Type callReturnType = getParameterUpperBound(0, (ParameterizedType) returnType); return new CustomCallAdapter(callReturnType); } return null; }}
使用addCallAdapterFactory向Retrofit注册CustomCallAdapterFactory
Retrofit retrofit = new Retrofit.Builder() .baseUrl("http://localhost:4567/") .addConverterFactory(Example09.StringConverterFactory.create()) .addConverterFactory(GsonConverterFactory.create()) .addCallAdapterFactory(CustomCallAdapterFactory.INSTANCE) .build();
注: addCallAdapterFactory与addConverterFactory同理,也有先后顺序。
- 学习Retrofit笔记
- Retrofit学习笔记
- Retrofit学习笔记
- Retrofit学习笔记
- Retrofit学习笔记
- Retrofit---学习笔记
- Retrofit学习笔记
- Retrofit学习笔记
- Retrofit学习笔记
- rx+retrofit 学习笔记
- Retrofit 2.0 学习笔记
- Retrofit源码学习笔记(1)一Retrofit
- dagger2 + RxJava +Retrofit 学习笔记
- Retrofit+RxJava+MVP学习笔记
- Android框架Retrofit 2.0学习笔记
- Android框架学习笔记03Retrofit框架
- Retrofit 学习笔记和简单封装
- Retrofit+RxJava学习笔记(1)
- MySQL设置事物隔离级别
- SAP创建生产订单时要求输入销售订单
- 开发中的问题——tomcat设置80端口问题(无详细代码)
- 《ACM程序设计》书中题目K-11
- Docker容器中部署静态网页
- Retrofit学习笔记
- 收藏一下题目分类
- 最富维生素ab ......good
- jQuery常用的元素查找方法总结
- final关键字
- Jquery 自定义 插件
- java中利用spring cache解耦业务中的缓存
- VMware虚拟机安装Ubuntu使用share folders共享windows目录的方法
- 支付接口