Android使用Retrofit进行网络请求及和Kotlin结合使用

前言

Retrofit是现在比较流行的网络请求框架,可以理解为okhttp的加强版,底层封装了Okhttp。

准确来说,Retrofit是一个RESTful的http网络请求框架的封装。因为网络请求工作本质上是由okhttp来完成,而Retrofit负责网络请求接口的封装。

https://square.github.io/retrofit/

代理HTTPS无法请求

Android要配置networkSecurityConfig

查看

https://www.psvmc.cn/article/2019-04-03-win-proxy.html

基本配置

添加网络权限

1
<uses-permission android:name="android.permission.INTERNET"/>

添加依赖

1
2
implementation ("com.squareup.retrofit2:retrofit:2.9.0")
implementation ("com.squareup.retrofit2:converter-gson:2.9.0")

Kotlin整体示例

新版本的Retrofit已经可以方便使用Kotlin的协程。

添加实体

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
import java.util.List;

public class ResultListVo<T> {
private int code;
private String msg;
private List<T> obj;

public int getCode() {
return code;
}

public void setCode(int code) {
this.code = code;
}

public String getMsg() {
return msg;
}

public void setMsg(String msg) {
this.msg = msg;
}

public List<T> getObj() {
return obj;
}

public void setObj(List<T> obj) {
this.obj = obj;
}
}

用户

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class UserVo {
private String name;
private String sex;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getSex() {
return sex;
}

public void setSex(String sex) {
this.sex = sex;
}
}

服务接口

添加suspend关键字,返回类型Response<T>

AppService.kt

1
2
3
4
5
6
7
8
9
10
import com.xhkjedu.xh_control_appstore.model.ResultListVo
import com.xhkjedu.xh_control_appstore.model.UserVo
import retrofit2.Response
import retrofit2.http.GET

interface AppService {
//get请求
@GET("userlist.json")
suspend fun getUserList(): Response<ResultListVo<UserVo>>
}

请求头设置

HeaderInterceptor.kt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import android.util.Log
import com.xhkjedu.xh_control_browser.common.CommonData
import okhttp3.Interceptor
import okhttp3.Response
import java.io.IOException

class HeaderInterceptor : Interceptor {
var TAG = "HeaderInterceptor"
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
val original = chain.request()
val mBuilder = original.newBuilder()
if (CommonData.loginUser != null) {
mBuilder.header("XH-UserId", CommonData.loginUser!!.userid.toString() + "")
}
val request = mBuilder
.header("XH-DeviceModel", CommonData.deviceModel())
.method(original.method(), original.body())
.build()
return chain.proceed(request)
}
}

管理类

ApiManager.kt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import com.xhkjedu.xh_control_appstore.common.CommonData
import okhttp3.OkHttpClient
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory

object ApiManager {
private lateinit var retrofit: Retrofit
lateinit var appService: AppService
fun init() {
if (!::retrofit.isInitialized) {
val client = OkHttpClient.Builder().addInterceptor(HeaderInterceptor()).build();
retrofit = Retrofit.Builder()
.baseUrl(CommonData.apiUrl)
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.build()
}

if (!::appService.isInitialized) {
appService = retrofit.create(AppService::class.java)
}
}
}

调用

简单调用

1
2
3
4
GlobalScope.launch {
var result = ApiManager.appService.getUserList().body()
Log.i("API", Gson().toJson(result))
}

ViewModel中调用

BaseViewModel

如果使用Jetpack Compose

BaseViewModel.kt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext

open class BaseViewModel : ViewModel() {
var zIsShowMsg = mutableStateOf(false)
var zShowMsgContent = mutableStateOf("")

fun mLaunch(block: suspend () -> Unit) {
viewModelScope.launch(Dispatchers.IO) {
try {
block()
} catch (e: Exception) {
withContext(Dispatchers.Main){
zIsShowMsg.value = true
zShowMsgContent.value = "接口请求失败"
}
}
}
}
}

如果使用DataBinding

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
import androidx.databinding.ObservableBoolean
import androidx.databinding.ObservableField
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext

open class BaseViewModel : ViewModel() {
@JvmField
var zIsShowMsg = ObservableBoolean(false)
@JvmField
var zShowMsgContent = ObservableField("")

@JvmField
val isLoading = ObservableBoolean(false)

fun mLaunch(block: suspend () -> Unit) {
viewModelScope.launch(Dispatchers.IO) {
try {
block()
} catch (e: Exception) {
withContext(Dispatchers.Main){
zIsShowMsg.set(true)
zShowMsgContent.set("接口请求失败")
}
}
}
}

fun loadDataWithLoading(block: suspend () -> Unit){
viewModelScope.launch(Dispatchers.IO) {
try {
isLoading.set(true)
block()
} catch (e: Exception) {
withContext(Dispatchers.Main){
zIsShowMsg.set(true)
zShowMsgContent.set("接口请求失败")
}
}
isLoading.set(false)
}
}
}

