Retrofit介绍
Retrofit框架: 它是Square公司开发的现在非常流行的网络框架
retrofit2.0它依赖于OkHttp,在这里我们也不需要显示的导入okHttp,在retrofit中已经导入okhttp3 性能好,处理快,使用简单,Retrofit 是安卓上最流行的HTTP Client库之一。
准确来说,网络请求的工作本质上是OkHttp完成,而 Retrofit 仅负责网络请求接口的封装。它的一个特点是包含了特别多注解,方便简化你的代码量。并且还支持很多的开源库(著名例子:Retrofit + RxJava)。Retrofit和OkHttp都是square公司写的.
Retrofit的优点
①超级解耦
②可以配置不同HttpClient来实现网络请求
③支持同步、异步和RxJava
④可以配置不同的反序列化工具来解析数据,如:json、xml
⑤请求速度快,使用非常方便灵活
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使用步骤
1、定义一个接口(封装URL地址和数据请求)
2、实例化Retrofit
3、通过Retrofit实例创建接口服务对象
4、接口服务对象调用接口中方法,获得Call对象
5、Call对象执行请求(异步、同步请求)
入门案例: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<ResponseBody> 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/08240888-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 :表明这是get请求
@Query:用于Get中指定参数
@QueryMap 和Query使用类似,用于不确定表单参数
/**
* get有参请求
* http://qt.qq.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.qq.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.qq.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请求
@Filed: 多用于post请求中表单字段,Filed和FieldMap需要FormUrlEncoded结合使用
@FiledMap :和@Filed作用一致,用于不确定表单参数
@FormUrlEncoded:表示请求实体是一个Form表单,每个键值对需要使用@Field注解
@Body:多用于post请求发送非表单数据,比如想要以post方式传递json格式数据
表单提交
FormUrlEncoded:表示请求实体是一个Form表单,每个键值对需要使用@Field注解
/**
* post请求
* FormUrlEncoded:表示请求实体是一个Form表单,每个键值对需要使用@Field注解
* 该接口是get请求,只是为了演示post请求的用法
* http://qt.qq.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:123456789",
"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<String,REquestBody>,可用于实现多文件上传
Part 后面支持三种类型,{@link RequestBody}、{@link okhttp3.MultipartBody.Part} 、任意类型;
///上传单张图片//
/**
* Multipart:表示请求实体是一个支持文件上传的Form表单,需要配合使用@Part,适用于 有文件 上传的场景
* Part:用于表单字段,Part和PartMap与Multipart注解结合使用,适合文件上传的情况
* PartMap:用于表单字段,默认接受的类型是Map<String,REquestBody>,可用于实现多文件上传
* 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, "123456");
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();
}
}