Android 开发之网络编程 Retrofit

Android 网络编程 Retrofit

适用于 Android 和 Java 的类型安全 HTTP 客户端。Retrofit 是一款基于注解把 http api 转成接口,使用起来更加简单。

文档和代码

文档地址:

Retrofit 文档 :https://square.github.io/retrofit/

Github 地址:https://github.com/square/retrofit

Retrofit API 文档:https://square.github.io/retrofit/2.x/retrofit/

添加依赖

版本

版本在 release 里看就好了

1
implementation 'com.squareup.retrofit2:retrofit:(insert latest version)'

目前最新的版本是:2.9.0

所以我这里的依赖就用

1
implementation 'com.squareup.retrofit2:retrofit:2.9.0

另外,我们要把结果转成对象,这个时候需要加一些转换器

1
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'

当然啦,有多种。在官方文档中有提到

可以转 xml 的数据,可以转 Json 的数据。我们的数据格式是 json,可以用 Gson,也可以用 Jackson

版本地址可以到中央仓库去搜索一下

converter-gson: https://mvnrepository.com/artifact/com.squareup.retrofit2/converter-gson

入门代码

  1. 先定义一个接口
1
2
3
4
5
6
7
public interface SobMiniWebInterface {

@GET("/get/text")
Call<JsonResult> getJson();

}

  1. 定义结果 bean 类(实体类)
  2. 创建 retrofit
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public void getJson(View view) {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http//:127.0.0.1:9102")
.addConverterFactory(GsonConverterFactory.create())
.build();
SobMiniWebInterface sobMiniWebInterface = retrofit.create(SobMiniWebInterface.class);
Call task = sobMiniWebInterface.getJson();
task.enqueue(new Callback() {
@Override
public void onResponse(Call call,Response response) {
Log.d(TAG,"response -- > " + response.body());
}

@Override
public void onFailure(Call call,Throwable t) {
Log.d(TAG,"exception -- > " + t.toString());
}
});
}
  1. 执行结果:

初步封装

如果我们每次使用,都要去创建 Retrofit,那不是很麻烦吗?

所以我们可以把这些相关的配置,整合起来。把 Retrofit 的创建和配置定义成一个单例,这样子,我们要去发起请求的时候,直接使用单例获取就完事了。不用再重复去编写 baseURL 、转换器、连接时的相关属性,等等…

于是我们创建一个单例

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
package com.sunofbeaches.retrofitdemo;

import java.util.concurrent.TimeUnit;

import okhttp3.OkHttpClient;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;

public class RetrofitCreator {

public static final String BASE_URL = "https://127.0.0.1:9102";
public static final int CONNECT_TIME_OUT = 10000;//毫秒
private Retrofit mRetrofit;


private RetrofitCreator() {
createRetrofit();
}

private void createRetrofit() {
//设置一下okHttp的参数
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.connectTimeout(CONNECT_TIME_OUT,TimeUnit.MILLISECONDS)
.build();
mRetrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)//设置BaseUrl
.client(okHttpClient)//设置请求的client
.addConverterFactory(GsonConverterFactory.create())//设置转换器
.build();
}

private static RetrofitCreator sRetrofitCreator = null;

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


public Retrofit getRetrofit() {
return mRetrofit;
}


}

那使用变成怎么样了呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public void getJson(View view) {
SobMiniWebInterface sobMiniWebInterface = RetrofitCreator.getInstance().getRetrofit().create(SobMiniWebInterface.class);
Call task = sobMiniWebInterface.getJson();
task.enqueue(new Callback() {
@Override
public void onResponse(Call call,Response response) {
Log.d(TAG,"response -- > " + response.body());
}

@Override
public void onFailure(Call call,Throwable t) {
Log.d(TAG,"exception -- > " + t.toString());
}
});
}

如果多个地方使用呢?重复的代码是不是变少啦!

接口定义

前面我们定义了一个接口