调用

UserViewModel

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
import android.util.Log
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel
import com.google.gson.Gson
import com.xhkjedu.xh_control_appstore.api.ApiManager
import com.xhkjedu.xh_control_appstore.model.UserVo
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext

class UserViewModel : BaseViewModel() {
val userList = mutableStateListOf<UserVo>()
fun loadData() {
mLaunch {
val result = ApiManager.appService.getUserList().body()
withContext(Dispatchers.Main) {
result?.let {
if (result.code == 0) {
userList.addAll(result.obj)
zIsShowMsg.value = false
} else {
zIsShowMsg.value = true
zShowMsgContent.value = result.msg
}
}
}
}
}
}

JAVA整体示例

假如我们要请求

https://www.psvmc.cn/userlist.json

添加实体

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
import java.util.List;

public class ResultListVo<T> {
private int code;
private String msg;
private List<T> obj;

public int getCode() {
return code;
}

public void setCode(int code) {
this.code = code;
}

public String getMsg() {
return msg;
}

public void setMsg(String msg) {
this.msg = msg;
}

public List<T> getObj() {
return obj;
}

public void setObj(List<T> obj) {
this.obj = obj;
}
}

用户

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class UserVo {
private String name;
private String sex;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getSex() {
return sex;
}

public void setSex(String sex) {
this.sex = sex;
}
}

请求接口类

1
2
3
4
5
6
7
8
9
import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.Query;

public interface UserApi {
//get请求
@GET("userlist.json")
Call<ResultListVo<UserVo>> getUserList();
}

创建Retrofit实例

1
2
3
4
5
6
7
//构建Retrofit实例
Retrofit retrofit = new Retrofit.Builder()
//设置网络请求BaseUrl地址
.baseUrl("https://www.psvmc.cn/")
//设置数据解析器
.addConverterFactory(GsonConverterFactory.create())
.build();

接口对象实例

1
2
3
4
//创建网络请求接口对象实例
UserApi userApi = mRetrofit.create(UserApi.class);
//对发送请求进行封装
Call<ResultListVo<UserVo>> resultCall = userApi.getUserList();

调用

同步调用

1
2
//同步请求
Response<ResultListVo<UserVo>> data= resultCall.execute();

异步调用

1
2
3
4
5
6
7
8
9
10
11
12
//异步请求
resultCall.enqueue(new Callback<ResultListVo<UserVo>>() {
//请求成功回调
@Override
public void onResponse(Call<ResultListVo<UserVo>> call, Response<ResultListVo<UserVo>> response) {
ResultListVo<UserVo> body = response.body();
}
//请求失败回调
@Override
public void onFailure(Call<ResultListVo<UserVo>> call, Throwable t) {
}
});

设置请求头

添加Interceptor

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
import android.util.Log;

import com.xhkjedu.xh_control_appstore.utils.ZDeviceUtils;

import java.io.IOException;

import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;

public class HeaderInterceptor implements Interceptor {
String TAG = "HeaderInterceptor";

@Override
public Response intercept(Interceptor.Chain chain) throws IOException {
String systemModel = ZDeviceUtils.getSystemModel();
Request original = chain.request();
Request request = original.newBuilder()
.header("XH-UserId", "1")
.header("XH-DeviceModel", systemModel)
.method(original.method(), original.body())
.build();

return chain.proceed(request);
}
}

其中

intercept 会在每次请求的时候都触发。

在创建实例的时候传入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import com.xhkjedu.xh_control_appstore.common.CommonData
import okhttp3.OkHttpClient
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory

object ApiManager {
private lateinit var retrofit: Retrofit
lateinit var appService: AppService
fun init() {
if (!::retrofit.isInitialized) {
val client = OkHttpClient.Builder().addInterceptor(HeaderInterceptor()).build();
retrofit = Retrofit.Builder()
.baseUrl(CommonData.apiUrl)
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.build()
}

if (!::appService.isInitialized) {
appService = retrofit.create(AppService::class.java)
}
}
}

注解

Retrofit使用大量注解来简化请求,Retrofit将okhttp请求抽象成接口,使用注解来配置和描述网络请求参数。

请求方法注解

请求方法注解 说明
@GET get请求
@POST post请求
@PUT put请求
@DELETE delete请求
@PATCH patch请求,该请求是对put请求的补充,用于更新局部资源
@HEAD head请求
@OPTIONS options请求
@HTTP 通过注解,可以替换以上所有的注解,它拥有三个属性:method、path、hasBody

