Retrofit介绍
Retrofit框架: 它是Square公司开发的现在非常流行的网络框架
retrofit2.0它依赖于OkHttp,在这里我们也不需要显示的导入okHttp,在retrofit中已经导入okhttp3 性能好,处理快,使用简单,Retrofit 是安卓上最流行的HTTP Client库之一。
准确来说,网络请求的工作本质上是OkHttp完成,而 Retrofit 仅负责网络请求接口的封装。它的一个特点是包含了特别多注解,方便简化你的代码量。并且还支持很多的开源库(著名例子:Retrofit + RxJava)。Retrofit和OkHttp都是square公司写的.
Retrofit官网
github地址
Retrofit的优点
Retrofit依赖
implementation 'com.squareup.retrofit2:retrofit:2.9.0' //gson转换器 implementation 'com.squareup.retrofit2:converter-gson:2.9.0' //rxjava转换器 implementation 'com.squareup.retrofit2:adapter-rxjava2:2.9.0' //string转换器 implementation 'com.squareup.retrofit2:converter-scalars:2.9.0'
Retrofit使用步骤
入门案例:get无参异步请求
测试Url:
https://www.wanandroid.com/banner/json
ApiService
public interface ApiService {
/ * get无参请求 * https://www.wanandroid.com/banner/json */ @GET("banner/json") Call<ResponseBody> getBanner(); }
public class MainActivity extends AppCompatActivity {
public static final String BASE_URL = "https://www.wanandroid.com/"; @Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //1.创建Retrofit对象 Retrofit retrofit = new Retrofit.Builder() .baseUrl(BASE_URL) .build(); //2.通过Retrofit实例创建接口服务对象 ApiService apiService = retrofit.create(ApiService.class); //3.接口服务对象调用接口中方法,获得Call对象 Call<ResponseBody> call = apiService.getBanner(); //4.Call对象执行请求(异步、同步请求) //同步请求:不常用,一般使用异步请求 //Response
execute = call.execute();
//异步请求 call.enqueue(new Callback<ResponseBody>() {
@Override public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
//onResponse方法是运行在主线程也就是UI线程的,所以我们可以在这里直接更新ui if (response.isSuccessful()) {
try {
String result = response.body().string(); Log.e("xyh", "onResponse: " + result); } catch (IOException e) {
e.printStackTrace(); } } } @Override public void onFailure(Call<ResponseBody> call, Throwable t) {
//onFailure方法是运行在主线程也就是UI线程的,所以我们可以在这里直接更新ui Toast.makeText(MainActivity.this, t.getMessage(), Toast.LENGTH_SHORT).show(); } }); //call.cancel(); //取消 } }
请求结果:
2020-10-21 14:58:05.494 9204-9204/com.xyh.retrofit E/xyh: onResponse: {
"data":[{
"desc":"享学~","id":29,"imagePath":"https://wanandroid.com/blogimgs/0-1d86-4eb5-8012-b3fd6945cbb1.jpeg","isVisible":1,"order":0,"title":"【Android开发进阶教程】热修复架构方案原理详解与项目实战","type":0,"url":"https://www.bilibili.com/video/BV1f54y1k77e"},{
"desc":"","id":6,"imagePath":"https://www.wanandroid.com/blogimgs/62c1bd68-b5f3-4a3c-a649-7ca8c7dfabe6.png","isVisible":1,"order":1,"title":"我们新增了一个常用导航Tab~","type":1,"url":"https://www.wanandroid.com/navi"},{
"desc":"一起来做个App吧","id":10,"imagePath":"https://www.wanandroid.com/blogimgs/50c115c2-cf6c-4802-aa7b-a4334de444cd.png","isVisible":1,"order":1,"title":"一起来做个App吧","type":1,"url":"https://www.wanandroid.com/blog/show/2"},{
"desc":"","id":20,"imagePath":"https://www.wanandroid.com/blogimgs/90c6cc12-742e-4c9f-b318-b912f163b8d0.png","isVisible":1,"order":2,"title":"flutter 中文社区 ","type":1,"url":"https://flutter.cn/"}],"errorCode":0,"errorMsg":""}
Retrofit注解
retrofit通过使用注解来简化请求,大体分为以下几类:
- 用于标注请求方式的注解
- 用于标记请求头的注解
- 用于标记请求参数的注解
- 用于标记请求和响应格式的注解




Retrofit2 的baseUlr 必须以 /(斜线) 结束
创建Retrofit实例时需要通过Retrofit.Builder,并调用baseUrl方法设置URL。
Retrofit2 的baseUlr 必须以 /(斜线) 结束,不然会抛出一个IllegalArgumentException,所以如果你看到别的教程没有以 / 结束,那么多半是直接从Retrofit 1.X 照搬过来的。
有些特殊情况可以不以/结尾,比如 其实这个 URL https://www.baidu.com?key=value用来作为baseUrl其实是可行的,因为这个URL隐含的路径就是 /(斜线,代表根目录) ,而后面的?key=value在拼装请求时会被丢掉所以写上也没用。之所以 Retrofit 2 在文档上要求必须以 /(斜线) 结尾的要求想必是要消除歧义以及简化规则。
get有参请求
/ * get有参请求 * http://qt..com/php_cgi/news/php/varcache_getnews.php?id=12&page=0&plat=android&version=9724 */ @GET("news/php/varcache_getnews.php") Call<ResponseBody> getNewsInfo(@Query("id") String id, @Query("page") String page, @Query("plat") String plat, @Query("version") String version);
public class MainActivity extends AppCompatActivity {
public static final String BASE_URL = "http://qt..com/php_cgi/"; @Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Retrofit retrofit = new Retrofit.Builder() .baseUrl(BASE_URL) .build(); retrofit.create(ApiService.class) .getNewsInfo("12", "0", "android", "9724") .enqueue(new Callback<ResponseBody>() {
@Override public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
if (response.isSuccessful()) {
try {
String string = response.body().string(); Log.e("xyh", "onResponse: " + string); } catch (IOException e) {
e.printStackTrace(); } } } @Override public void onFailure(Call<ResponseBody> call, Throwable t) {
Log.e("xyh", "onFailure: " + t.getMessage()); } }); } }
@QueryMap注解
@QueryMap:参数太多时可以用@QueryMap封装参数,相当于多个@Query
/ * @QueryMap:参数太多时可以用@QueryMap封装参数,相当于多个@Query * * http://qt..com/php_cgi/news/php/varcache_getnews.php?id=12&page=0&plat=android&version=9724 */ @GET("news/php/varcache_getnews.php") Call<ResponseBody> getNewsInfo(@QueryMap Map<String, String> map);
Map<String, String> map = new HashMap<>(); map.put("id", "12"); map.put("page", "0"); map.put("plat", "android"); map.put("version", "9724"); Call<ResponseBody> call = apiService.getNewsInfo(map);
gosn转换器:直接返回对象
默认情况下Retrofit只支持将HTTP的响应体转换换为ResponseBody,而Converter是Retrofit为我们提供用于将ResponseBody转换为我们想要的类型。
通过GsonConverterFactory为Retrofit添加Gson支持
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
/ * gson转换器 * https://www.wanandroid.com/article/list/0/json?cid=60 */ @GET("article/list/0/json") Call<BaseEntity<ArticleBean>> getArticleData(@Query("cid") int id);
public class MainActivity extends AppCompatActivity {
public static final String BASE_URL = "https://www.wanandroid.com/"; @Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //1.创建Retrofit对象 Retrofit retrofit = new Retrofit.Builder() .baseUrl(BASE_URL) .addConverterFactory(GsonConverterFactory.create()) //gson转换器 .build(); ApiService apiService = retrofit.create(ApiService.class); Call<BaseEntity<ArticleBean>> call = apiService.getArticleData(60); call.enqueue(new Callback<BaseEntity<ArticleBean>>() {
@Override public void onResponse(Call<BaseEntity<ArticleBean>> call, Response<BaseEntity<ArticleBean>> response) {
BaseEntity<ArticleBean> entity = response.body(); Log.e("xyh", "onResponse: "+entity.getData().toString() ); } @Override public void onFailure(Call<BaseEntity<ArticleBean>> call, Throwable t) {
} }); } }
@Path :用于url中的占位符,所有在网址中的参数(URL的问号前面的参数)
/ * @Path :用于url中的占位符,所有在网址中的参数(URL的问号前面的参数) * https://www.wanandroid.com/article/list/0/json?cid=60 */ @GET("article/list/{page}/json") Call<BaseEntity<ArticleBean>> getArticleData(@Path("page") int page, @Query("cid") int id);
Call<BaseEntity<ArticleBean>> call = apiService.getArticleData(0,60);
POST请求
表单提交
FormUrlEncoded:表示请求实体是一个Form表单,每个键值对需要使用@Field注解
/ * post请求 * FormUrlEncoded:表示请求实体是一个Form表单,每个键值对需要使用@Field注解 * 该接口是get请求,只是为了演示post请求的用法 * http://qt..com/php_cgi/news/php/varcache_getnews.php?id=12&page=0&plat=android&version=9724 */ @FormUrlEncoded @POST("news/php/varcache_getnews.php") Call<ResponseBody> getGameInfo(@Field("id") String id, @Field("page") String page, @Field("plat") String plat, @Field("version") String version);
FieldMap
多个参数时可以使用,类型@QueryMap
@FormUrlEncoded @POST("news/php/varcache_getnews.php") Call<ResponseBody> getGameInfo(@FieldMap Map<String, String> map);
body注解:上传json数据
方式1:使用RequestBody
@POST("news/php/varcache_getnews.php") Call<ResponseBody> getNewsInfoByBody(@Body RequestBody Body);
String json = ""; RequestBody body = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), json); retrofit.create(ApiService.class) .getNewsInfoByBody(body) .enqueue(new Callback<ResponseBody>() {
@Override public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
} @Override public void onFailure(Call<ResponseBody> call, Throwable t) {
} });
方式2:
直接传入实体,它会自行转化成Json,这个转化方式是GsonConverterFactory定义的。
/ * 直接传入实体,它会自行转化成Json,这个转化方式是GsonConverterFactory定义的。 * @param bean * @return */ @POST("news/php/varcache_getnews.php") Call<ResponseBody> getNewsInfoByBody(@Body ParmasBean bean);
Retrofit retrofit = new Retrofit.Builder() .baseUrl(BASE_URL) .addConverterFactory(GsonConverterFactory.create()) //gson转换器 .build(); ApiService apiService = retrofit.create(ApiService.class); ParmasBean parmasBean=new ParmasBean(); Call<ResponseBody> call = apiService.getNewsInfoByBody(parmasBean);
方式3:使用Map集合
上传json格式的数据,也可以使用Map集合,加上body注解。
@POST("news/php/varcache_getnews.php") Call<ResponseBody> getNewsInfoByBody(@Body Map<String, Object> map);
Map<String, Object> parmas = new HashMap<>(); parmas.put("alipay_account", "xx"); parmas.put("real_name", "xx"); Call<ResponseBody> call = apiService.getNewsInfoByBody(parmas);
@Url:指定请求路径
若需要重新定义接口地址,可以使用@Url,将地址以参数的形式传入即可。
/ * 若需要重新定义接口地址,可以使用@Url,将地址以参数的形式传入即可。 * * @param url * @param map * @return */ @GET Call<List<Activity>> getActivityList(@Url String url, @QueryMap Map<String, String> map);
@Headers注解
添加请求头,用于添加固定请求头,可以同时添加多个。通过该注解添加的请求头不会相互覆盖,而是共同存在。
/ * 使用@Headers添加多个请求头 * 用于添加固定请求头,可以同时添加多个。通过该注解添加的请求头不会相互覆盖,而是共同存在 * * @param url * @param map * @return */ @Headers({
"User-Agent:android", "apikey:", "Content-Type:application/json", }) @POST() Call<BaseEntity<NewsInfo>> post(@Url String url, @QueryMap Map<String, String> map);
@Header注解
作为方法的参数传入,用于添加不固定值的Header,该注解会更新已有的请求头。
/ * @Header注解: * 作为方法的参数传入,用于添加不固定值的Header,该注解会更新已有的请求头 * * @param token * @param activeId * @return */ @GET("mobile/active") Call<BaseEntity<NewsInfo>> get(@Header("token") String token, @Query("id") int activeId);
@HTTP注解
/ * 11.@HTTP注解: * method 表示请求的方法,区分大小写 * path表示路径 * hasBody表示是否有请求体 */ @HTTP(method = "GET", path = "blog/{id}", hasBody = false) Call<ResponseBody> getBlog(@Path("id") int id);
Streaming注解
表示响应体的数据用流的方式返回,适用于返回的数据比较大,该注解在在下载大文件的特别有用。
/ * 12.Streaming注解:表示响应体的数据用流的方式返回,适用于返回的数据比较大,该注解在在下载大文件的特别有用 */ @Streaming @GET Call<BaseEntity<NewsInfo>> downloadPicture(@Url String fileUrl);
上传单张图片
///上传单张图片// / * Multipart:表示请求实体是一个支持文件上传的Form表单,需要配合使用@Part,适用于 有文件 上传的场景 * Part:用于表单字段,Part和PartMap与Multipart注解结合使用,适合文件上传的情况 * PartMap:用于表单字段,默认接受的类型是Map
,可用于实现多文件上传 * Part 后面支持三种类型,{@link RequestBody}、{@link okhttp3.MultipartBody.Part} 、任意类型; * * @param file 服务器指定的上传图片的key值 * @return */
@Multipart @POST("upload/upload") Call<BaseEntity<NewsInfo>> upload1(@Part("file" + "\";filename=\"" + "test.png") RequestBody file); @Multipart @POST("xxxxx") Call<BaseEntity<NewsInfo>> upload2(@Part MultipartBody.Part file); private void upLoadImage1() {
Retrofit retrofit = new Retrofit.Builder() .baseUrl("") .addConverterFactory(GsonConverterFactory.create()) //gson转换器 .build(); File file = new File(""); RequestBody requestBody = RequestBody.create(MediaType.parse("image/png"), file); retrofit.create(ApiService.class) .upload1(requestBody) .enqueue(new Callback<BaseEntity<NewsInfo>>() {
@Override public void onResponse(Call<BaseEntity<NewsInfo>> call, Response<BaseEntity<NewsInfo>> response) {
} @Override public void onFailure(Call<BaseEntity<NewsInfo>> call, Throwable t) {
} }); } private void upLoadImage2() {
Retrofit retrofit = new Retrofit.Builder() .baseUrl("") .addConverterFactory(GsonConverterFactory.create()) //gson转换器 .build(); File file = new File(""); RequestBody photoRequestBody = RequestBody.create(MediaType.parse("image/png"), file); MultipartBody.Part photo = MultipartBody.Part.createFormData("上传的key", file.getName(), photoRequestBody); retrofit.create(ApiService.class) .upload2(photo) .enqueue(new Callback<BaseEntity<NewsInfo>>() {
@Override public void onResponse(Call<BaseEntity<NewsInfo>> call, Response<BaseEntity<NewsInfo>> response) {
} @Override public void onFailure(Call<BaseEntity<NewsInfo>> call, Throwable t) {
} }); }
上传多张图片
//上传多张图片/ / * @param map * @return */ @Multipart @POST("upload/upload") Call<BaseEntity<NewsInfo>> upload3(@PartMap Map<String, RequestBody> map); @Multipart @POST("upload/upload") Call<BaseEntity<NewsInfo>> upload4(@PartMap Map<String, MultipartBody.Part> map); private void upLoadImage3() {
Retrofit retrofit = new Retrofit.Builder() .baseUrl("") .addConverterFactory(GsonConverterFactory.create()) //gson转换器 .build(); //图片集合 List<File> files = new ArrayList<>(); Map<String, RequestBody> map = new HashMap<>(); for (int i = 0; i < files.size(); i++) {
RequestBody requestBody = RequestBody.create(MediaType.parse("image/png"), files.get(i)); map.put("file" + i + "\";filename=\"" + files.get(i).getName(), requestBody); } retrofit.create(ApiService.class) .upload3(map) .enqueue(new Callback<BaseEntity<NewsInfo>>() {
@Override public void onResponse(Call<BaseEntity<NewsInfo>> call, Response<BaseEntity<NewsInfo>> response) {
} @Override public void onFailure(Call<BaseEntity<NewsInfo>> call, Throwable t) {
} }); } private void upLoadImage4() {
Retrofit retrofit = new Retrofit.Builder() .baseUrl("") .addConverterFactory(GsonConverterFactory.create()) //gson转换器 .build(); Map<String, MultipartBody.Part> map = new HashMap<>(); File file1 = new File(""); RequestBody photoRequestBody = RequestBody.create(MediaType.parse("image/png"), file1); MultipartBody.Part photo1 = MultipartBody.Part.createFormData("上传的key1", file1.getName(), photoRequestBody); map.put("上传的key1", photo1); File file2 = new File(""); RequestBody photoRequestBody2 = RequestBody.create(MediaType.parse("image/png"), file2); MultipartBody.Part photo2 = MultipartBody.Part.createFormData("上传的key2", file2.getName(), photoRequestBody2); map.put("上传的key2", photo2); retrofit.create(ApiService.class) .upload4(map) .enqueue(new Callback<BaseEntity<NewsInfo>>() {
@Override public void onResponse(Call<BaseEntity<NewsInfo>> call, Response<BaseEntity<NewsInfo>> response) {
} @Override public void onFailure(Call<BaseEntity<NewsInfo>> call, Throwable t) {
} }); }
图文混传
//图文混传/ / * @param params * @param files * @return */ @Multipart @POST("upload/upload") Call<BaseEntity<NewsInfo>> upload5(@FieldMap() Map<String, String> params, @PartMap() Map<String, RequestBody> files); / * Part 后面支持三种类型,{@link RequestBody}、{@link okhttp3.MultipartBody.Part} 、任意类型; * * @param userName * @param passWord * @param file * @return */ @Multipart @POST("xxxxx") Call<BaseEntity<NewsInfo>> upload6(@Part("username") RequestBody userName, @Part("password") RequestBody passWord, @Part MultipartBody.Part file); private void upload6() {
Retrofit retrofit = new Retrofit.Builder() .baseUrl("") .addConverterFactory(GsonConverterFactory.create()) //gson转换器 .build(); //RequestBody startTowerId = RequestBody.create(MediaType.parse("multipart/form-data"), "xx"); MediaType textType = MediaType.parse("text/plain"); RequestBody name = RequestBody.create(textType, "二傻子"); RequestBody pass = RequestBody.create(textType, ""); File file = new File(""); RequestBody photoRequestBody = RequestBody.create(MediaType.parse("image/png"), file); MultipartBody.Part photo = MultipartBody.Part.createFormData("上传的key", file.getName(), photoRequestBody); //multipart/form-data : 需要在表单中进行文件上传时,就需要使用该格式 // RequestBody requestFile = RequestBody.create(MediaType.parse("multipart/form-data"), file); // MultipartBody.Part body = MultipartBody.Part.createFormData("image", file.getName(), requestFile); // // String descriptionString = "hello, 这是文件描述"; // RequestBody description = RequestBody.create(MediaType.parse("multipart/form-data"), descriptionString); retrofit.create(ApiService.class) .upload6(name, pass, photo) .enqueue(new Callback<BaseEntity<NewsInfo>>() {
@Override public void onResponse(Call<BaseEntity<NewsInfo>> call, Response<BaseEntity<NewsInfo>> response) {
} @Override public void onFailure(Call<BaseEntity<NewsInfo>> call, Throwable t) {
} }); }
和rxjava结合使用
implementation "io.reactivex.rxjava2:rxjava:2.1.1" implementation 'io.reactivex.rxjava2:rxandroid:2.0.2'
/ * https://www.wanandroid.com/article/list/0/json?cid=60 */ @GET("article/list/{page}/json") Observable<BaseEntity<ArticleBean>> getArticleData(@Path("page") int page, @Query("cid") int id);
public class MainActivity extends AppCompatActivity {
public static final String BASE_URL = "https://www.wanandroid.com/"; @Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Retrofit retrofit = new Retrofit.Builder() .baseUrl(BASE_URL) .addConverterFactory(GsonConverterFactory.create()) //gson转换器 .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) //RxJava .build(); ApiService apiService = retrofit.create(ApiService.class); apiService .getArticleData(0, 60) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Observer<BaseEntity<ArticleBean>>() {
@Override public void onSubscribe(@NonNull Disposable d) {
} @Override public void onNext(@NonNull BaseEntity<ArticleBean> articleBeanBaseEntity) {
Log.e("xyh", "onNext: " + articleBeanBaseEntity.getData().toString()); } @Override public void onError(@NonNull Throwable e) {
} @Override public void onComplete() {
} }); } }
Retrofit请求直接返回string
在默认情况下Retrofit只支持将HTTP的响应体转换换为ResponseBody,这也是为什么我在前面的例子接口的返回值都是 Call,但如果响应体只是支持转换为ResponseBody的话何必要引入泛型呢,返回值直接用一个Call就行了嘛,既然支持泛型,那说明泛型参数可以是其它类型的,而Converter就是Retrofit为我们提供用于将ResponseBody转换为我们想要的类型。
string转换器:
implementation 'com.squareup.retrofit2:converter-scalars:2.9.0'
/ * 不加转化器类型必须是ResponseBody * https://www.wanandroid.com/article/list/0/json?cid=60 */ @GET("article/list/{page}/json") Call<ResponseBody> getArticleData1(@Path("page") int page, @Query("cid") int id); / * gson转换器:GsonConverterFactory.create() * https://www.wanandroid.com/article/list/0/json?cid=60 */ @GET("article/list/{page}/json") Call<BaseEntity<ArticleBean>> getArticleData2(@Path("page") int page, @Query("cid") int id); / * 字符串转换器:ScalarsConverterFactory.create() * https://www.wanandroid.com/article/list/0/json?cid=60 */ @GET("article/list/{page}/json") Call<String> getArticleData3(@Path("page") int page, @Query("cid") int id);
public class MainActivity extends AppCompatActivity {
public static final String BASE_URL = "https://www.wanandroid.com/"; @Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Retrofit retrofit = new Retrofit.Builder() .baseUrl(BASE_URL) .addConverterFactory(ScalarsConverterFactory.create()) //字符串转换器,这个要写在gson转换器前面,不然不起作用 .addConverterFactory(GsonConverterFactory.create()) //gson转换器 .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) //RxJava .build(); ApiService apiService = retrofit.create(ApiService.class); apiService.getArticleData1(0, 60).enqueue(new Callback<ResponseBody>() {
@Override public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
try {
Log.e("xyh", "onResponse1: " + response.body().string()); } catch (IOException e) {
e.printStackTrace(); } } @Override public void onFailure(Call<ResponseBody> call, Throwable t) {
Log.e("xyh", "onFailure1: "+t.getMessage() ); } }); apiService.getArticleData2(0,60).enqueue(new Callback<BaseEntity<ArticleBean>>() {
@Override public void onResponse(Call<BaseEntity<ArticleBean>> call, Response<BaseEntity<ArticleBean>> response) {
Log.e("xyh", "onResponse2: " + response.body().getData().toString()); } @Override public void onFailure(Call<BaseEntity<ArticleBean>> call, Throwable t) {
Log.e("xyh", "onFailure2: "+t.getMessage() ); } }); apiService.getArticleData3(0, 60).enqueue(new Callback<String>() {
@Override public void onResponse(Call<String> call, Response<String> response) {
Log.e("xyh", "onResponse3: " + response.body()); } @Override public void onFailure(Call<String> call, Throwable t) {
Log.e("xyh", "onFailure3: "+t.getMessage() ); } }); } }
自定义一个转换器,把请求到的数据转换成字符串
Retrofit retrofit = new Retrofit.Builder() .baseUrl(BASE_URL) .addConverterFactory(new ToStringConverterFactory()) //字符串转换器,这个要写在gson转换器前面,不然不起作用 .addConverterFactory(GsonConverterFactory.create()) //gson转换器 .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) //RxJava .build();
/ * 定义一个转换器,把请求到的数据转换成字符串 */ private class ToStringConverterFactory extends Converter.Factory {
private final MediaType MEDIA_TYPE = MediaType.parse("text/plain"); @Override public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
if (String.class.equals(type)) {
return new Converter<ResponseBody, String>() {
@Override public String convert(ResponseBody value) throws IOException {
return value.string(); } }; } return null; } @Override public Converter<?, RequestBody> requestBodyConverter(Type type, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
if (String.class.equals(type)) {
return new Converter<String, RequestBody>() {
@Override public RequestBody convert(String value) throws IOException {
return RequestBody.create(MEDIA_TYPE, value); } }; } return null; } }
Retrofit数据转换器(Converter)
在默认情况下Retrofit只支持将HTTP的响应体转换换为ResponseBody, 这也是什么我在前面的例子接口的返回值都是 Call, 但如果响应体只是支持转换为ResponseBody的话何必要引用泛型呢, 返回值直接用一个Call就行了嘛,既然支持泛型,那说明泛型参数可以是其它类型的, 而Converter就是Retrofit为我们提供用于将ResponseBody转换为我们想要的类型.
转换器就是把服务器返回的json格式数据,多做了一步处理,转换成你希望的类型.
转换器可以被添加到支持其他类型。提供了方便适应流行的串行化库, Retrofit 提供了六兄弟模块如下: Gson: com.squareup.retrofit:converter-gson Jackson: com.squareup.retrofit:converter-jackson Moshi: com.squareup.retrofit:converter-moshi Protobuf: com.squareup.retrofit:converter-protobuf Wire: com.squareup.retrofit:converter-wire Simple XML: com.squareup.retrofit:converter-simplexml
Retrofit封装
/ * Created by : xiaoyehai * description :Retrofit的封装 */ public class RetrofitManager {
private static RetrofitManager mInstance; private final Retrofit mRetrofit; private RetrofitManager() {
mRetrofit = new Retrofit.Builder() .baseUrl(ConstantUrls.BASE_URL) .addConverterFactory(GsonConverterFactory.create()) .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .client(getOkhttpClient()) .build(); } public static RetrofitManager getInstance() {
if (mInstance == null) {
synchronized (RetrofitManager.class) {
if (mInstance == null) {
mInstance = new RetrofitManager(); } } } return mInstance; } private OkHttpClient getOkhttpClient() {
OkHttpClient.Builder builder = new OkHttpClient.Builder(); if (BuildConfig.DEBUG) {
builder.addInterceptor(getHttpLoggingInterceptor()); //日志拦截器 } return builder .addInterceptor(getInterceptor()) //通用拦截器 .connectTimeout(20, TimeUnit.SECONDS) //设置连接超时时间 .readTimeout(20, TimeUnit.SECONDS) //设置读取超时时间 .retryOnConnectionFailure(true) .build(); } / * 日志拦截器 * * @return */ private Interceptor getHttpLoggingInterceptor() {
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor(); interceptor.setLevel(HttpLoggingInterceptor.Level.BODY); return interceptor; } / * 通用拦截器 * 根据自己项目的需求添加公共的请求头和请求参数 * * @return */ private Interceptor getInterceptor() {
Interceptor interceptor = new Interceptor() {
@Override public Response intercept(Chain chain) throws IOException {
Request request = chain.request() .newBuilder() .addHeader("token", "xxx") .addHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8") .addHeader("Accept-Encoding", "gzip, deflate") .addHeader("Connection", "keep-alive") .addHeader("Accept", "*/*") .addHeader("Cookie", "add cookies here") .build(); return chain.proceed(request); } }; return interceptor; } public <T> T create(Class<T> clazz) {
return mRetrofit.create(clazz); } }
使用retrofit做为网络请求时,解决多个BaseURL切换的问题
项目中使用Retrofit进行请求时,后台接口的域名有多个:
public static final String BASE_URL_APP = "https://app.tjinzhu.com/"; public static final String BASE_URL_H5 = "https://res.tjinzhu.com/"; public static final String BASE_URL_MAT = "https://mat.tjinzhu.com/"; public static final String BASE_URL_LOGIN = "https://login.tjinzhu.com/"; public static final String BASE_URL_HOME_MGI = "https://mgi.sitezt.cn/"; public static final String BASE_URL_HOME_MGAPP = "https://mgapp.sitezt.cn/";
在service代码中添加@Headers():
//默认baseurl @GET("api/tjz/v1/tao/assets") Observable<BaseResp<UserAssets>> getUserAssets(@Header("token") String token); @Headers({
"baseurl:mat"}) @GET("api/tjzadmin/v1/appver/new") Observable<BaseResp<AppVersionResp>> getAppVersionInfo(); @Headers({
"baseurl:homeapp"}) @GET("api/info/item/getdetailpics") Observable<List<GoodsDetailPicInfo>> getDetailpics(@Query("itemId") String itemId);
添加okhttpclient拦截器,捕获添加的Headers,然后修改baseURL
/ * Cerated by xiaoyehai * Create date : 2020/4/3 11:17 * description : okhttpclient拦截器,捕获添加的Headers,然后修改baseURL */ public class BaseUrlInterceptor implements Interceptor {
@Override public Response intercept(Chain chain) throws IOException {
//获取request Request request = chain.request(); //从request中获取原有的HttpUrl实例oldHttpUrl HttpUrl oldHttpUrl = request.url(); //获取request的创建者builder Request.Builder builder = request.newBuilder(); //从request中获取headers,通过给定的键url_name List<String> headerValues = request.headers("baseurl"); if (headerValues != null && headerValues.size() > 0) {
//如果有这个header,先将配置的header删除,因此header仅用作app和okhttp之间使用 builder.removeHeader("baseurl"); //匹配获得新的BaseUrl String headerValue = headerValues.get(0); HttpUrl newBaseUrl = null; if ("game".equals(headerValue)) {
newBaseUrl = HttpUrl.parse(ConstantUrls.BASE_URL_LOGIN); } else if ("homeapp".equals(headerValue)) {
newBaseUrl = HttpUrl.parse(ConstantUrls.BASE_URL_HOME_MGAPP); } else if ("homeagi".equals(headerValue)) {
newBaseUrl = HttpUrl.parse(ConstantUrls.BASE_URL_HOME_MGI); } else if ("mat".equals(headerValue)) {
newBaseUrl = HttpUrl.parse(ConstantUrls.BASE_URL_MAT); } else {
newBaseUrl = oldHttpUrl; } //重建新的HttpUrl,修改需要修改的url部分 HttpUrl newFullUrl = oldHttpUrl .newBuilder() .scheme("https")//更换网络协议 .host(newBaseUrl.host())//更换主机名 .port(newBaseUrl.port())//更换端口 //.removePathSegment(0)//移除第一个参数(根据baseurl移除相关参数) //.removePathSegment(1)//移除第二个参数(根据baseurl移除相关参数) //.removePathSegment(2)//移除第三个参数(根据baseurl移除相关参数) .build(); //重建这个request,通过builder.url(newFullUrl).build(); // 然后返回一个response至此结束修改 Log.e("xyh1", "intercept: " + newFullUrl.toString()); return chain.proceed(builder.url(newFullUrl).build()); } return chain.proceed(request); } }
在okhttpclient中设置
private OkHttpClient getOkhttpClient() {
OkHttpClient.Builder builder = new OkHttpClient.Builder(); if (BuildConfig.DEBUG) {
builder.addInterceptor(getHttpLoggingInterceptor()); //日志拦截器 } return builder // .addInterceptor(getInterceptor()) //通用拦截器 .addInterceptor(new BaseUrlInterceptor()) //多个baseurl动态切换 .connectTimeout(20, TimeUnit.SECONDS) //设置连接超时时间 .readTimeout(20, TimeUnit.SECONDS) //设置读取超时时间 .retryOnConnectionFailure(true) .build(); }
使用okhttp拦截器添加公共的参数和请求头
下面是我项目中的一个案例,给大家一个参考。
public class PublicHeaderInterceptor implements Interceptor {
private static final String TAG = "PublicHeaderInterceptor"; private static final String TIMES_TAMP = "timestamp"; private static final String TOUCH_ID = "touchid"; private static final String DEVICE = "device"; private static final String VERSION = "version"; private static final String TOKEN = "token"; private static final String SIGN = "sign"; private static final String KEY = "key"; private static final String KEY_VALUE = "base64:qC93ZPHeTNxh2SwB/DeOSb0zUwhHWHWHiU61ZDAPvdnOjkOYE="; private Gson gson; private String imeiMd5; private String androidId; public PublicHeaderInterceptor() {
gson = createGsonObj(); } @Override public Response intercept(Chain chain) throws IOException {
Request request = chain.request(); // 单位秒 10位 long timeMillis = DateUtils.getSystemTimestamp(); long time = timeMillis / 1000L; String device = "android"; String touchId = UTDevice.getUtdid(Utils.getContext()); String token = StringUtils.null2Length0(UserManager.getDefault().getLoginToken()); HttpUrl url = request.url(); // Log.e(TAG, "intercept: " + url); String sign = ""; Request.Builder builder = request.newBuilder(); // 获取参数 Map<String, String> params = new HashMap<>(10); // 获取 url 参数 List<String> urlKeys = new ArrayList<>(url.queryParameterNames()); String urlKey = ""; for (int i = 0; i < urlKeys.size(); i++) {
urlKey = urlKeys.get(i); params.put(urlKey, StringUtils.null2Length0(url.queryParameter(urlKey))); } // 获取 Body 参数 String bodyString = "{}"; if (request.body() != null) {
final Buffer buffer = new Buffer(); request.body().writeTo(buffer); bodyString = buffer.readUtf8(); } KLog.d(String.format("bodyString=%s", bodyString)); if (isJsonObject(bodyString) || isJsonArray(bodyString)) {
TreeMap<String, String> map = gson.fromJson(bodyString, new TypeToken<TreeMap<String, String>>() {
}.getType()); if (map != null) {
for (String key : map.keySet()) {
params.put(key, map.get(key)); } } } // 传入指定参数 params.put(PublicHeaderInterceptor.TIMES_TAMP, String.valueOf(time)); if (!TextUtils.isEmpty(touchId)) {
params.put(PublicHeaderInterceptor.TOUCH_ID, touchId); } params.put(PublicHeaderInterceptor.DEVICE, device); if (!TextUtils.isEmpty(token)) {
params.put(PublicHeaderInterceptor.TOKEN, token); } params.put(PublicHeaderInterceptor.KEY, KEY_VALUE); StringBuilder signStringBuilder = new StringBuilder(); List<String> keys = new ArrayList<>(params.keySet()); // 移除SIGN key keys.remove(SIGN); // 排序 Collections.sort(keys); // 排序后 拼接 for (int i = 0; i < keys.size(); i++) {
String key = keys.get(i); if (i != 0) {
signStringBuilder.append("&"); } String valueObj = params.get(key); String value = ""; if (valueObj != null) {
value = valueObj; } signStringBuilder.append(String.format("%s=%s", key, value)); } String signBefore = signStringBuilder.toString().toLowerCase(); Log.e(TAG, "signBefore: " + signBefore); // MD5加密 sign = MD5Util.md5(signBefore.getBytes()); // 在URL末尾追加签名与时间 String newUrl = url.toString(); if (!newUrl.contains("?")) {
newUrl = String.format("%s?sign=%s×tamp=%s", newUrl, sign, time); } else {
newUrl = String.format("%s&sign=%s×tamp=%s", newUrl, sign, time); } builder.url(newUrl); KLog.d("request", String.format("newUrl=%s \n md5_str=%s timeMillis=%s timeDiffForLocalAndService=%s", newUrl, signBefore, timeMillis, Constants.getTimeDiffForLocalAndService())); // --start 将验签参数加入header if (!TextUtils.isEmpty(token)) {
builder.addHeader(TOKEN, token); } builder.addHeader(PublicHeaderInterceptor.TOUCH_ID, touchId); builder.addHeader(PublicHeaderInterceptor.DEVICE, device); builder.addHeader(PublicHeaderInterceptor.VERSION, BuildConfig.VERSION_NAME); // --end // builder.addHeader("platform", "android"); builder.addHeader("v_code", BuildConfig.VERSION_CODE + ""); builder.addHeader("channel", ConfigManager.getDefault().getAppChannel()); // samsung SM-G6200 8.1.0 builder.addHeader("systemVersion", String.format("%s|%s|%s", Build.BRAND, Build.MODEL, Build.VERSION.RELEASE)); if (TextUtils.isEmpty(imeiMd5)) {
String imei = AppUtils.getImei(Utils.getContext()); if (!TextUtils.isEmpty(imei)) {
imeiMd5 = MD5Util.md5(imei.getBytes()); } } if (!TextUtils.isEmpty(imeiMd5)) {
builder.addHeader("imei", imeiMd5); } if (TextUtils.isEmpty(androidId)) {
androidId = AppUtils.getAndroidId(Utils.getContext()); } builder.addHeader("androidId", androidId); Request req = builder.build(); Log.e(TAG, "url: " + req.url()); //Log.e(TAG, "intercept3: " + req.toString()); Headers headers = req.headers(); Iterator<Pair<String, String>> iterator = headers.iterator(); while (iterator.hasNext()) {
Pair<String, String> pair = iterator.next(); Log.e(TAG, "headers: " + pair.component1() + "==" + pair.component2()); } //请求信息 return chain.proceed(builder.build()); } private boolean isJsonObject(String content) {
return !StringUtils.isEmpty(content) && (content.trim().startsWith("{") && content.trim().endsWith("}")); } private boolean isJsonArray(String content) {
return !StringUtils.isEmpty(content) && (content.trim().startsWith("[") && content.trim().endsWith("]")); } / * Gson 自动将 int 转为 double 问题解决 */ private Gson createGsonObj() {
return new GsonBuilder() .registerTypeAdapter( new TypeToken<TreeMap<String, String>>() {
}.getType(), new JsonDeserializer<TreeMap<String, String>>() {
@Override public TreeMap<String, String> deserialize( JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
TreeMap<String, String> treeMap = new TreeMap<>(); JsonObject jsonObject = json.getAsJsonObject(); Set<Map.Entry<String, JsonElement>> entrySet = jsonObject.entrySet(); for (Map.Entry<String, JsonElement> entry : entrySet) {
if (entry.getValue().isJsonArray()) {
treeMap.put(entry.getKey(), Utils.getGson().toJson(entry.getValue())); } else {
treeMap.put(entry.getKey(), entry.getValue().getAsString()); } } return treeMap; } }).create(); } }
发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/222300.html原文链接:https://javaforall.net
