前言
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("userlist.json") suspend fun getUserList(): Response<ResultListVo<UserVo>> }
|
suspend 关键字的核心作用是允许函数暂停协程的执行(而非阻塞线程),并在适当的时候恢复,它是 Kotlin 协程实现 “高效异步编程” 的基础。
Retrofit 支持用 suspend 标记接口方法,直接在协程中调用,无需回调;
请求头设置
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) } }
|
超时时间设置
1 2 3 4 5 6 7 8 9 10 11 12
| val client = OkHttpClient().newBuilder() .connectTimeout(10, TimeUnit.SECONDS) .readTimeout(20, TimeUnit.SECONDS) .writeTimeout(20, TimeUnit.SECONDS) .addInterceptor(HeaderInterceptor()) .build();
retrofit = Retrofit.Builder() .baseUrl(CommonData.apiUrl) .client(client) .addConverterFactory(GsonConverterFactory.create()) .build()
|
这三个超时时间分别是:
连接超时时间(Connect Timeout):指的是客户端连接到服务器所需的最大时间。
如果在规定的时间内未能建立连接,则会抛出 java.net.SocketTimeoutException 异常。
读取超时时间(Read Timeout):指的是客户端从服务器读取数据的最大时间。比如下载。
如果在规定的时间内未能读取到数据,则会抛出 java.net.SocketTimeoutException 异常。
写入超时时间(Write Timeout):指的是客户端向服务器发送数据的最大时间。比如上传。
如果在规定的时间内未能将数据发送完毕,则会抛出 java.net.SocketTimeoutException 异常。
建议:
上传下载和普通接口请求的超时时间分开设置。因为两个的时间差别较大。
管理类
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("userlist.json") Call<ResultListVo<UserVo>> getUserList(); }
|
创建Retrofit实例
1 2 3 4 5 6 7
| Retrofit retrofit = new Retrofit.Builder() .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 name = RequestBody.create(textType, "这里是你需要写入的文本:刘亦菲");
File file = new File("文件路径"); if (!file.exists()) { file.mkdir(); }
RequestBody imgBody = RequestBody.create(MediaType.parse("image/png"), file);
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);
|