参考文章:
https://blog.youkuaiyun.com/m0_37796683/article/details/90702095
欢迎关注我的个人公众号
文章目录
一、前言
最近着实有点烦躁,疫情的影响,整个生活和工作都感觉到压抑。今天打算写一下Retrofit网络请求框架,之后的几篇打算把所有的网络框架都写一下,算是对网络请求框架的一个总结。
1、Retrofit是什么
Retrofit是一个RESTful的HTTP网络请求框架的封装,Retrofit的本质是通过注解的形式封装请求参数、Header、URL、返回值等信息。底层的请求工作时交给OkHttp完成的,Retrofit是本质上是对网络请求接口的封装。但是这种高度封装带来的后果就是扩展性差,解析的数据都是统一的converter,如果服务器不能给出统一的API形式,那么很难处理。但是这种缺点我们可以通过自定义converter来处理。
二、Retrofit注解类型
我们前面说过Retrofit是通过注解的方式对接口的统一封装,然后交给okhttp来完成网络请求。
Retrofit对网络请求接口的定义如下:
public interface APIInterface {
//获取无重复ID
@GET("tools/no_repeat_id/long")
Call<ResponseBody> getLongId();
}
这里APIInterface是所有网络请求的封装的Interface,getLongId为我们定义的请求方法名,注解@GET为get请求后面的参数代表请求的部分url,返回值类型为Call泛型为ResonseBody;
Retrofit的注解类型共有三种:
- 网络请求方法注解;
- 标记类注解;
- 网络请求参数注解
1、网络请求方法注解
有的博客还将@HEADER和@HEADERS单独划分出来作为请求头注解也是可以的。
注解名称 | 说明 |
---|---|
@GET | 采用Get方法发送网络请求 |
@POST | 采用Post方法发送网络请求 |
@PUT | 采用Put方法发送网络请求 |
@DELETE | 采用Delete方法发送网络请求 |
@PATCH | 该请求是对put请求的补充,用于更新局部资源 |
@HEAD | head请求 |
@OPTIONS | options请求 |
@HTTP | 通过注解,可以替换以上所有的注解,它拥有三个属性:method、path、hasBody |
2、标记类注解
注解名称 | 说明 |
---|---|
@FromUrlEncoded | 发送编码表单数据请求,每个键值对需要使用@Filed注解 |
@Multipart | 表示发送form-encoded的数据(适用于有文件上传的场景) |
@Streaming | 表示响应用字节流的形式返回,如果没有使用注解,默认会把数据全部载入到内存中,之后获取数据也是从内存中读取。 |
3、网络请求参数注解
注解名称 | 说明 |
---|---|
@Headers | 添加固定的请求头,作用于方法 |
@Header | 添加不固定的请求头,作用于方法的参数 |
@Body | 非表单请求体如:json |
@Filed、@FiledMap | Post表单请求字段,需要配合@FromUrlEncoded使用。体现在请求体上 |
@Part、@PartMap | 文件上传,表单字段 |
@Query、@QueryMap | Get的请求参数字段,体现在URL上。 |
@Path | 用于URL中的占位符 |
@Url | 设置请求路径 |
以上的请求注解我们暂时仅做了解一下,接下来结合具体的例子能帮助我们理解。
三、Retrofit的使用方法
1、添加依赖
implementation ‘com.squareup.retrofit2:retrofit:2.7.2’
2、添加网络权限
3、创建用于描述网络接口的类
public interface APIInterface {
//获取无重复ID
@GET("tools/no_repeat_id/long")
Call<ResponseBody> getLongId();
}
4、创建Retrofit的实例
mRetrofit=new Retrofit.Builder()
.baseUrl(Configure.URL_OPEN)
//支持自定义数据解析
.addConverterFactory(GsonConverterFactory.create())
.build();
说明:
- Retrofit的实例通过new Retrofit.Builder()创建,设置baseUrl,接口地址包含两个部分,一部分就是baseUrl,另一部分是APIInterface中定义的@GET注解接口方法名tools/no_repeat_id/long;这两部分就是一个完整的请求路径。
- 添加数据解析器Gson,如果不添加默认接受的是字符串类型。
- 注意baseUrl的地址必须以“/”结尾,否则会抛异常
设置Gson数据解析需要配置以下依赖:
implementation 'com.squareup.retrofit2:converter-gson:2.8.1'
5、创建网络接口请求示例
Call<ResponseBody>callGetID=mRetrofit.create(APIInterface.class)
.getLongId();
6、完成网络请求(同步、异步)
6.1、发送同步请求:
new Thread(new Runnable() {
@Override
public void run() {
try {
Response<ResponseBody>mResponse=callGetID.execute();
Log.d(TAG,mResponse.body().string());
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
同步请求调用execute()方法;
6.2、发送异步请求:
callGetID.getLongId().enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
try {
Log.d(TAG,response.body().string());
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
Log.d(TAG,t.toString());
}
});
异步请求执行enqueue方法。
我们简单的介绍了如何使用@Get无参注解进行同步和异步的请求,前面我们也介绍了Retrofit中的注解类型,接下来我们具体看一下请求注解的使用。
7、请求注解的使用
7.1、Get请求注解的使用
get请求的无参请求,上面的例子我们已经给出了
7.1.1、无参请求
public interface APIInterface {
//获取无重复ID
@GET("tools/no_repeat_id/long")
Call<ResponseBody> getLongId();
}
说明:
- @GET(),get请求方法注解,括号内代表url中的路径和请求方法,注意不能以“/”结尾;否则会报错,这一点要与baseUrl区分;
- getLongId()是我们定义的请求方法,返回值类型为Call,入参为无参。
7.1.2、有参请求
Get有参请求注解中,分为参数个数固定的和参数不固定的,我们统一来看
@GET("jokes/list")
Call<ResponseBody> getJokes(@Query("page") String pageNum);
说明:
@Query是get请求中的请求参数注解,括号内的page就是我们的请求参数,它会拼接到@Get请求url的后面(拼接形式:baseurl+jokes/list?page=1),String 代表请求参数类型,pageNum请求参数的数值。
@GET("music/order/song/list")
Call<ResponseBody> getNewsList(@QueryMap Map<String,Object> map);
说明:@QueryMap是get请求参数不固定的注解,相当于多个@Query,请求方法接收一个Map,这个Map是用来对请求参数的封装。
7.2、 Post请求注解的使用
7.2.1、Post请求无参注解的使用
post请求无参注解和get请求无参注解类似,只不过将@GET注解改成@POST注解,示例代码如下:
//获取无重复ID
@Post("tools/no_repeat_id/long")
Call<ResponseBody> getLongId();
7.2.2、Post请求有参注解的使用(表单数据)
@FormUrlEncoded
@POST("user/login")
Call<ResponseBody> postLogin(@Field("username") String name,@Field("password") String pwd);
有参的post请求,针对form表单请求需要添加注解@FormUrlEncoded,表示表单请求的键值对进行了url编码,此外每个键的字段需要添加@Field注解。
这个是针对参数固定请求的写法,如果参数不固定,采用下面的写法。
@FormUrlEncoded
@POST("user/login")
Call<ResponseBody> postLogin(@FieldMap Map<String,String> map);
@FieldMap与@Field的作用一致,可以用于添加多个不确定的参数,类似@QueryMap;
7.2.3、Post请求有参注解非标单数据的使用
对于非表单数据的请求,通过@Body注解传递自定义的数据类型给服务器,表单注解@Body不能用于表单或者支持文件上传的表单的编码,即不能与@FormUrlEncoded和@Multipart注解同时使用
@POST("user/login")
Call<ResponseBody> postRegisterAccountBody(@Body RequestBody body)
使用方法:
FormBody.Builder builder = new FormBody.Builder();
builder.addEncoded("username","xuchangqing");
builder.addEncoded("password","Xcq12345");
builder.addEncoded("repassword","Xcq12345");
builder.build()作为RequestBody中body参数的使用。
7.3、请求头注解的使用
有时我们需要在网络请求中加入请求头,Retrofit提供了两种添加请求头的方式@Header添加不固定的请求头,@Headers添加固定的请求头。
@GET("user/login")
Call<ResponseBody> getHeaderData(@Header("token") String token);
@Headers({"phone-type:android", "version:1.1.1"})
@GET("user/login")
Call<ResponseBody> getHeadersData();
说明:@Header作用于参数,@Headers作用于方法。
7.4、文件上传注解的使用
7.4.1、文件和参数混合上传注解的使用
@Multipart
@POST("user/picture")
Call<ResponseBody> getPartData(@Part("name") RequestBody name, @Part MultipartBody.Part file);
说明:@Multipart是一个支持文件上传的表单,但是需要配合@Part和@PartMap使用,@Part 支持三种类型的数据:一种是RequestBody类型,一种是MultipartBody.Part类型,另一种是任意数据类型;这里的网络接口是文件和请求参数混合上传的方式,第一个参数为请求参数,第二个参数为文件。
//上传文字
MediaType mediaType = MediaType.parse("text/plain");
RequestBody fileName = RequestBody.create(mediaType, "xuchangqing");
//上传图片
File file = new File("");
MediaType mediaTypeImg = MediaType.parse("image/png");
RequestBody mFile = RequestBody.create(mediaTypeImg, file);
MultipartBody.Part muiltPart = MultipartBody.Part.createFormData("file", file.getName(), mFile);
7.4.2、单文件上传注解的使用
@Multipart
@POST("user/picture")
Call<ResponseBody> getPartData @Part MultipartBody.Part file);
//上传图片
File file = new File("");
MediaType mediaTypeImg = MediaType.parse("image/png");
RequestBody mFile = RequestBody.create(mediaTypeImg, file);
MultipartBody.Part muiltPart = MultipartBody.Part.createFormData("file", file.getName(), mFile);
7.4.3、多文件上传注解的使用
@Multipart
@POST("user/followers")
Call<ResponseBody> getPartMapData(@PartMap Map<String, MultipartBody.Part> map);
注意:
在使用文件上传的过程中,如果我们采用这种写法
@Multipart
@POST("user/picture")
Call<ResponseBody> getPartData @Part("file") RequestBody file);
服务端识别不出这个是一个文件的,会把它当成一个参数;
另外MultipartBody.Part作为参数类型的话,@Part一定是无参的,否则会报错。
7.5、其他请求注解的使用
7.5.1、@Body注解的使用
前面我们说过@Body注解用于非表单形式的请求,如json,HashMap,自定义实体类等。它的用法:
@POST("user/login")
Call<UserBean> postUserLogin(@Body UserBean userBean);
我们传入的是一个实体类,当然我们要在retrofit中添加适配器GsonConverterFactory,这时UserBean会转成json发送到服务器中。注意:@Body注解不能用于表单或者支持文件上传的表单的编码,即不能与@FormUrlEncoded和@Multipart注解同时使用,否则会报错。
7.5.2、@HTTP注解的使用
@HTTP(method = "POST", path = "user/login", hasBody = false)
Call<ResponseBody> getHttpData();
@HTTP它拥有三个属性:method、path、hasBody,注意method的请求方法名要注意大小写,path代表请求路径,hasBody代表是否包含请求体。
@HTTP注解可以替代Get\Post\Delete请求;
7.5.3、@Path注解的使用
@Path注解用于Url中的占位符{},所有在网址中的参数,通过{}占位符来标记;
@GET("user/{id}")
Call<ResponseBody> getUserId(@Query("name")String userName, @Path("id")String id);
7.5.4、@Url注解的使用
@FormUrlEncoded
@POST
Call<ResponseBody> postRegisterAccountMap(@Url String url, @FieldMap Map<String, String> map);
注意如果有@Url注解时,@POST中的参数必须省略,否则报错。
7.5.5、@Streaming注解的使用
表示响应体的数据用流的方式返回,使用于返回数据比较大,该注解在下载大文件时特别有用
四、HTTPS请求方式
Https的请求为了保证数据在传输过程中更加的安全,采用SSL证书验证身份的一种请求方式。htpps认证分为单项认证和双项认证的方式;
4.1、单项认证
单项认证就是校验服务端是否具有合法性,这需要在客户端配置服务端的证书;客户端请求服务器的证书与客户端证书比对是否一致,如果不一致则认为是非法的服务端;
4.2、双项认证
双项认证主要是应用在安全等级要求比较高的领域比如银行金融等。双项认证不仅要求客户端校验服务端也同时要求服务端校验客户端。服务端校验客户端,需要客户端将证书传送给服务端进行校验。
4.3、注意
我们使用Https请求并没有配置SSL证书,为什么是成功的呢?这是因为我们Android系统中配置了CA的根证书或者是我们对Https请求没有进行证书校验,但是这么做假设不合法的服务器也配置了CA的证书,客户端也是能够校验通过的。对于客户端对https请求没有进行证书校验,有两种通用的做法:
- 创建一个不验证证书链的证书信任管理器
final TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() {
@Override
public void checkClientTrusted(
java.security.cert.X509Certificate[] chain,
String authType) throws CertificateException {
}
@Override
public void checkServerTrusted(
java.security.cert.X509Certificate[] chain,
String authType) throws CertificateException {
}
@Override
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return new java.security.cert.X509Certificate[0];
}
}};
- 使用自定义SSLSocketFactory
private void onHttps(OkHttpClient.Builder builder) {
try {
builder.sslSocketFactory(getSSLSocketFactory()).hostnameVerifier(org.apache.http.conn.ssl.SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
} catch (Exception e) {
e.printStackTrace();
}
}
五、与Rxjava结合使用
与Rxjava的结合使用方式比较简单,如果对Rxjava的使用方法还不熟悉的同学可以看一下我上篇的Rxjva使用。
5.1、配置依赖
使用Rxjava需要我们配置添加依赖:
implementation 'com.squareup.retrofit2:adapter-rxjava:2.8.1'
代码中添加依赖:
addCallAdapterFactory(RxJavaCallAdapterFactory.create())
mRetrofit=new Retrofit.Builder()
.baseUrl(Configure.URL_OPEN)
//支持自定义数据解析
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.build();
5.2、使用方法
Retrofit与Rxjava的结合使用方法, 需要我们在定义的接口中的返回值类型变更为Observable即可。
具体使用方法如下:
- 接口定义:
@POST("user/login")
@FormUrlEncoded
Observable<ResponseBody> postLoginObserve(@Field("username") String name, @Field("password") String pwd);
- 代码实现
Retrofit.Builder retrofitBuilder = new Retrofit.Builder();
Retrofit retrofit = retrofitBuilder.baseUrl(Configure.URL_Android)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.build();
retrofit.create(APIInterface.class)
.postLoginObserve("xuchangqing","Xcq12345")
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<ResponseBody>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
}
@Override
public void onNext(ResponseBody responseBody) {
try {
Log.d(TAG,"Observer_____+"+responseBody.string());
} catch (IOException e) {
e.printStackTrace();
}
}
});
六、问题
java.lang.IllegalArgumentException: baseUrl must end in /
解决办法:baseurl必须以"/"结尾;
java.lang.IllegalArgumentException: @Url cannot be used with @POST URL (parameter #1)
解决办法:@Post或者@Get的注解必须保证是不带路径的。不能写成@Post(“xx/xx”)或者@Get(“xx”)这种形式。
Observable 泛型不支持
解决办法:不要导入java.util的包,导入import rx.Observable;
引入rxjava无法识别AndroidSchedulers.maithread();
注意导包问题,可能Observable所在的包和RxAndroid所在的包名不一致;
七、 总结
这篇文章算是对Retrofit的基本使用做了简单的介绍,但是讲起如何进行封装,有时间打算说一下框架的封装及源码的实现。先说这么多,算是个总结吧。