1
2
3
4
5
6
7
public interface SobMiniWebInterface {

@GET("/get/text")
Call<JsonResult> getJson();

}

这里我们可以看到有一个注解@Get

@Documented

@Target(value=METHOD)

@Retention(value=RUNTIME)

public @interface GET Make a GET request.

从官方的 API 文档里我们可以看到这个注解是作用于方法,是一个运行时起作用的注解。创建一个 get request 之前我们创建 Request 是 new 出来了。Retrofit 则直接使用注解

知道这个,那就好办了!

那我 post 请求呢?是不也可以来一个@Post 呢?

是的!Restrofit,rest 就是 Restful 的意思,按这个套路,肯定有 get、post、delete、put。当然还有其他的。

那咱们列一个思维导图吧

get 请求

1
2
3
4
5
6
7
public interface SobMiniWebInterface {

@GET("/get/text")
Call<JsonResult> getJson();

}

带参数的咋整呢?

比如说这个接口:

1
/get/param

参数: - keyword 关键字 - page 页码 - order 0 顺序 1 逆序

接口定义:两种写法都可以

1
2
3
4
5
@GET("/get/param")
Call<GetParamsResult> getWithParams(@Query("keyword") String keyword,@Query("page") int page,@Query("order") String order);

@GET("/get/param")
Call<GetParamsResult> getWithParams(@QueryMap Map<String,Object> params);

调用:

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
Call<GetParamsResult> call = mSobMiniWebInterface.getWithParams("关键字测试",1,"1");
call.enqueue(new Callback<GetParamsResult>() {
@Override
public void onResponse(Call<GetParamsResult> call,Response<GetParamsResult> response) {
GetParamsResult result = response.body();
Log.d(TAG,"result -- > " + result);
if(result.getCode() == 10000) {
Log.d(TAG,"请求成功");
Log.d(TAG,"结果信息 --> " + result.getMessage());
}
}

@Override
public void onFailure(Call<GetParamsResult> call,Throwable t) {
Log.d(TAG,"请求失败...onFailure");
}
});

Map<String,Object> params = new HashMap<>();
params.put("keyword","map 参数测试关键字");
params.put("page",1);
params.put("order","0");
Call<GetParamsResult> mapTask = mSobMiniWebInterface.getWithParams(params);
mapTask.enqueue(new Callback<GetParamsResult>() {
@Override
public void onResponse(Call<GetParamsResult> call,Response<GetParamsResult> response) {
Log.d(TAG,"onResponse ==> " + response.body());
}

@Override
public void onFailure(Call<GetParamsResult> call,Throwable t) {
Log.d(TAG,"onFailure == > " + t.toString());
}
});

结果:

