Retrofit简介

1
2
Retrofit 是一个Square开发的安卓客户端请求库。其中内部封装了okhttp库。
官方的介绍是使用非常简短 Retrofit使用注解,能够极大的简化网络请求数据的代码;

与其它网络库的对比

1
2
3
4
AndroidAsynHttp
- 基于HttpClient作者已停止维护,Android5.0不再使用HttpClient,因此不推荐使用。
Volley
- 基于HttpUrlConnection,Google官方推出,只适合轻量级网络交互如数据传输小,不适合大文件下传下载场景;

Retrofit优点

1
API设计简洁易用、注解化配置高度解耦、支持多种解析器、支持Rxjava

Retrofit使用

1.Retrofit开源库、OkHttp网络库、数据解析器集成注册网络权限

1
2
3
4
5
6
//依赖包导入
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.okhttp3:okhttp:3.14.9'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
//网络权限
<uses-permission android:name="android.permission.INTERNET"/>

2.创建接口设置请求类型与参数

1
2
3
4
5
新建UserInfoModel类和UserMgrService接口
public interface RequestInterface {
@GET("login")
Call<LoginBean> getCloseTime(@Query("ip") String devideIp);
}

请求示例

//假设BASE_URL = “http://192.168.0.1/

@Url

1
2
3
//整个地址都要变化,甚至是baseUrl
@GET
Call<List<Repo>> getData(@Url String user);

@Path

1
2
3
4
//用于替换Url路径中的变量字符。
@GET("weather/{city_name}")
Observable<Object> getWeather(@Path("city_name") String city_name);
//完整结果:http://192.168.0.1/weather/北京

2.1Get请求

@Query

1
2
3
4
//主要用于Get请求数据,用于拼接在拼接在Url路径后面的查询参数,一个@Query相当于拼接一个参数,多个参数中间用,隔开。
@GET("weather")
Observable<WeatherEntity> getWeather(@Query("city") String city);
//完整结果:http://192.168.0.1/weather?city=北京

@QueryMap

1
2
3
4
//主要的效果等同于多个@Query参数拼接,主要也用于Get请求网络数据。
@GET("weather/{city_name}")
Observable<Object> getWeather(@Path("city_name") String city_name, @QueryMap Map<String, String> queryParams);
//完整结果:http://192.168.0.1/weather/北京?user_id=1&user_name=jojo

2.2Post请求

@Filed

1
2
3
4
5
6
//@Field的用法类似于@Query,于拼接在拼接在Url路径后面的查询参数。
@FormUrlEncoded //使用@Field时记得添加@FormUrlEncoded
@POST("comment")
void doComments(@Field("content") String content, @Field("user_id") String user_id);
//完整结果:http://192.168.0.1/comment
//body参数:{content":"我是评论","user_id":"1001"}

@FieldMap

1
2
3
4
5
6
7
//用法类似于@QueryMap,主要用于Map拼接多个参数
@FormUrlEncoded
@POST("comment")
void doComments(@FieldMap Map<String, String> paramsMap );
// HashMap<String, String> hashMap = new HashMap<>();
// hashMap.put("content","我是评论");
// hashMap.put("user_id","1001");

@Body

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//例1
@POST("comment")
void doComments(@Body Object reqBean);
//例2
@POST("comment")
void doComments(@Body List<Object> requestList);
//例3 文件上传
@POST("upload/")
Observable<Object> uploadFile(@Body RequestBody requestBody);
//只不过文件上传传入的是RequestBody类型,下面是构建RequestBody的方式:
File file = new File(mFilePath); //mImagePath为上传的文件绝对路径
//构建body
RequestBody requestBody = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("file", file.getName(), RequestBody.create(MediaType.parse("multipart/form-data"), file))
.build();

2.3Put请求

1
2
3
@PUT("comment/{comment_id}")
void comment(@Path("comment_id") String comment_id);
//完整结果:http://192.168.0.1/comment/88
1
2
3
@PUT("comment/{comment_id}")
void comment(@Path("comment_id") String comment_id @Query("user_id") String user_id);
//完整结果:http://192.168.0.1/comment/88?user_id=1001
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//加body参数:{"content":"我是评论","type":"1"}
//此类请求的应用场景:适合于body中需要传入多个请求参数,这样可以将多个请求的参数字段封装到一个实体中,这样就不用写多个@Filed了
public class RequestBean {
public String content;
public String type;
//实际中可能还有多个请求字段
}
...
RequestBean requestBean = new RequestBean();
requestBean .content = "我是评论";
requestBean .type = "1";
...
@PUT("comment/{comment_id}")
void comment(@Path("comment_id") String comment_id @Query("user_id") String user_id @Body RequestBean reqBean);