请求头注解

请求头注解 说明
@Headers 用于添加固定请求头,可以同时添加多个,通过该注解的请求头不会相互覆盖,而是共同存在
@Header 作为方法的参数传入,用于添加不固定的header,它会更新已有请求头

请求参数注解

请求参数注解 说明
@Body 多用于Post请求发送非表达数据,根据转换方式将实例对象转化为对应字符串传递参数,比如使用Post发送Json数据,添加GsonConverterFactory则是将body转化为json字符串进行传递
@Filed 多用于Post方式传递参数,需要结合@FromUrlEncoded使用,即以表单的形式传递参数
@FiledMap 多用于Post请求中的表单字段,需要结合@FromUrlEncoded使用
@Part 用于表单字段,Part和PartMap与@multipart注解结合使用,适合文件上传的情况
@PartMap 用于表单字段,默认接受类型是Map<String,RequestBody>,可用于实现多文件上传
@Path 用于Url中的占位符
@Query 用于Get请求中的参数
@QueryMap 与Query类似,用于不确定表单参数
@Url 指定请求路径

请求和响应格式(标记)注解

标记类注解 说明
@FormUrlEncoded 表示请求发送编码表单数据,每个键值对需要使用@Filed注解
@Multipart 表示请求发送form_encoded数据(使用于有文件上传的场景),每个键值对需要用@Part来注解键名,随后的对象需要提供值
@Streaming 表示响应用字节流的形式返回,如果没有使用注解,默认会把数据全部载入到内存中,该注解在下载大文件时特别有用

参数示例

URL上的参数

1
2
3
4
5
@GET("user")
Call<ResponseBody> getData2(@Query("id") long idLon, @Query("name") String nameStr);

@GET("user")
Call<ResponseBody> getData3(@QueryMap Map<String, Object> map);

Post请求-Form形式

1
2
3
4
5
6
7
@FormUrlEncoded
@POST("user/emails")
Call<ResponseBody> getPostData2(@Field("name") String nameStr, @Field("sex") String sexStr);

@FormUrlEncoded
@POST("user/emails")
Call<ResponseBody> getPsotData3(@FieldMap Map<String, Object> map);

Post请求-JSON形式

1
2
@POST("user/emails")
Call<ResponseBody> getPsotDataBody(@Body RequestBody body);

URL上占位符

1
2
@GET("orgs/{id}")
Call<ResponseBody> getPathData(@Query("name") String nameStr, @Path("id") long idLon);

文件上传

1
2
3
@Multipart
@POST("user/followers")
Call<ResponseBody> getPartData(@Part("name") RequestBody name, @Part MultipartBody.Part file);

调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//声明类型,这里是文字类型
MediaType textType = MediaType.parse("text/plain");
//根据声明的类型创建RequestBody,就是转化为RequestBody对象
RequestBody name = RequestBody.create(textType, "这里是你需要写入的文本:刘亦菲");

//创建文件,这里演示图片上传
File file = new File("文件路径");
if (!file.exists()) {
file.mkdir();
}

//将文件转化为RequestBody对象
//需要在表单中进行文件上传时,就需要使用该格式:multipart/form-data
RequestBody imgBody = RequestBody.create(MediaType.parse("image/png"), file);
//将文件转化为MultipartBody.Part
//第一个参数:上传文件的key;第二个参数:文件名;第三个参数:RequestBody对象
MultipartBody.Part filePart = MultipartBody.Part.createFormData("file", file.getName(), imgBody);

Call<ResponseBody> partDataCall = retrofit.create(Api.class).getPartData(name, filePart);

多文件上传

1
2
3
@Multipart
@POST("user/followers")
Call<ResponseBody> getPartMapData(@PartMap Map<String, MultipartBody.Part> map);

调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
File file1 = new File("文件路径");
File file2 = new File("文件路径");
if (!file1.exists()) {
file1.mkdir();
}
if (!file2.exists()) {
file2.mkdir();
}

RequestBody requestBody1 = RequestBody.create(MediaType.parse("image/png"), file1);
RequestBody requestBody2 = RequestBody.create(MediaType.parse("image/png"), file2);
MultipartBody.Part filePart1 = MultipartBody.Part.createFormData("file1", file1.getName(), requestBody1);
MultipartBody.Part filePart2 = MultipartBody.Part.createFormData("file2", file2.getName(), requestBody2);

Map<String,MultipartBody.Part> mapPart = new HashMap<>();
mapPart.put("file1",filePart1);
mapPart.put("file2",filePart2);

Call<ResponseBody> partMapDataCall = retrofit.create(Api.class).getPartMapData(mapPart);