1
2
3
4
D/MainActivity: getWithParams..
D/MainActivity: result -- > GetParamsResult{success=true, code=10000, message='get带参数请求成功.', data=DataBean{page='1', keyword='关键字测试', order='逆序
D/MainActivity: 请求成功
D/MainActivity: 结果信息 --> get带参数请求成功.

到这里我们就学习了

@Qeury 注解,@QueryMap 注解

post 请求,body 携带字符串内容(json)

这个参数是 body 的内容

接口文档看前面呀。

定义接口

1
2
@POST("/post/comment")
Call<PostWithBodyResult> postWithBodyContent(@Body CommentItem commentItem);

调用代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public void postWithBody(View view) {
CommentItem commentItem = new CommentItem();
commentItem.setArticleId("1251412341");
commentItem.setCommentContent("这是我提交的测试评论内容...");
Call<PostWithBodyResult> task = mSobMiniWebInterface.postWithBodyContent(commentItem);
task.enqueue(new Callback<PostWithBodyResult>() {
@Override
public void onResponse(Call<PostWithBodyResult> call,Response<PostWithBodyResult> response) {
Log.d(TAG,"response -- > " + response.body());
}

@Override
public void onFailure(Call<PostWithBodyResult> call,Throwable t) {
Log.d(TAG,"post with body...onFailure");
}
});
}

结果:

1
D/MainActivity: response -- > PostWithBodyResult{success=true, code=10000, message='评论成功:这是我提交的测试评论内容...', data=null}

到这里我们就学习了@Body 注解

post 请求 Url 携带参数

接口:

接口定义:

1
2
3

@POST("/post/string")
Call<PostWithUrlParamsResult> postWithUrlParams(@Query("string") String text);

调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

public void postWithUrlParams(View view) {
Call<PostWithUrlParamsResult> task = mSobMiniWebInterface.postWithUrlParams("这是我提交的参数内容");
task.enqueue(new Callback<PostWithUrlParamsResult>() {
@Override
public void onResponse(Call<PostWithUrlParamsResult> call,Response<PostWithUrlParamsResult> response) {
Log.d(TAG,"onResponse result -- > " + response.body());
}

@Override
public void onFailure(Call<PostWithUrlParamsResult> call,Throwable t) {
Log.d(TAG,"onFailure -- > " + t.toString());
}
});
}

单文件上传

权限自行处理

@Part 注解,要跟@Multipart 注解一起使用。

而参数类型有三种:

  • MultipartBody.Part
  • RequestBody
  • Other object

具体可以去看看文档

1
2
3
4
/**
* The name of the part. Required for all parameter types except
* {@link okhttp3.MultipartBody.Part}.
*/

除了 MultipartBody.Part 类型以外,其他的参数类型都要加上注解参数。

接口:

1
2
3
4

@Multipart
@POST("/file/upload")
Call<FileUploadResult> postFile(@Part MultipartBody.Part file);

上传文件调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public void postFile(View view) {
File file = new File("/storage/emulated/0/Download/1.jpg");
MediaType mediaType = MediaType.parse("image/jpg");
RequestBody fileBody = RequestBody.create(mediaType,file);
MultipartBody.Part part = MultipartBody.Part.createFormData("file",file.getName(),fileBody);
Call<FileUploadResult> task = mSobMiniWebInterface.postFile(part);
task.enqueue(new Callback<FileUploadResult>() {
@Override
public void onResponse(Call<FileUploadResult> call,Response<FileUploadResult> response) {
Log.d(TAG,"onResponse -- > " + response.body());
}

@Override
public void onFailure(Call<FileUploadResult> call,Throwable t) {
Log.d(TAG,"onFailure -- > " + t.toString());
}
});
}

上传结果:

img

到这里我们就学习了@Part 和@Multipart 注解的使用了

上传文件及附带信息

提这个方式是为了跟大家了解@PartMap 的使用

接口定义:

1
2
3
4
@Multipart
@POST("/multiFiles/upload")
Call<FileUploadResult> postFileWithParams(@PartMap Map<String,Object> params,@Part MultipartBody.Part file);

调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public void postMultiFile(View view) {
Map<String,Object> params = new HashMap<>();
MultipartBody.Part filePart = getPart("file","/storage/emulated/0/Download/rBsADV3nxtKACoSfAAAPx8jyjF8169.png");
params.put("isFree","false");
params.put("description","我是文件的描述内容...");
Call<FileUploadResult> task = mSobMiniWebInterface.postFileWithParams(params,filePart);
task.enqueue(new Callback<FileUploadResult>() {
@Override
public void onResponse(Call<FileUploadResult> call,Response<FileUploadResult> response) {
Log.d(TAG,"onResponse -- > " + response.body());
}

@Override
public void onFailure(Call<FileUploadResult> call,Throwable t) {
Log.d(TAG,"onFailure -- >" + t.toString());
}
});
}

结果:

1
onResponse -- > FileUploadResult{success=true, code=10000, message='上传成功.文件路径为:E:\codes\Idear\SobNetworkCourseServer\target\classes\sobUpload\rBsADV3nxtKACoSfAAAPx8jyjF8169.png', data=your descriptions is --> "我是文件的描述内容..." isFree == > "false"}

多文件上传

后台接口:

定义接口:

1
2
3
@Multipart
@POST("/files/upload")
Call<FileUploadResult> postFiles(@Part List<MultipartBody.Part> files);

调用代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public void postFiles(View view) {
List<MultipartBody.Part> files = new ArrayList<>();
files.add(getPart("files","/storage/emulated/0/Download/1.jpg"));
files.add(getPart("files","/storage/emulated/0/Download/rBsADV3nxtKACoSfAAAPx8jyjF8169.png"));
files.add(getPart("files","/storage/emulated/0/Download/rBsADV2rEz-AIzSoAABi-6nfiqs456.png"));

Call<FileUploadResult> task = mSobMiniWebInterface.postFiles(files);
task.enqueue(new Callback<FileUploadResult>() {
@Override
public void onResponse(Call<FileUploadResult> call,Response<FileUploadResult> response) {
Log.d(TAG,"多文件上传结果" + response.body());
}

@Override
public void onFailure(Call<FileUploadResult> call,Throwable t) {
Log.d(TAG,"onFailure -- > 多文件上传失败 ---> " + t.toString());
}
});
}

辅助方法

1
2
3
4
5
6
7
private MultipartBody.Part getPart(String key,String filePath) {
File file = new File(filePath);
MediaType mediaType = MediaType.parse("image/jpg");
RequestBody fileBody = RequestBody.create(mediaType,file);
return MultipartBody.Part.createFormData(key,file.getName(),fileBody);
}

多文件上传结果

FileUploadResult{success=true, code=10000, message=’上传成功 3 个文件,路径:C:/Users/TrillGates/Desktop/SOBAndroidMiniWeb/sobUpload’, data=null}

文件下载

接口

定义接口:

1
2
3
@Streaming
@GET
Call<ResponseBody> downFile(@Url String url);

代码调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
ublic void downFile(View view) {
Call<ResponseBody> task = mSobMiniWebInterface.downFile("/download/10");
task.enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(Call<ResponseBody> call,Response<ResponseBody> response) {
Headers headers = response.headers();
for(int i = 0; i < headers.size(); i++) {
Log.d(TAG,headers.name(i) + " == " + headers.value(i));
}
writeFile2Sd(response,headers);
}

@Override
public void onFailure(Call<ResponseBody> call,Throwable t) {
Log.d(TAG,"onFailure -- > " + t.toString());
}
});
}

