在上一篇我们已经学习了OkHttp的使用,接下来我们再来看看与OkHttp配套使用的Retrofit。
定义:基于OkHttp的网络请求封装框架
什么意思呢?其实Retrofit只是对我们传递的参数通过注解的方式来进行封装,实际的网络请求工作依旧是由OkHttp来完成的。
优点:
(1)功能强大,不仅支持同步与异步,还支持多种数据格式解析。
(2)支持RxJava,实现线程调度。
(3)简洁易用,通过注解的方式配置网络请求参数。
(4)扩展性能好,功能模块高度解耦。
在Retrofit的使用过程中,有一个很重要的知识点就是关于注解的使用,因为Retrofit框架的本质就是通过注解的方式对OkHttp的请求参数进行封装,对返回的结果进行封装的一个框架,所以我们很有必要先来了解一下注解。
注解:
首先来看一张图
注解的类型分为三类:
第一类:网络请求方法
(1)@GET、@POST、@PUT、@DELETE、@HEAD。以上方法分别对应 HTTP中的网络请求方式。以GET请求方法为例:
fun retrofitGet(view: View) {
// 1.创建Retrofit的实例
val retrofit = Retrofit.Builder()
.baseUrl("http://www.baidu.com/") // 设置请求的完整域名
.build()
// 2.创建请求接口的实例
val apiService = retrofit.create(ApiService::class.java)
// 3.封装请求
val call = apiService.getRequest("more")
// 4.执行请求操作
call.enqueue(object : Callback<ResponseBody> {
override fun onResponse(call: Call<ResponseBody>, response: Response<ResponseBody>) {
Log.i("retrofit", response.body().toString())
Toast.makeText(this@NetWorkRetrofitActivity,response.body().toString(),Toast.LENGTH_SHORT).show()
}
override fun onFailure(call: Call<ResponseBody>, e: Throwable) {
Log.i("retrofit", "请求失败${e.printStackTrace()}")
}
})
}
public interface ApiService {
@GET("{path}/")
Call<ResponseBody> getRequest(@Path("path") String path);
}
Retrofit把网络请求的URL 分成了两部分设置,第一个部分是设置的baseUrl,即http://www.baidu.com/,而另外一部分则是在此基础上进行拼接组成的,通过注解的方式进行替换,所以上面的例子中最终的URL为:http://www.baidu.com/more。
(2)@HTTP
· 作用:替换@GET、@POST、@PUT、@DELETE、@HEAD注解的作用 及 更多功能拓展
· 具体使用:通过属性method、path、hasBody进行设置
@HTTP(method = "GET", path = "{path}/", hasBody = false)
Call<ResponseBody> getHttpRequest(@Path("path") String path);
第二类:标记类
(1)@FormUrlEncoded表示请求体是一个Form表单,以键值对的形式来传递参数。
// Post请求
@FormUrlEncoded
@POST("{user}/")
Observable<ResponseBody> postData(@Path("user") String user,@FieldMap Map<String, String> maps, @Query("meta[]") String... linked);
// 执行Post请求(包含数组)
@Override
public void mHttpPost(Context context,String api, TreeMap map, String[] data, int type, HttpRequestCallback mCallBack) {
map = HttpTool.getTreeCrc(map);
Observable<String> observable = apiManager.postData(api,map, data);
observable.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(new BaseObserver(context, mCallBack, type));
}
(2)@Multipart表示请求体是一个支持文件的Form表单。
// 上传单个文件
@Multipart
@POST("{user}/")
Observable<String> upload(@Path("user") String user,@PartMap Map<String, RequestBody> maps, @Part MultipartBody.Part file);
@Override
public void mHttpFile(Context context,String api, File file, TreeMap map, int type, HttpRequestCallback mCallBack) {
// 生成单个文件
RequestBody requestFile = RequestBody.create(MediaType.parse("multipart/form-data"), file);
MultipartBody.Part body = MultipartBody.Part.createFormData("avatar", file.getName(), requestFile);
// 将所有的字段进行转换
map = HttpTool.getTreeCrc(map);
Map<String, RequestBody> mapValue = new HashMap<>();
for (Object key : map.keySet()) {
mapValue.put(key.toString(), HttpTool.convertToRequestBody(map.get(key).toString()));
}
Observable<String> observable = apiManager.upload(api,mapValue, body);
observable.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(new BaseObserver(context, mCallBack, type));
}
(3)@Streaming表示返回的数据以流的形式进行返回,比如下载大的文件等等都可以采用这种方式来实现,举一个例子:
public interface DownloadService {
@Streaming
@GET
Call<ResponseBody> download(@Url String url);
}
public static void download(String url, final String path, final DownloadListener downloadListener) {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("[<u>http://www.xxx.com</u>](http://www.xxx.com)")
//通过线程池获取一个线程,指定callback在子线程中运行。
.callbackExecutor(Executors.newSingleThreadExecutor())
.build();
DownloadService service = retrofit.create(DownloadService.class);
Call<ResponseBody> call = service.download(url);
call.enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(@NonNull Call<ResponseBody> call, @NonNull final Response<ResponseBody> response) {
//将Response写入到从磁盘中,详见下面分析
//注意,这个方法是运行在子线程中的
writeResponseToDisk(path, response, downloadListener);
}
@Override
public void onFailure(@NonNull Call<ResponseBody> call, @NonNull Throwable throwable) {
downloadListener.onFail("网络错误~");
}
});
}
第三类:网络请求参数
@Header & @Headers
作用:添加请求头 &添加不固定的请求头
具体使用如下:
// @Header
@GET("user")
Call<User> getUser(@Header("Authorization") String authorization)
// @Headers
@Headers("Authorization: authorization")
@GET("user")
Call<User> getUser()
// 以上的效果是一致的。
// 区别在于使用场景和使用方式
// 1. 使用场景:@Header用于添加不固定的请求头,@Headers用于添加固定的请求头
// 2. 使用方式:@Header作用于方法的参数;@Headers作用于方法
当然,我们还可以通过拦截器的方式去添加我们的头文件,比如:
public class HttpHeaderInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Request original = chain.request();
int userId = IOFactoryUtil.getIOFactoryUtil().getDefaultHandler().getInt("user_id", 0);
Log.i("zhoufan",userId+"");
if (userId > 0) {
Request request = original.newBuilder()
.header("userID", String.valueOf(userId))
.build();
return chain.proceed(request);
}
return chain.proceed(original);
}
}
@Body
作用:以 Post方式 传递 自定义数据类型 给服务器
特别注意:如果提交的是一个Map,那么作用相当于 @Field
不过Map要经过 FormBody.Builder 类处理成为符合 Okhttp 格式的表单,如:
FormBody.Builder builder = new FormBody.Builder();
builder.add("key","value");
@Field & @FieldMap
作用:发送 Post请求 时提交请求的表单字段
具体使用:与 @FormUrlEncoded 注解配合使用
@FormUrlEncoded
@POST("{user}/")
Observable<ResponseBody> postData(@Path("user") String user,@FieldMap Map<String, String> maps, @Query("meta[]") String... linked);
@Part & @PartMap
作用:发送 Post请求 时提交请求的表单字段
与@Field的区别:功能相同,但携带的参数类型更加丰富,包括数据流,所以适用于 有文件上传 的场景
具体使用:与 @Multipart 注解配合使用
@Multipart
@POST("{user}/")
Observable<String> upload(@Path("user") String user,@PartMap Map<String, RequestBody> maps, @Part MultipartBody.Part file);
@Query和@QueryMap
作用:用于 @GET 方法的查询参数(Query = Url 中 ‘?’ 后面的 key-value)
@GET("{user}/")
Observable<String> getData(@Path("user") String user,@QueryMap TreeMap<String, Object> map);
@Path
作用:URL地址的缺省值
@Url
作用:直接传入一个请求的 URL变量 用于URL设置
具体使用:
@GET
Observable<String> testUrlAndQuery(@Url String url, @Query("showAll") boolean showAll);
// 当有URL注解时,@GET传入的URL就可以省略
最后总结一下:
创建Retrofit的实例:
val retrofit = Retrofit.Builder()
.baseUrl("http://www.baidu.com/") // 设置请求的完整域名
.addConverterFactory(GsonConverterFactory.create()) // 设置数据解析器
.addCallAdapterFactory(RxJava2CallAdapterFactory.create()) // 支持RxJava平台
.build()
a. 关于数据解析器(Converter)
Retrofit支持多种数据解析方式
使用时需要在Gradle添加依赖
Scalars com.squareup.retrofit2:converter-scalars:2.0.2
Gson com.squareup.retrofit2:converter-gson:2.0.2
通常情况下我们选择的是这两种,其中Scalars代表的是基础数据类型的解析,而Gson代表的是Gson格式的解析。
b. 关于网络请求适配器(CallAdapter)
Retrofit支持多种网络请求适配器方式:guava、Java8和rxjava,使用时如使用的是 Android 默认的 CallAdapter,则不需要添加网络请求适配器的依赖,否则则需要按照需求进行添加Retrofit 提供的 CallAdapter,一般情况下我们选择RxJava的适配器。
完整的请求例子:
(1)添加依赖
(2)添加网络权限
(3)创建Retrofit实例
(4)创建用于描述网络请求的接口
(5)对发送请求进行封装
(6)发送请求
(7)对返回数据进行解析
第一步:添加依赖
// okHttp网络请求框架
// define a BOM and its version
implementation(platform("com.squareup.okhttp3:okhttp-bom:4.9.1"))
// define any required OkHttp artifacts without version
implementation("com.squareup.okhttp3:okhttp")
implementation("com.squareup.okhttp3:logging-interceptor")
//retrofit网络请求框架
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
//retrofit添加Json解析返回数据
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
//retrofit添加RxJava支持
implementation 'com.jakewharton.retrofit:retrofit2-rxjava2-adapter:1.0.0'
第二步:添加网络权限
<uses-permission android:name="android.permission.INTERNET"/>
剩余步骤:
fun retrofitGet(view: View) {
// 3.创建Retrofit的实例
val retrofit = Retrofit.Builder()
.baseUrl("http://www.baidu.com/") // 设置请求的完整域名
.addConverterFactory(GsonConverterFactory.create()) // 设置数据解析器
.addCallAdapterFactory(RxJava2CallAdapterFactory.create()) // 支持RxJava平台
.build()
// 4.创建请求接口的实例
val apiService = retrofit.create(ApiService::class.java)
// 5.封装请求
val call = apiService.getHttpRequest("more")
// 6.执行请求操作
call.enqueue(object : Callback<ResponseBody> {
override fun onResponse(call: Call<ResponseBody>, response: Response<ResponseBody>) {
// 7.解析返回数据
Log.i("retrofit", response.body().toString())
Toast.makeText(this@NetWorkRetrofitActivity,response.body().toString(),Toast.LENGTH_SHORT).show()
}
override fun onFailure(call: Call<ResponseBody>, e: Throwable) {
Log.i("retrofit", "请求失败${e.printStackTrace()}")
}
})
}
注意:在设置数据转换器的时候,通常情况下不能设置多个,因为无法保证后台返回的数据类型同时满足所有的转换,一般情况下我们会使用Gson作为数据转换的类型或者使用Scalars作为数据转换的类型,如果我们需要对返回数据进行特殊处理,那么可以考虑自定义数据转换器。