1 retrofit基础资料 1.1 Retrofit是什么 官网介绍是A type-safe HTTP client for Android and Java,是一个 RESTful 的 HTTP 网络请求框架的封装,但网络请求不是Retrofit来完成的,它只是封装了请求参数、Header、Url、返回结果处理等信 息,而请求是由OkHttp3来完成的。
1.2 使用 1.2.1 导包 1 2 3 4 5 6 7 8 implementation "com.squareup.retrofit2:retrofit:$rootProject.retrofitVersion" implementation "com.squareup.retrofit2:retrofit-mock:$rootProject.retrofitVersion" implementation "com.squareup.retrofit2:converter-gson:$rootProject.retrofitVersion" implementation 'com.squareup.okhttp3:logging-interceptor:3.5.0' implementation "com.squareup.retrofit2:converter-scalars:$rootProject.retrofitVersion" implementation "com.squareup.retrofit2:adapter-rxjava2:$rootProject.retrofitVersion" implementation "com.squareup.retrofit2:converter-gson:$rootProject.retrofitVersion"
1.2.2 定义一个HTTP API接口类 1 2 3 4 interface WanAndroidApi { @GET("project/tree/json") Call<ProjectBean> getProject () ; }
1.2.3 使用Retrofit类生成WanAndroidApi 接口实现 1 2 3 4 5 Retrofit retrofit = new Retrofit.Builder() .baseUrl("https://www.wanandroid.com/" ) .addConverterFactory(GsonConverterFactory.create()) .build(); WanAndroidApi wanAndroidApi = retrofit.create(WanAndroidApi.class);
1.2.4 发送HTTP请求,返回Response可以同步或者异步处理 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 Call<ProjectBean> call = wanAndroidApi.getProject(); Response<ProjectBean> response = call.execute(); ProjectBean projectBean = response.body(); call.enqueue(new Callback<ProjectBean>() { @Override public void onResponse (final Call<ProjectBean> call, final Response<ProjectBean> response) { Log.i("Zero" ,"response: " + response.body()); } @Override public void onFailure (final Call<ProjectBean> call, final Throwable t) {} });
1.3 注解分类解析 1.3.1 请求方法类
序号
名称
说明
1
GET
get请求
2
POST
post请求
3
PUT
put请求
4
DELETE
delete请求
5
PATCH
patch请求,该请求是对put请求的补充,用于更新局部资源
6
HEAD
head请求
7
OPTIONS
option请求
8
HTTP
通用注解,可以替换以上的所有注解,其拥有method,path,hasBody三个属性
序号1 ~ 7
分别对应 HTTP 的请求方法;
接收一个字符串表示接口 path ,与 baseUrl 组成完整的 Url;
可以不指定,结合 @Url 注解使用;
url 中可以使用变量,如 {id} ,并使用 @Path(“id”) 注解为 {id} 提供值。
1 2 @GET("project/tree/json") Call<ProjectBean> getProject1 () ;
序号8
可用于替代以上 7 个,及其他扩展方法;
有 3 个属性:method、path、hasBody、 举个例子
1 2 @HTTP(method = "get", path = "project/tree/json",hasBody = false) Call<ProjectBean> getProject2 () ;
1.3.2 标记类
分类
名称
备注
表单请求
FormUrlEncoded
表示请求实体是一个Form表单,每个键值对需要使用@Field注解
请求参数
Multipart
表示请求实体是一个支持文件上传的Form表单,需要配合使用@Part,适用于有文件上传的场景
标记
Streaming
表示响应体的数据用流的方式返回,适用于返回的数据比较大,该注解在在下载大文件的特别有用
登录页面使用: Content-Type:application/x-www-form-urlencoded 用于修饰Field注解和FieldMap注解,使用该注解,表示请求正文将使用表单网址编码。字段应该声明为参数,并用@Field注释或FieldMap注释。使用FormUrlEncoded注解的请求将具”application/ x-www-form-urlencoded” 。MIME类型。字段名称和值将先进行UTF-8进行编码,再根据RFC-3986进行URI编码.
2 Multipart 上传文件使用: Content-Type:multipart/form-data
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 @Multipart @POST("project/upload") Call<ProjectBean> upload1 (@Part("file" + "\";filename=\"" + "test.png") RequestBody file) ;@Multipart @POST("project/xxx") Call<ProjectBean> upload2 (@Part MultipartBody.Part file) ; File file = new File("" ); RequestBody requestBody = RequestBody.create(MediaType.parse("image/png" ),file); wanAndroidApi.upload1(requestBody).execute(); MultipartBody.Part imagePart = MultipartBody.Part.createFormData("上传的 key" ,file.getName(),requestBody); wanAndroidApi.upload2(imagePart).enqueue(new Callback<ProjectBean>() { @Override public void onResponse (Call<ProjectBean> call, Response<ProjectBean> response) { } @Override public void onFailure (Call<ProjectBean> call, Throwable t) { } }); @Multipart @POST("project/upload") Call<ProjectBean> upload3 (@PartMap Map<String, RequestBody> map) ; @Multipart @POST("project/xxx") Call<ProjectBean> upload4 (@PartMap Map<String, MultipartBody.Part> map) ; List<File> files = new ArrayList<>(); Map<String, RequestBody> map = new HashMap<>(); for (int i = 0 ; i < files.size(); i++) { RequestBody requestBody = RequestBody.create(MediaType.parse("image/png" ), files.get(i)); map.put("file" + i + "\";filename=\"" + files.get(i).getName(), requestBody); } wanAndroidApi.upload3(map).execute(); Map<String, MultipartBody.Part> map1 = new HashMap<>(); File file1 = new File("" ); RequestBody requestBody1 = RequestBody.create(MediaType.parse("image/png" ), file1); MultipartBody.Part part1 = MultipartBody.Part.createFormData("上传的key1" , file1.getName(), requestBody1); map1.put("上传的key1" , part1); File file2 = new File("" ); RequestBody requestBody2 = RequestBody.create(MediaType.parse("image/png" ), file2); MultipartBody.Part part2 = MultipartBody.Part.createFormData("上传的key2" , file2.getName(), requestBody2); map1.put("上传的key2" , part2); wanAndroidApi.upload4(map1).execute(); @Multipart @POST("upload/upload") Call<ProjectBean> upload5 (@FieldMap() Map<String, String> params, @PartMap() Map<String, RequestBody> files) ; @Multipart @POST("project/xxx") Call<ProjectBean> upload6 (@Part("username") RequestBody userName, @Part("password") RequestBody passWord, @Part MultipartBody.Part file) ;MediaType textType = MediaType.parse("text/plain" ); RequestBody name = RequestBody.create(textType, "zero" ); RequestBody password = RequestBody.create(textType, "123456" ); File file = new File("" ); RequestBody requestBody = RequestBody.create(MediaType.parse("image/png" ), file); MultipartBody.Part part = MultipartBody.Part.createFormData("上传的 key" , file.getName(), requestBody); wanAndroidApi.upload6(name, password, part).enqueue(new Callback<ProjectBean>() { @Override public void onResponse (Call<ProjectBean> call, Response<ProjectBean> response) { } @Override public void onFailure (Call<ProjectBean> call, Throwable t) { } });
3 Streaming 未使用该注解,默认会把数据全部载入内存,之后通过流获取数据也是读取内存中数据,所以返回数据较大时,需要使用该注解
1 2 3 4 5 6 @Streaming @GET Call<ProjectBean> downloadFile (@Url String fileUrl) ;
1.4 参数类
分类
名称
备注
作用于方法
Headers
用于添加固定请求头,可以同时添加多个。通过该注解添加的请求头不会相互覆盖,而是共同存在
作用于方法参数(形 参)
Header
作为方法的参数传入,用于添加不固定值的Header,该注解会更新已有的请求头
请求参数
Body
多用于post请求发送非表单数据,比如想要以post方式传递json格式数据
请求参数
Field
多用于post请求中表单字段,Filed和FieldMap需要FormUrlEncoded结合使用
请求参数
FieldMap
表单字段,与 Field、FormUrlEncoded 配合;接受 Map<String,String>类型,非 String 类型会调用 toString() 方法
请求参数
Part
用于表单字段,Part和PartMap与Multipart注解结合使用,适合文件上传的情况
请求参数
PartMap
表单字段,与 Part 配合,适合文件上传情况;默认接受 Map<String, RequestBody> 类型,非 RequestBody 会通过 Converter 转换
请求参数
HeaderMap
用于URL,添加请求头
请求参数
Path
用于url中的占位符
请求参数
Query
用于Get中指定参数
请求参数
QueryMap
和Query使用类似
请求参数
Url
指定请求路径
注意:
Map 用来组合复杂的参数;
Query、QueryMap 与 Field、FieldMap 功能一样,生成的数据形式一样; Query、QueryMap 的数据体现在 Url 上; Field、FieldMap 的数据是请求体;
{占位符}和 PATH 尽量只用在URL的 path 部分,url 中的参数使用 Query、QueryMap 代替,保 证接口的简洁;
Query、Field、Part 支持数组和实现了 Iterable 接口的类型, 如 List、Set等,方便向后台传递数组,示例如下:
使用 @Headers
注解设置固定的请求头,所有请求头不会相互覆盖,即使名字相同。
1 2 3 4 5 6 @Headers("Cache-Control: max-age=640000") @GET("project/list") Call<ProjectBean> getMsg1 () ;@Headers({ "Accept: application/vnd.github.v3.full+json","User-Agent: Retrofit-Sample-App"}) @GET("project/{username}") Call<ProjectBean> getMsg2 (@Path("username") String username) ;
使用@Header
注解动态更新请求头,匹配的参数必须提供给@Header
,若参数值为 null ,这个头会被忽略,否则,会使用参数值的 toString 方法的返回值。
1 2 3 @GET("project") Call<ProjectBean> getProject3 (@Header("Authorization") String authorization) ;
1.4.3 Body 使用@Body
注解,指定一个对象作为 request body 。
1 2 @POST("project/new") Call<ProjectBean> createProject (@Body ProjectBean user) ;
1.4.4 Field
作用于方法的参数
用于发送一个表单请求
用String.valueOf()把参数值转换为String,然后进行URL编码,当参数值为null值时,会自动忽略,如果 传入的是一个List或array,则为每一个非空的item拼接一个键值对,每一个键值对中的键是相同的,值 就是非空item的值,如:name=张三&name=李四&name=王五,另外,如果item的值有空格,在拼接时 会自动忽略,例如某个item的值为:张三,则拼接后为name=张三.
1 2 3 4 @FormUrlEncoded @POST("/list") Call<ResponseBody> example (@Field("name") String... names) ;
1.4.5 FieldMap
作用于方法的参数
用于发送一个表单请求
map中每一项的键和值都不能为空,否则抛出IllegalArgumentException异常
1 2 3 FormUrlEncoded @POST("/examples") Call<ResponseBody> example(@FieldMap Map<String, String> fields);
1.4.6 Part
作用于方法的参数,用于定义Multipart请求的每个part
使用该注解定义的参数,参数值可以为空,为空时,则忽略
使用该注解定义的参数类型有以下3种方式可选:
如果类型是okhttp3.MultipartBody.Part,内容将被直接使用。省略part中的名称,即 @Part MultipartBody.Part part
如果类型是RequestBody,那么该值将直接与其内容类型一起使用。在注释中提供part名称 (例如,@Part(“foo”)RequestBody foo)
其他对象类型将通过使用转换器转换为适当的格式。 在注释中提供part名称(例如, @Part(“foo”)Image photo)
1.4.7 PartMap
作用于方法的参数,以map的方式定义Multipart请求的每个part
map中每一项的键和值都不能为空,否则抛出IllegalArgumentException异常
使用该注解定义的参数类型有以下2种方式可选:
如果类型是RequestBody,那么该值将直接与其内容类型一起使用
其他对象类型将通过使用转换器转换为适当的格式
1 2 3 4 5 6 @Multipart @POST("upload/upload") Call<ProjectBean> upload5 (@FieldMap() Map<String, String> params, @PartMap() Map<String, RequestBody> files, @Part("file") RequestBody file, @PartMap Map<String,RequestBody> maps) ;
作用于方法的参数,用于添加请求头
以map的方式添加多个请求头,map中的key为请求头的名称,value为请求头的值,且value使用 String.valueOf()统一转换为String类型,
map中每一项的键和值都不能为空,否则抛出IllegalArgumentException异常
1 2 3 4 5 6 7 8 9 10 11 12 @GET("/example1") Call<ProjectBean> example1 (@HeaderMap Map<String, String> headers) ; Map<String,String> headers = new HashMap<>(); headers.put("Accept" ,"text/plain" ); headers.put("Accept-Charset" , "utf-8" ); wanAndroidApi.example1(headers).enqueue(new Callback<ProjectBean>() { @Override public void onResponse (Call<ProjectBean> call, Response<ProjectBean> response) { } @Override public void onFailure (Call<ProjectBean> call, Throwable t) { } });
1.4.9 Path 请求 URL 可以替换模块来动态改变,替换模块是 {}包含的字母数字字符串,替换的参数必须使用 @Path
注解的相同字符串
1 2 @GET("example5/{id}") Call<ResponseBody> example5 (@Path("id") int id) ;
1.4.10 Query
作用于方法的参数
用于添加查询参数,即请求参数
参数值通过String.valueOf()转换为String并进行URL编码
使用该注解定义的参数,参数值可以为空,为空时,忽略该值,当传入一个List或array时,为每个非空 item拼接请求键值对,所有的键是统一的,如:name=张三&name=李四&name=王五.
1 2 @GET("example2/{id}") Call<ResponseBody> example2 (@Path("id") int id) ;
1.4.11 QueryMap 复杂的查询参数
1 2 @GET("example3/{id}") Call<ResponseBody> example3 (@Path("id") int id, @QueryMap Map<String, String> options) ;
1.4.12 Url
1 2 @GET Call<ResponseBody> example4 (@Url String url) ;
2 原理分析 2.1 关键类功能说明 2.1.1 Retrofit Retrofit提供的子系统
serviceMethodCache(自定义的接口映射对象集合)
baseUrl(请求地址)
callFactory(默认为OKHttpCall)
converterFactories(数据解析器工厂集合)
callAdapterFactories(Call适配器工厂集合)
callbackExecutor(回调执行,Android平台默认为MainThreadExecutor)
使用Builder模型构建(把对象依赖的零件创建、零件的组装封装起来;以使客户很方便的获取一个复杂对象;)
Retrofit中用来管理多平台的方法,支持Android、Java8。通过findPlatform获取对应的平台,同时也初始化了defaultCallAdapterFactory工厂
2.1.3 ServiceMethod 接口映射的网络请求对象,通过动态代理,将自定义接口的标注转换为该对象,将标注及参数生成 OkHttp所需的Request对象。Retrofit的create通过动态代理拦截,将每一个自定义接口转换成为一个 ServiceMethod对象,并通过serviceMethodCache进行缓存。
2.1.4 Call Retrofit定义的网络请求接口,包含execute、enqueue等方法
2.1.5 OkHttpCall Ohttp的Call实现,通过createRawCall得到真正的 okhttp3.Call对象,用于进行实际的网络请求
2.1.6 CallAdapter.Factory CallAdapter的静态工厂,包含get的抽象方法,用于生产CallAdapter对象
2.1.7 ExecutorCallAdapterFactory Android平台默认的CallAdapter工厂,get方法使用匿名内部类实现CallAdapter,返回 ExecutorCallbackCall,实现了Call。
2.1.8 ExecutorCallbackCall 采用静态代理设计,delegate实际为OkHttpCall,使用callbackExecutor实现回调在主线程中执行
2.1.9 RxJavaCallAdapterFactory Rxjava平台的CallAdapter工厂,get方法返回RxJavaCallAdapter对象
2.1.10 RxJavaCallAdapter Rxjava平台的设配器,返回observable对象
2.1.11 Converter.Factory 数据解析器工厂,用于生产Converter实例
2.1.12 GsonConverterFactory 数据解析工厂实例,返回了GsonResponseBodyConverter数据解析器
2.1.13 GsonResponseBodyConverter Gson的数据解析器,将服务端返回的json对象转换成对应的java模型
2.1.14 Response Retrofit网络请求响应的Response
2.2 关键的几个流程
Retrofit创建和配置过程
Retrofit Adapter适配方案
Retrofit Converter转化原理
Retrofit使用到的设计模式分析
2.2.1 Retrofit创建和配置过程 1 2 3 4 5 6 7 8 9 Retrofit retrofit = new Retrofit.Builder() .baseUrl("https://www.baidu.com/" ) .addConverterFactory(GsonConverterFactory.create()) .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .build(); ISharedListService sharedListService = retrofit.create(ISharedListService.class); sharedListService.getSharedList(2 ,1 );
1: 成功建立一个Retrofit对象的标准:配置好Retrofit类里的成员变量
baseUrl:网络请求的url地址
callFactory:网络请求工厂(new OkHttpClient())
callbackExecutor:回调方法执行器,网络线程切换。
adapterFactories:网络请求适配器工厂的集合
converterFactories:数据转换器工厂的集合
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 public Retrofit build () { if (baseUrl == null ) { throw new IllegalStateException("Base URL required." ); } okhttp3.Call.Factory callFactory = this .callFactory; if (callFactory == null ) { callFactory = new OkHttpClient(); } Executor callbackExecutor = this .callbackExecutor; if (callbackExecutor == null ) { callbackExecutor = platform.defaultCallbackExecutor(); } List<CallAdapter.Factory> callAdapterFactories = new ArrayList<>(this .callAdapterFactories); callAdapterFactories.add(platform.defaultCallAdapterFactory(callbackExecutor)); List<Converter.Factory> converterFactories = new ArrayList<>(1 + this .converterFactories.size()); converterFactories.add(new BuiltInConverters()); converterFactories.addAll(this .converterFactories); return new Retrofit(callFactory, baseUrl, unmodifiableList(converterFactories), unmodifiableList(callAdapterFactories), callbackExecutor, validateEagerly); } }
(1)CallFactory默认直接创建一个OkHttpClient。
(2)CallbackExecutor主要完成网络请求回来,线程切换工作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 static class Android extends Platform { @Override public Executor defaultCallbackExecutor () { return new MainThreadExecutor(); } @Override CallAdapter.Factory defaultCallAdapterFactory (@Nullable Executor callbackExecutor) { if (callbackExecutor == null ) throw new AssertionError(); return new ExecutorCallAdapterFactory(callbackExecutor); } static class MainThreadExecutor implements Executor { private final Handler handler = new Handler(Looper.getMainLooper()); @Override public void execute (Runnable r) { handler.post(r); } } }
2: 创建了一个ISharedListService 接口类的对象,create函数内部使用了动态代理来创建接口对象,这样的设计可以让所有的访问请求都被代理。
Call sharedListCall = sharedListService.getSharedList(2,1);调用getSharedList的时候,在动态代理里面,会存在一个函数 getSharedList,这个函数里面会调用 invoke,这个invoke函数也就是retrofit里面 invoke函数。所以,动态代理可以代理所有的接口,让所有的接口都走 invoke函数,这样就可以拦截调用函数的执行,从而将网络接口的参数配置归一化。
建造者设计模式:有5个配置以上,且有部分参数可选情况下使用。
门面模式/外观模式:所有与用户交互的接口都是用retrofit来实现。