同学们会发现,我们收取的内容,需要进行 iO 操作,但是主线程不可以做文件读取呀。

所以又整了一个子线程。

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
private void writeFile2Sd(final Response<ResponseBody> response,final Headers headers) {
new Thread(new Runnable() {
@Override
public void run() {
String disposition = headers.get("Content-disposition");
if(disposition != null) {
int fileNameIndex = disposition.indexOf("filename=");
Log.d(TAG,"fileNameIndex -- > " + fileNameIndex);
String fileName = disposition.substring(fileNameIndex + "filename=".length());
Log.d(TAG,"fileName -- > " + fileName);
File picFilePath = MainActivity.this.getExternalFilesDir(Environment.DIRECTORY_PICTURES);
Log.d(TAG,"picFilePath --> " + picFilePath);
File file = new File(picFilePath + File.separator + fileName);
Log.d(TAG,"file -- > " + file);
FileOutputStream fos = null;
try {
if(!file.getParentFile().exists()) {
file.getParentFile().mkdirs();
}
if(!file.exists()) {
file.createNewFile();
}
fos = new FileOutputStream(file);
InputStream inputStream = response.body().byteStream();
byte[] buf = new byte[1024];
int len;
while((len = inputStream.read(buf,0,buf.length)) != -1) {
fos.write(buf,0,len);
}
} catch(Exception e) {
e.printStackTrace();
} finally {
if(fos != null) {
try {
fos.close();
} catch(IOException e) {
e.printStackTrace();
}
}
}
}
}
}).start();
}

