前言
retrofit基于okhttp封装的网络请求框架,网络请求的工作本质上是 OkHttp 完成,而 retrofit 仅负责网络请求接口的封装.如果你不了解OKhttp建议你还是先了解它在来学习使用retrofit,传送门:Android 开发 框架系列 OkHttp使用详解
Retrofit优势,就是简洁易用,解耦,扩展性强,可搭配多种Json解析框架(例如Gson),另外还支持RxJava.但是,这篇博客不讲解RxJava配合使用的部分,与RxJava的配合使用将在另外一篇博客中讲解.
另外retrofit已经是封装的非常好了,作者的想的很完整,它的封装的思想十分准确而且恰到好处(堪称标准),所以不建议多此一举的再次封装retrofit. 再次封装不会让你很牛逼. 只会让你看起来更蠢。你会想封装只能说明你压根没理解或者阅读过retrofit。过度封装以后出现一个任何问题都可能出现重构灾难,那就整个项目的接口代码都要增加代码。减少重复工作只需要给2个东西做一个工具类(OkHttpClient与Retrofit.Builder()),在一些简单场景只需要将配置好的网络接口列表服务类保存好(保存到Application或者单例保存)。
Github地址
https://github.com/square/retrofit
依赖
如果你不需要使用RxJava模式,那么你只需要依赖下面2个:
implementation 'com.squareup.retrofit2:retrofit:2.6.2'
implementation 'com.squareup.retrofit2:converter-gson:2.4.0'
gson是用来解析的Json数据使用的(个人偏爱Gson),retrofit也支持其他解析工具比如fastJson
简单的Demo(异步请求)
老规矩按思维顺序讲解demo
1.创建Retrofit请求基础配置
Retrofit配置好后,你可以将它单例保存,也不可以保存。Retrofit.Builder()就是希望你根据不同的业务创建出不同的Retrofit来搭配接口服务使用。
private Retrofit mRetrofit;
private void initHttpBase(){
mRetrofit = new Retrofit.Builder()
.baseUrl("http://doclever.cn:8090/mock/5c3c6da33dce46264b24452b/")//base的网络地址 baseUrl不能为空,且强制要求必需以 / 斜杠结尾
.addConverterFactory(GsonConverterFactory.create())//使用Gson解析
.callbackExecutor(Executors.newSingleThreadExecutor())//使用单独的线程处理 (这很重要,一般网络请求如果不设置可能不会报错,但是如果是下载文件就会报错)
.build();
}
注意! base的网络地址 baseUrl不能为空,且强制要求必需以 / 斜杠结尾
2.创建数据返回后的Bean类
public class LoginBean {
private int code;
private String message;
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
2.创建一个网络请求接口
public interface HttpList {
@FormUrlEncoded //注解表示from表单 还有@Multipart 表单可供使用 当然你也可以不添加
@POST("test/login_test") //网络请求路径
Call<LoginBean> login(@Field("number") String number, @Field("password") String password); //@Field("number") 为post值的的key
}
注意,这是一个接口类. LoginBean则是数据返回后的Bean类(Retrofit会自动使用导入的Gson解析)
注意! @POST("test/login_test") 这路径最前面不能加斜杠 / ,否则它会自动裁剪路径,这样会导致你的路径错误
3.请求网络
这里的mRetrofit.create创建的接口服务,如果无改变Retrofit,也可以用一个单例类保存在Application起来全局使用。
private void postHttp(){
HttpList httpList = mRetrofit.create(HttpList.class);
Call<LoginBean> call = httpList.login("181234123", "123456");
call.enqueue(new Callback<LoginBean>() {
@Override
public void onResponse(Call<LoginBean> call, Response<LoginBean> response) {
LoginBean bean = response.body();
Log.e(TAG, "onResponse: code="+bean.getCode());
Log.e(TAG, "onResponse: message="+bean.getMessage());
}
@Override
public void onFailure(Call<LoginBean> call, Throwable t) {
Log.e(TAG, "onFailure: 网络请求失败="+t.getMessage());
}
});
}
这样,我们就完成了一个网络请求.是不是特别简单
同步请求
private void postHttp2() {
HttpList httpList = mRetrofit.create(HttpList.class);
final Call<LoginBean> call = httpList.login("181234123", "123456");
new Thread(new Runnable() { //Android主线程不能操作网络请求,所以new一个线程来操作
@Override
public void run() {
try {
Response<LoginBean> response = call.execute();//同步请求网络
LoginBean bean = response.body();
Log.e(TAG, "onResponse: code=" + bean.getCode());
Log.e(TAG, "onResponse: message=" + bean.getMessage());
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
取消网络请求
public void cancelHttp(){
HttpList httpList = mRetrofit.create(HttpList.class);
//这里贴这部分代码是告诉call是哪里来的,关键点就是这个call,当然你也可以从回调里获取
mCall = httpList.login("181234123", "123456");
mCall.cancel(); //取消请求
}
如何添加Header头
以固定数据的形式添加头信息
public interface HttpList {
@Headers({"content1:one","content2:two"})
@POST("test/logout_test")
Call<LoginBean> logout1();
}
以非固定数据的形式添加头信息
public interface HttpList {
@POST("test/logout_test")
Call<LoginBean> logout2(@Header("content") String content);
}
批量设置head头
/**
* 获取帮助列表数据 AppGetQuestAndHelp
*/
@POST("jiaiot/api/app/smarthome/token")
suspend fun helpList(
@HeaderMap headers: Map<String, String>,
@Body requestBody: RequestBody?
): Response<Result<List<HelpBean>>>
Body配置
Body一般有4个种类
- application/x-www-form-urlencoded 表单数据
- multipart/form-data 表单文件上传
- application/json 序列化JSON数据
- text/xml XML数据
框架直接提供的2个Body
public interface HttpList {
@FormUrlEncoded //application/x-www-form-urlencoded 表单body
@POST("test/login_test")
Call<LoginBean> login2(@Field("number") String number, @Field("password") String password);
@Multipart //multipart/form-data 此body支持文件上传与下载
@POST("test/login_test")
Call<LoginBean> login3(@Field("number") String number, @Field("password") String password);
}
自定义Body
其他2个就需要自定义创建了,下面举例Json Body的创建:
/**
*
* @param string 直接导入需要发送给服务器的JSON的String值
* @return
*/
public static RequestBody getRequestBody(String string) {
return RequestBody.create(MediaType.parse("application/json; charset=utf-8"), string);
}
在接口类参数需要设置为 @Body RequestBody requestBody
@POST("app/system/demo")
Observable<UpdateInfo> demo(@Body RequestBody requestBody);
表单带参上传文件的Body
http
/**
* 上传日志文件
* @return
*/
@Multipart
@POST("smarthome/upload_token")
suspend fun updateLogFile(
@HeaderMap headers: Map<String, String>,
@Part part: List<MultipartBody.Part>
): Response<Result<Any>>
请求部分
val http = Http.getInstance().tokenUploadFile<HttpList>(HttpList::class.java)
val fileBody = zipFile.asRequestBody("multipart/form-data".toMediaTypeOrNull())
val multipartBody = MultipartBody.Builder()
.addFormDataPart("biz_id", "app-log")
.addFormDataPart("file_name", zipFile.getName())
.addFormDataPart("file", zipFile.getName(),fileBody)
.build().parts
http.updateLogFile(heads, multipartBody).apply {
"上传日志结果 = $isSuccessful".logE()
if (isSuccessful) {
"${body().toString()}".logE()
} else {
}
}
手动解析Response请求返回
有时候我们不需要使用GsonConverterFactory帮我们解析body数据,我们希望自己能操作数据并且解析。比如获取下载数据的流,我们需要使用ResponseBody类(这个类是okhttp返回body类)。代码如下
public interface HttpList {
@GET("aaa/bbb/ccc/")
Call<ResponseBody> post(@Query("token") String token);
}
请求后返回的ResponseBody使用方式请参考okhttp的博客 https://www.cnblogs.com/guanxinjing/p/9708575.html
添加配置的OkHttpClient(主要使用请求超时/拦截器等功能)
上面说了retrofit是基于Okhttp开发的网络请求框架,所以它有一部分的功能依然需要使用Okhttp的方式来配置比如请求超时时间/设置拦截器等等,下面就展示一下如何添加
private void initHttpBase2() {
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.retryOnConnectionFailure(false) //在连接失败时重试
.callTimeout(30, TimeUnit.SECONDS) //呼叫超时,设置此参数为整体流程请求的超时时间
.connectTimeout(20,TimeUnit.SECONDS)//连接超时
.readTimeout(20,TimeUnit.SECONDS)//读取超时
.writeTimeout(20,TimeUnit.SECONDS)//写入超时
// .callTimeout()//呼叫超时,设置此参数为整体流程请求的超时时间
// .addInterceptor() //设置拦截器
// .authenticator() //设置认证器
// .proxy()//设置代理
.build();
mRetrofit = new Retrofit.Builder()
.client(okHttpClient)
.baseUrl("http://doclever.cn:8090/mock/5c3c6da33dce46264b24452b/")//base的网络地址
.addConverterFactory(GsonConverterFactory.create())//使用Gson解析
.callbackExecutor(Executors.newSingleThreadExecutor())
.build();
}
部分路径动态的BaseUrl
@POST("/article/query/{page}/json")
@FormUrlEncoded
Observable<DataResponse<Article>> getSearchArticles(@Path("page") int page, @Field("k") String k);
实现Url路径传参数
@Query 有值的查询名称
@GET("app/data")
Call<Result> getData(@Query("id") String id);
@GET("app/data")
Call<Result> getData(@Query("id") String... id);
@QueryName 只有值没有key的传参
@GET("app/data")
Call<Resul> getData(@QueryName String id);
@GET("app/data")
Call<Resul> getData(@QueryName String... id);
@QueryMap 用哈希集合传值
@GET("app/data")
Call<Result> getData(@QueryMap Map<String,String> map);
自定义GsonConverterFactory
下面这个自定义用于检查了token是否失效
bean
data class BaseResult<T>(var code: Int, var msg: String?, var data: T?)
GsonConverterFactory
public class CheckTokenGsonConverterFactory extends Converter.Factory {
/**
* Create an instance using a default {@link Gson} instance for conversion. Encoding to JSON and
* decoding from JSON (when no charset is specified by a header) will use UTF-8.
*/
public static CheckTokenGsonConverterFactory create() {
return create(new Gson());
}
/**
* Create an instance using {@code gson} for conversion. Encoding to JSON and decoding from JSON
* (when no charset is specified by a header) will use UTF-8.
*/
@SuppressWarnings("ConstantConditions") // Guarding public API nullability.
public static CheckTokenGsonConverterFactory create(Gson gson) {
if (gson == null) throw new NullPointerException("gson == null");
return new CheckTokenGsonConverterFactory(gson);
}
private final Gson gson;
private CheckTokenGsonConverterFactory(Gson gson) {
this.gson = gson;
}
@Override
public Converter<ResponseBody, ?> responseBodyConverter(
Type type, Annotation[] annotations, Retrofit retrofit) {
TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
return new MyGsonResponseBodyConverter<>(gson, adapter);
}
@Override
public Converter<?, RequestBody> requestBodyConverter(
Type type,
Annotation[] parameterAnnotations,
Annotation[] methodAnnotations,
Retrofit retrofit) {
TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
return new MyGsonRequestBodyConverter<>(gson, adapter);
}
static final class MyGsonRequestBodyConverter<T> implements Converter<T, RequestBody> {
private static final MediaType MEDIA_TYPE = MediaType.get("application/json; charset=UTF-8");
private static final Charset UTF_8 = Charset.forName("UTF-8");
private final Gson gson;
private final TypeAdapter<T> adapter;
MyGsonRequestBodyConverter(Gson gson, TypeAdapter<T> adapter) {
this.gson = gson;
this.adapter = adapter;
}
@Override
public RequestBody convert(T value) throws IOException {
Buffer buffer = new Buffer();
Writer writer = new OutputStreamWriter(buffer.outputStream(), UTF_8);
JsonWriter jsonWriter = gson.newJsonWriter(writer);
adapter.write(jsonWriter, value);
jsonWriter.close();
return RequestBody.create(MEDIA_TYPE, buffer.readByteString());
}
}
static final class MyGsonResponseBodyConverter <T> implements Converter<ResponseBody, T> {
private final Gson gson;
private final TypeAdapter<T> adapter;
MyGsonResponseBodyConverter(Gson gson, TypeAdapter<T> adapter) {
this.gson = gson;
this.adapter = adapter;
}
@Override
public T convert(ResponseBody value) throws IOException {
String response = value.string();
BaseResult baseResult = gson.fromJson(response, BaseResult.class);
if (baseResult.getCode() == NetCode.REQUEST_SUCCESS){
//token失效,请在这里处理token失效后返回登入页面的代码。
}
InputStream inputStream = new ByteArrayInputStream(response.getBytes());
MediaType contentType = value.contentType();
Charset charset = contentType != null ? contentType.charset(UTF_8) : UTF_8;
Reader reader = new InputStreamReader(inputStream, charset);
JsonReader jsonReader = gson.newJsonReader(reader);
try {
T result = adapter.read(jsonReader);
if (jsonReader.peek() != JsonToken.END_DOCUMENT) {
throw new JsonIOException("JSON document was not fully consumed.");
}
return result;
} finally {
value.close();
}
}
}
}
end
Response
本文来自博客园,作者:观心静 ,转载请注明原文链接:https://www.cnblogs.com/guanxinjing/p/11594249.html
本文版权归作者和博客园共有,欢迎转载,但必须给出原文链接,并保留此段声明,否则保留追究法律责任的权利。
来源:https://www.cnblogs.com/guanxinjing/p/11594249.html |