前言
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); }
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);
手动解析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