这样子特别不方便,对吧!也不优雅,所以后面我们就要引入 RxJava 去解决这个问题了。

RxJava 可以很方便地切换线程。

下载结果

提交表单

方法:post 接口:/login

定义接口

1
2
3
@FormUrlEncoded
@POST("/login")
Call<ResponseBody> doLogin(@Field("userName") String userName,@Field("password") String password);

调用代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public void login(View view) {
Call<ResponseBody> call = mSobMiniWebInterface.doLogin("root","admin");
call.enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(Call<ResponseBody> call,Response<ResponseBody> response) {
try {
Log.d(TAG,"onResponse -- > " + response.body().string());
} catch(IOException e) {
e.printStackTrace();
}
}

@Override
public void onFailure(Call<ResponseBody> call,Throwable t) {
Log.d(TAG,"onFailure == > " + t.toString());
}
});
}

运行结果:

1
{"success":true,"code":10000,"message":"这是你提交上来的数据:root - admin","data":"8920d231-20e4-46c6-9b41-9812730c4cce"}

到这里的话,我们就把@Failed 搞定了,那@FailedMap 呢?

相信聪明的你早就想到了,套路跟前面一样呀!

再来一个,定义接口

1
2
3
@FormUrlEncoded
@POST("/login")
Call<ResponseBody> login(@FieldMap Map<String,Object> params);

调用代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

public void login(View view) {
// Call<ResponseBody> call = mSobMiniWebInterface.doLogin("root","admin");
Map<String,Object> params = new HashMap<>();
params.put("password","234123lkjsfa");
params.put("userName","root");
Call<ResponseBody> call = mSobMiniWebInterface.login(params);
call.enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(Call<ResponseBody> call,Response<ResponseBody> response) {
try {
Log.d(TAG,"onResponse -- > " + response.body().string());
} catch(IOException e) {
e.printStackTrace();
}
}

@Override
public void onFailure(Call<ResponseBody> call,Throwable t) {
Log.d(TAG,"onFailure == > " + t.toString());
}
});
}

运行结果

1
{"success":true,"code":10000,"message":"这是你提交上来的数据:root - 234123lkjsfa","data":"5af51e33-8c9c-4898-8c0e-bf44e0d2fc4f"}

Okay 啦,到这里@FiledMap 也搞定了

关于参数的接口

大家再 review 一下吧。

header 注解

请求头注解我们还没使用上,那要怎么使用呢?

看码:

1
2
3
4
5
6
7
8
9
10
11
12
13
@Multipart
@POST("/file/upload")
Call<FileUploadResult> postFile(@Part MultipartBody.Part file,@Header("token") String token);

@Multipart
@POST("/files/upload")
Call<FileUploadResult> postFiles(@Part List<MultipartBody.Part> files,@HeaderMap Map<String,String> headers);

@Headers({"token:231231","version:1.0","client:android"})
@Multipart
@POST("/file/params/upload")
Call<FileUploadResult> postFileWithParams(@PartMap Map<String,Object> params,@Part MultipartBody.Part file);

其实我们很少这么用的,假如说,我们是为了用户验证,携带 token 的话,在每个方法都添加,那不是很麻烦吗?

而 Okhttp 是支持拦截器的,我们直接在每次请求的时候,在头部加上 token 就不可以了吗?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
private void createRetrofit() {
//设置一下okHttp的参数
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.connectTimeout(CONNECT_TIME_OUT,TimeUnit.MILLISECONDS)
.addInterceptor(mHeaderInterceptor)
.build();
mRetrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)//设置BaseUrl
.client(okHttpClient)//设置请求的client
.addConverterFactory(GsonConverterFactory.create())//设置转换器
.build();
}

private Interceptor mHeaderInterceptor = new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request().newBuilder()
.addHeader("token","204391jawslejqowejqwi")
.addHeader("version","1.2.0")
.addHeader("client","android铂金版")
.build();
return chain.proceed(request);
}
};

PS:注解里的可以覆盖掉拦截器里的内容.