Android知识总结
一、简介
1、Retrofit是什么?
准确来说,Retrofit 是一个 RESTful 的 HTTP 网络请求框架的封装。
原因:网络请求的工作本质上是 OkHttp 完成,而 Retrofit 仅负责 网络请求接口的封装
我们先来看看下面这个图:
上图说明了如下几点:
- App应用程序通过 Retrofit 请求网络,实际上是使用 Retrofit 接口层封装请求参数、Header、Url 等信息,之后由 OkHttp 完成后续的请求操作。
- 在服务端返回数据之后,OkHttp 将原始的结果交给 Retrofit,Retrofit根据用户的需求对结果进行解析。
所以,网络请求的本质仍旧是OkHttp完成的,retrofit只是帮使用者来进行工作简化的,比如配置网络,处理数据等工作,提高这一系列操作的复用性。这也就是网上流行的一个不太准确的总结:okhttp是瑞士军刀,retrofit则是讲这边瑞士军刀包装从了一个非常好用的指甲钳。
2、Retrofit用到的 8 大设计模式
1)Retrofit 实例使用
建造者模式
通过Builder类构建。
当构造函数的参数大于4个,
且存在可选参数的时候既可以使用 建造者设计模式2)Retrofit 创建时的callFactory,使用
工厂方法设计模式
,但是似乎并不打算支持其他的工厂。3)整个retrofit 采用的时
外观模式
。统一的调用创建网络请求接口实例和网络请求参数配置的方法。4)retrofit里面使用了
动态代理
来创建网络请求接口实例,这个是retrofit对用户使用来说最大的复用,其它的代码都是为了支撑这个动态代理给用户带来便捷性的。5)使用了
策略模式
对serviceMethod对象进行网络请求参数配置,即通过解析网络请求接口方法的参数、返回值和注解类型,从Retrofit对象中获取对应的网络的url地址、网络请求执行器、网络请求适配器和数据转换器。6)ExecuteCallBack 使用
装饰者模式
来封装callbackExecutor,用于完成线程的切换。7)ExecutorCallbackCall 使用
静态代理(委托) 代理
了Call进行网络请求,真正的网络请求由okhttpCall执行,然而okHttpCall不是自己执行,它是okhttp 提供call给 外界(retrofit)使用的唯一门户,其实这个地方就是门面模式
。8)ExecutorCallbackCall 的被初始化是在ExecutorCallAdapterFactory里面通过
适配器模式
被创建的。
CallAdapter采用了适配器模式
为创建访问Call接口提供服务。默认不添加Rxjava则使用默认的ExecutorCallAdapterFactory 将okhttp3.call转变成为 retroift中的call,如果有Rxjava则将okhttp3.call转化为abservable。
3、依赖库
implementation 'com.squareup.retrofit2:retrofit:2.3.0'
implementation 'com.squareup.retrofit2:adapter-rxjava2:2.3.0'
implementation 'com.squareup.retrofit2:converter-gson:2.3.0'
4、注解类型
二、请求方法类
2.1、序号 1 ~ 7
- 分别对应 HTTP 的请求方法;
- 接收一个字符串表示接口 path ,与 baseUrl 组成完整的 Url;
- 可以不指定,结合 @Url 注解使用;
- url 中可以使用变量,如 {id} ,并使用 @Path(“id”) 注解为 {id} 提供值
@GET("project/tree/json")
Call getProject1();
//简单的get请求(URL中带有两个参数)
@GET("news/{userId}")
Call getItem(@Path("userId") String userId,@Path("type") String type);
2.2、序号 8
- 可用于替代以上 7 个,及其他扩展方法;
- 有 3 个属性:method、path、hasBody、 举个例子
@HTTP(method = "get", path = "project/tree/json",hasBody = false)
Call getProject2();
三、标记类
3.1、FormUrlEncoded
登录页面使用: Content-Type:application/x-www-form-urlencoded
- 用于修饰
Field
注解和FieldMap
注解 - 使用该注解,表示请求正文将使用表单网址编码。字段应该声明为参数,并用
@Field
注释或FieldMap
注释。使用FormUrlEncoded
注解的请求将具application/ x-www-form-urlencoded
MIME类型。字段名称和值将先进行UTF-8进行编码,再根据RFC-3986进行URI编码.
//普通参数
@FormUrlEncoded
@POST("/")
Call example(@Field("name") String name,@Field("occupation") String occupation);
//固定或可变数组
@FormUrlEncoded
@POST("/list")
Call example(@Field("name") String... names);
3.1、Multipart
上传文件使用: Content-Type:multipart/form-data
使用该注解,表示请求体是多部分的,每个部分作为一个参数,且用Part
和PartMap
注解声明。
- 上传单张图片
///////上传单张图片//////
/**
* Multipart:表示请求实体是一个支持文件上传的Form表单,需要配合使用@Part,适用于
有文件 上传的场景
* Part:用于表单字段,Part和PartMap与Multipart注解结合使用,适合文件上传的情况
* PartMap:用于表单字段,默认接受的类型是Map,可用于实现多
文件上传
* Part 后面支持三种类型,{@link RequestBody}、{@link
okhttp3.MultipartBody.Part} 、任意类型;
*
* @param file 服务器指定的上传图片的key值
* @return
*/
@Multipart
@POST("project/upload")
Call upload1(@Part("file" + "\";filename=\"" + "test.png") RequestBody file);
@Multipart
@POST("project/xxx")
Call upload2(@Part MultipartBody.Part file);
////////请求///////////
//上传单个图片1
File file = new File("");
RequestBody requestBody = RequestBody.create(MediaType.parse("image/png"), file);
wanAndroidApi.upload1(requestBody).execute();
//上传单个图片2
MultipartBody.Part imagePart = MultipartBody.Part.createFormData("上传的key", file.getName(), requestBody);
wanAndroidApi.upload2(imagePart)
.enqueue(new Callback() {
@Override
public void onResponse(Call call, Response response) { }
@Override
public void onFailure(Call call, Throwablet) { }
});
- 上传多张图片
///////上传多张图片//////
@Multipart
@POST("project/upload")
Call upload3(@PartMap Map map);
@Multipart
@POST("project/xxx")
Call upload4(@PartMap Map map);
////////使用//////////
//上传多张图片1
//图片集合
List files = new ArrayList<>();
Map 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();
//上传多张图片2
Map 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();
- 图文混传
//////图文混传/////
/**
* @param params
* @param files
* @return
*/
@Multipart
@POST("upload/upload")
Call upload5(@FieldMap() Map params, @PartMap() Map files);
/**
* Part 后面支持三种类型,{@link RequestBody}、{@link
okhttp3.MultipartBody.Part} 、任意类型;
*
* @param userName
* @param passWord
* @param file
* @return
*/
@Multipart
@POST("project/xxx")
Call 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() {
@Override
public void onResponse(Call call, Response response) {
}
@Override
public void onFailure(Call call, Throwable t) {
}
});
3.1、Streaming
未使用@Straming 注解,默认会把数据全部载入内存,之后通过流获取数据也是读取内存中数据,所以返回数据较大时,需要使用该注解。
处理返回Response的方法的响应体,用于下载大文件。
/**
* Streaming注解:表示响应体的数据用流的方式返回,
* 适用于返回的数据比较大,该注解在在下载大文件的特别有用
*/
@Streaming
@GET
Call downloadFile(@Url String fileUrl);
四、参数注解
注意:
- 1、Map用来组合复杂的参数,并且对于FieldMap,HeaderMap,PartMap,QueryMap这四种作用方法的注解,其参数类型必须为Map实例,且key的类型必须为String类型,否则抛出异常。
- 2、Query、QueryMap与Field、FieldMap功能一样,生成的数据形式一样;Query、QueryMap的数据体现在
Url
上;Field、FieldMap的数据是请求体
- 3、{占位符}和PATH尽量只用在URL的path部分,url的参数使用Query、QueryMap代替,保证接口的简洁
- 4、Query、Field、Part支持数据和实现了iterable接口的类型,如List、Set等,方便向后台传递数组,代码如下:
- 5、以上部分注解真正的实现在ParameterHandler类中,每个注解的真正实现都是ParameterHandler类中的一个final类型的内部类,每个内部类都对各个注解的使用要求做了限制,比如参数是否可空、键和值是否可空等。
- 6、@FormUrlEncoded 注解和@Multipart 注解不能同时使用,否则会抛出methodError(“Only one encoding annotation is allowed.”),可在ServiceMethod类中parseMethodAnnotation()方法中找到不能同时使用的具体原因。
- 7、@Path 与@Url 注解不能同时使用,否则会抛出parameterError(p, "@Path parameters may not be used with @Url."),可在ServcieMethod类中parseParameterAnnotation()方法中找到不能同时使用的具体代码。其实原因也是很好理解:Path注解用于替换url中的参数,这就要求在使用path注解时,必须已经存在请求路径。不然没法替换路径中指定的参数。而@Url 注解是在参数中指定了请求路径的,这时候情定请求路径已经晚,path注解找不到请求路径,更别提更换请求路径了中的参数了。
- 8 使用@Body 注解的参数不能使用form 或multi-part编码,即如果为方法使用了
FormUrlEncoded
或Multipart
注解,则方法的参数中不能使用@Body 注解,否则会抛出异常parameterError(p, “@Body parameters cannot be used with form or multi-part encoding.”)
4.1、Headers
使用 @Headers
注解设置固定的请求头,所有请求头不会相互覆盖,即使名字相同
@Headers("Cache-Control: max-age=640000")
@GET("project/list")
Call getMsg1();
@Headers({ "Accept: application/vnd.github.v3.full+json","User-Agent:
Retrofit-Sample-App"})
@GET("project/{username}")
Call getMsg2(@Path("username") String username);
4.2、Header
使用 @Header 注解动态更新请求头,匹配的参数必须提供给 @Header ,若参数值为 null ,这个头会被省略,否则,会使用参数值的 toString 方法的返回值。
具有相同名称的请求头不会相互覆盖,而是照样添加到请求头中。
@GET("project")
Call getProject3(@Header("Authorization") String authorization);
4.3、Body
使用 @Body 注解,指定一个对象作为 request body,使用@Body 注解定义的参数不能为null。
当你发送一个post或put请求,但是又不想作为请求参数或表单的方式发送请求时,使用该注解定义的参数可以直接传入一个实体类,retrofit会通过convert把该实体序列化并将序列化的结果直接作为请求体发送出去。
public class ProjectBean {
private String owner;
private String name;
ProjectBean (String owner, String name) {
this.owner = owner;
this.name = name;
}
}
@POST("project/new")
Call createProject(@Body ProjectBean user);
4.4、Field
- 作用于方法的参数
- 用于发送一个表单请求
- 用String.valueOf()把参数值转换为String,然后进行URL编码,当参数值为null值时,会自动忽略,如果传入的是一个List或array,则为每一个非空的item拼接一个键值对,每一个键值对中的键是相同的,值就是非空item的值,如:name=张三&name=李四&name=王五,另外,如果item的值有空格,在拼接时会自动忽略,例如某个item的值为:张三,则拼接后为name=张三。
//固定或可变数组
@FormUrlEncoded
@POST("/list")
Call example(@Field("name") String... names);
4.5、FieldMap
- 作用于方法的参数
- 用于发送一个表单请求
- map中每一项的键和值都不能为空,否则抛出IllegalArgumentException异常
FormUrlEncoded
@POST("/examples")
Call example(@FieldMap Map fields);
4.6、Part
- 作用于方法的参数,用于定义Multipart请求的每个part
- 使用该注解定义的参数,参数值可以为空,为空时,则忽略
- 使用该注解定义的参数类型有以下3种方式可选:
1、如果类型是okhttp3.MultipartBody.Part,内容将被直接使用。省略part中的名称,即 @Part MultipartBody.Part part
2、如果类型是RequestBody,那么该值将直接与其内容类型一起使用。在注释中提供part名称(例如,@Part(“foo”)RequestBody foo)
3、其他对象类型将通过使用转换器转换为适当的格式。 在注释中提供part名称(例如,@Part(“foo”)Image photo
@Multipart
@POST("/")
Call example(
@Part("description") String description,
@Part(value = "image", encoding = "8-bit") RequestBody image);
//上传一张图片
@Multipart
@POST("you methd url upload/")
Call uploadFile(@Part("avatar\\\\"; filename=\\\\"avatar.jpg") RequestBody file);
//上传图片数量不定
@Multipart
@POST("{url}")
Observable uploadFiles(
@Path("url") String url,
@Part("filename") String description,
@PartMap() Map maps);
//上传图片数量不定方法2
@Multipart
@POST("{url}")
Observable uploads(
@Path("url") String url,
@Part("description") RequestBody description,
@Part("filekey") MultipartBody.Part file);
4.7、PartMap
- 作用于方法的参数,以map的方式定义Multipart请求的每个part
- map中每一项的键和值都不能为空,否则抛出IllegalArgumentException异常
- 使用该注解定义的参数类型有以下2种方式可选:
1、如果类型是RequestBody,那么该值将直接与其内容类型一起使用
2、其他对象类型将通过使用转换器转换为适当的格式
@Multipart
@POST("upload/upload")
Call upload5(@FieldMap() Map params,
@PartMap() Map files,
@Part("file") RequestBody file,
@PartMap Map maps)
4.8、HeaderMap
- 作用于方法的参数,用于添加请求头
- 以map的方式添加多个请求头,map中的key为请求头的名称,value为请求头的值,且value使用 String.valueOf() 统一转换为String类型,
- map中每一项的键和值都不能为空,否则抛出IllegalArgumentException异常
@GET("/example1")
Call example1(@HeaderMap Map headers);
/////使用///////
Map headers = new HashMap<>();
headers.put("Accept","text/plain");
headers.put("Accept-Charset", "utf-8");
wanAndroidApi.example1(headers)
.enqueue(new Callback() {
@Override
public void onResponse(Call call, Response response) { }
@Override
public void onFailure(Call call, Throwablet) { }
});
4.9、Path
请求 URL 可以替换模块来动态改变,替换模块是 {}包含的字母数字字符串,替换的参数必须使用 @Path 注解的相同字符串
@GET("example5/{id}")
Call example5(@Path("id") int id);
4.10、Query
- 作用于方法的参数
- 用于添加查询参数,即请求参数
- 参数值通过String.valueOf()转换为String并进行URL编码
- 使用该注解定义的参数,参数值可以为空,为空时,忽略该值,当传入一个List或array时,为每个非空item拼接请求键值对,所有的键是统一的,如:name=张三&name=李四&name=王五
@GET("/list")
Call list(@Query("page") int page);
@GET("/list")
Call list(@Query("category") String category);
//传入一个数组
@GET("/list")
Call list(@Query("category") String... categories);
//不进行URL编码
@GET("/search")
Call list(@Query(value="foo", encoded=true) String foo);
4.11、QueryMap
复杂的查询参数
//使用默认URL编码
@GET("/search")
Call list(@QueryMap Map filters);
//不使用默认URL编码
@GET("/search")
Call list(@QueryMap(encoded=true) Map filters);
4.12、Url
- 作用于方法参数
- 用于添加请求的接口地址
@GET
Call example4(@Url String url);
五、Retrfit文件上传
1.创建描述网络请求的接口
public interface FileUploadService {
@Multipart //表示发送form-encoded的数据(适用于 有文件 上传的场景)
@POST("upload")
Call upload(@Part("description") RequestBody description,
@Part MultipartBody.Part file);
}
2.具体使用
// 创建 RequestBody,用于封装 请求RequestBody
RequestBody requestFile =
RequestBody.create(MediaType.parse("multipart/form-data"), file);
// MultipartBody.Part 和后端约定好Key,这里的partName是用image
MultipartBody.Part body =
MultipartBody.Part.createFormData("image", file.getName(), requestFile);
// 添加描述
String descriptionString = "hello, 这是文件描述";
RequestBody description =
RequestBody.create(
MediaType.parse("multipart/form-data"), descriptionString);
// 执行请求
Call call = service.upload(description, body);
call.enqueue(new Callback() {
@Override
public void onResponse(Call call,
Response response) {
Log.v("Upload", "success");
}
@Override
public void onFailure(Call call, Throwable t) {
Log.e("Upload error:", t.getMessage());
}
});
/**
* 文字参数
* @param value
* @return
*/
public static RequestBody toRequestBodyOfText (String value) {
RequestBody body = RequestBody.create(MediaType.parse("text/plain"), value);
return body ;
}
/**
* 图片文件参数
* @param pFile
* @return
*/
public static RequestBody toRequestBodyOfImage(File pFile){
RequestBody fileBody = RequestBody.create(MediaType.parse("multipart/form-data"), pFile);
return fileBody;
}
/**
* 加工图片参数
* @param params
* @param file 图片路径
* @param key 和后台约定
* @return
*/
public static HashMap toPutImgFileParams(HashMap params,String key,File file){
if (params == null) {
throw new RuntimeException("params is not allow null");
}
if (key == null) {
throw new RuntimeException("file key is not allow null");
}
String fileName = "yyt_image"+ (Math.random()*9+1)*10000 +".png";
params.put(""+key+"\"; filename=\""+fileName+"",toRequestBodyOfImage(file));
return params;
}