2.4Delete

1
2
3
@DELETE("comment/{comment_id}")
void comment(@Path("comment_id") String comment_id);
//假如删除评论:http://192.168.0.1/comment/88

综上所述,可以归纳出上面几个注解的用法:

1
2
3
4
5
6
@Path : 请求的参数值直接跟在URL后面时,用@Path配置
@Query: 表示查询参数,以?key1=value1&key2=value2的形式跟在请求域名后面时使用@Query
@QueryMap :以map的方式直接传入多个键值对的查询参数
@Field: 多用于post请求中表单字段,每个@Field后面,对应一对键值对。
@FieldMap :以map的方式传入多个键值对,作为body参数
@Body: 相当于多个@Field,以对象的形式提交

注意:Filed和FieldMap需要FormUrlEncoded结合使用,否则会抛异常!

3.创建Retrofit对象、设置数据解析器

1
2
Retrofit retrofit = new Retrofit.Builder().baseUrl("http://149.0.171.247:8088/")
.addConverterFactory(GsonConverterFactory.create().build();

baseUrl:

1
2
3
这里的baseUrl是自己访问的Url的基类地址,加上刚才@GET(“login”)中的login才是我们真正要访问的地址,
因为使用了@Query("ip"),所以最终的访问地址为http://149.0.171.247:8088/login?ip=devideIp,此处的ip为自己传入的参数。
注意: baseUrl必须要以/结尾!!!

4.生成接口对象

1
RequestInterface service= retrofit.create(RequestInterface.class);

5.调用接口方法返回Call对象

1
Call<LoginBean> call=service.login("zhangsan""123456")

6.发送请求(同步、异步)

1
2
3
4
5
6
7
8
9
10
11
12
13
同步:调用Call对象的execute(),返回结果的响应体
异步:调用Call对象的enqueue0,参数是一个回调

call.enqueue(new Callback<LoginBean>() {
@Override
public void onResponse(Call<LoginBean> call, Response<LoginBean> response) {

}
@Override
public void onFailure(Call<LoginBean> call, Throwable t) {

}
});

问题:

1
2
3
4
5
6
7
使用时别忘了申请网络权限哦,使用时可能会遇到以下问题:
CLEARTEXT communication to mock-api.com not permitted by network security policy

这是因为Android P不允许明文访问,而前面的mock地址是http开头的,
解决办法是在AndroidManifest中的application内加入下面这段代码即可:

android:usesCleartextTraffic="true"

一、Retrofit工具类的封装(核心类)

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
 /**
* Retrofit工具类
*/
public class RetrofitUtils {
public static final String BASE_URL = "http://XXX";
/**
* 超时时间
*/
public static final int TIMEOUT = 60;
private static volatile RetrofitUtils mInstance;
private Retrofit mRetrofit;

public static RetrofitUtils getInstance() {
if (mInstance == null) {
synchronized (RetrofitUtils.class) {
if (mInstance == null) {
mInstance = new RetrofitUtils();
}
}
}
return mInstance;
}

private RetrofitUtils() {
initRetrofit();
}

/**
* 初始化Retrofit
*/
private void initRetrofit() {
OkHttpClient.Builder builder = new OkHttpClient.Builder();
// 设置超时
builder.connectTimeout(TIMEOUT, TimeUnit.SECONDS);
builder.readTimeout(TIMEOUT, TimeUnit.SECONDS);
builder.writeTimeout(TIMEOUT, TimeUnit.SECONDS);
OkHttpClient client = builder.build();
mRetrofit = new Retrofit.Builder()
// 设置请求的域名
.baseUrl(BASE_URL)
// 设置解析转换工厂,用自己定义的
.addConverterFactory(ResponseConvert.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.client(client)
.build();
}

/**
* 创建API
*/
public <T> T create(Class<T> clazz) {
return mRetrofit.create(clazz);
}
}

代码很简单,创建后台请求接口,调用create即可。

二、Converter.Factory的封装

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
/**
* 自定义Gson解析转换
*/
public class ResponseConvert extends Converter.Factory {
public static ResponseConvert create() {
return new ResponseConvert();
}

/**
* 转换的方法
*/
@Override
public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
return new BodyConverter<>(type);
}

private class BodyConverter<T> implements Converter<ResponseBody, T> {
private Gson gson;
private Type type;

public BodyConverter(Type type) {
this.type = type;
gson = new GsonBuilder()
.registerTypeHierarchyAdapter(List.class, new ListTypeAdapter())
.create();
}

@Override
public T convert(ResponseBody value) throws IOException {
String json = value.string();
return gson.fromJson(json, type);
}
}

/**
* 空列表的转换
*/
private static class ListTypeAdapter implements JsonDeserializer<List<?>> {
@Override
public List<?> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
if (json != null && json.isJsonArray()) {
JsonArray array = json.getAsJsonArray();
Type itemType = ((ParameterizedType) typeOfT).getActualTypeArguments()[0];
java.util.List list = new ArrayList<>();
for (int i = 0; i < array.size(); i++) {
JsonElement element = array.get(i);
Object item = context.deserialize(element, itemType);
list.add(item);
}
return list;
} else {
//和接口类型不符,返回空List
return Collections.EMPTY_LIST;
}
}
}
}

三、Retrofit网络请求基类的封装

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
/**
* 后台统一接口API
*/

public interface ServerApi {
// 联系人编辑
@POST(URLS.LOGIN)
Observable<ResponseBean<LoginBean>> login(@Body RequestBody requestBody);
}

/**
* 请求网络业务的基类,AppPresenterr 的封装
*/

public class AppPresenter {
protected ServerApi mApi = RetrofitUtils.getInstance().create(ServerApi.class);
private static final Gson gson = new Gson();

/**
* 1. 转换
* 统一处理一些动作
*/
public static <T> void convert(Observable<ResponseBean<T>> observable, Observer<T> observer) {
observable
.map(new Function<ResponseBean<T>, T>() {
@Override
public T apply(ResponseBean<T> httpResult) throws Exception {
// 打印响应的对象
LogUtils.object(httpResult);
// TODO 实际开发的时候统一处理一些东西
if (httpResult == null || httpResult.head == null) {
throw new RuntimeException("请求数据异常");
} else if (!"1".equals(httpResult.head.bcode)) {
throw new RuntimeException(httpResult.head.bmessage);
}
return httpResult.data;
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(observer);
}

/**
* 2. 执行的方法
*/
public static <T> void execute(Observable<ResponseBean<T>> observable, Observer<ResponseBean<T>> observer) {
observable.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(observer);
}

/**
* 3.请求数据是Json,Json转成RequestBody
*/
public static RequestBody createRequestBody(Object obj) {
RequestBean bean = new RequestBean<>(obj);
String json = gson.toJson(bean);
// 打印请求的Json
LogUtils.json(json);
RequestBody body = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), json);
return body;
}

}
有三个通用的方法:
1.convert方法,转换、统一处理网络请求,将公共处理部分放在这方法里面。
2.execute方法,只执行,不做任何处理操作,适用于一些不能统一处理的接口。
3.createRequestBody方法,就是统一创建请求的RequestBody。

四、具体网络请求业务类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 登录的业务类
*/

public class LoginPresenter extends AppPresenter {
/**
* 登录的接口
*/
public void login(LoginData data, Observer<LoginBean> observer) {
Observable<ResponseBean<LoginBean>> login = mApi.login(createRequestBody(data));
// 转换
convert(login, observer);
}
}

五、测试和使用

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 调用登录接口
*/
public void open(View view) {
LoginData loginData = new LoginData("135****5219", "12***56");
presenter.login(loginData, new DialogObserver<LoginBean>(getAppActivity()) {
@Override
public void onNext(LoginBean data) {
// TODO 做登录的成功的操作
Toast.makeText(getAppActivity(), "" + data.userInfo.nickName, Toast.LENGTH_SHORT).show();
}
});
}

六、补充,对于Observer需要再次封装。

1
2
3
4
1.如调用登录要显示Dialog
2.如Activity销毁要取消请求。
3.结合加载数据各种状态页面,如:加载中、加载失败、网络异常、数据为空等等
这些都是可以统一封装在Observer<T>里面。

工具类原文:https://www.jianshu.com/p/7f843d65a7c6