众所周知,目前市面上最流行的网络请求的方式莫过于Retrofit+OkHttp+RxJava.
首先我们要知道每个框架的作用:
Retrofit: Retrofit 仅负责 网络请求接口的封装
OkHttp:负责请求的过程
RxJava: 负责异步,各种线程之间的切换。
下面我们逐一介绍各个框架的使用,以及组合起来的使用:
一、Retrofit
流程图如下:
1、上图说明了如下几点:
- App应用程序通过 Retrofit 请求网络,实际上是使用 Retrofit 接口层封装请求参数、Header、Url 等信息,之后由 OkHttp 完成后续的请求操作。
- 在服务端返回数据之后,OkHttp 将原始的结果交给 Retrofit,Retrofit根据用户的需求对结果进行解析。所以网络请求的本质仍旧是OkHttp完成的,retrofit只是帮使用者来进行工作简化的,比如配置网络,理数据等工作,提高这一系列操作的复用性。这也就是网上流行的一个不太准确的总结:okhttp是瑞士军刀,retrofit则是将这把瑞士军刀包装成了一个非常好用的指甲钳。
2、Retrofit 对Okhttp做了什么?
Retrofit并没有改变网络请求的本质,也无需改变,因为Okhttp已经足够强大,Retrofit的封装可以说是很强大,里面涉及到一堆的设计模式,可以通过注解直接配置请求,可以使用不同的http客户端,虽然默认是用http ,可以使用不同Json Converter 来序列化数据,同时提供对RxJava的支持,使用Retrofit + OkHttp + RxJava 可以说是目前比较
潮的一套框架,但是需要有比较高的门槛。
3、下面我们来看一下Retrofit的具体使用:
1、导入依赖: module/build.gradle下
dependencies {
// Retrofit库
compile 'com.squareup.retrofit2:retrofit:2.0.2'
// Okhttp库
compile 'com.squareup.okhttp3:okhttp:3.1.2'
}
2、添加网络权限:\src\main\AndroidManifest.xml
<uses-permission android:name="android.permission.INTERNET"/>
3、创建用于描述网络请求的接口
Retrofit将Http请求抽象成java接口:采用注解描述网络请求和配置网络请求参数。
1.用动态代理 动态 将该接口的注解“翻译”成一个 Http 请求,最后再执行 Http 请求
2.接口中的每个方法的参数都需要使用注解标注,否则会报错
注解类型:
2.1.网络请求方法:@GET 、@POST 、@PUT、 @DELETE、 @PATH 、@HEAD 、@OPTIONS 、@HTTP
2.2.标记类:@FormUrlEncoded、 @Multipart 、@Streaming
2.3.网络请求参数:@Header 、@hraders 、@URL、 @Body、 @Path 、@Field、 @FieldMap 、@Part 、@PartMap 、@Query 、@QueryMap
3.1、网络请求方法
@GET、@POST、@PUT、@DELETE、@HEAD
以上方法分别对应 HTTP中的网络请求方式:Retrofit把网络请求的URL分成了两部分
第一部分:在网络请求接口的注解设置
@GET("openapi.do?name=jack&pwd=123456")
fun login() : Call<Reception>
//@GET注解的作用:采用Get方法发送网络请求
//getCall() = 接收网络请求数据的方法
//其中返回类型为Call<*>,*是接收数据的类(此类可以自定义)
第二部分:在创建Retrofit实例时通过.baseUrl()设置
Retrofit.Builder()
.baseUrl("https://guz.retroft.com/")
.addConverterFactory(GsonConverterFactory.create())//设置数据解析器
.build()
// 从上面看出:一个请求的URL可以通过 替换块 和 请求方法的参数 来进行动态的URL更新。
// 替换块是由 被{}包裹起来的字符串构成
// 即:Retrofit支持动态改变网络请求根目录
网络请求的完整Url = 在创建Retrofit实例时通过baseUrl设置 + 3网络请求接口的注解设置
@HTTP
作用:替换@GET、@POST、@PUT、@DELETE、@HEAD注解及更多功能的拓展。
具体使用:通过属性method,path,hasBody进行设置。
/**
* method:网络请求的方法(区分大小写)
* path:网络请求地址路径
* hasBody:是否有请求体
*/
@HTTP(method = "GET", path = "blog/{id}", hasBody = false)
Call<ResponseBody> getCall(@Path("id") int id);
// {id} 表示是一个变量
// method 的值 retrofit 不会做处理,所以要自行保证准确
3.2、标记
a. @FromUrlEncoded
发送form-encoded的数据,每个键值对需要用@Filed来注解键名,随后的对象需要提供值。
b.@Multipart
发送form-encoded的数据(适用于有文件上传的场景),每个键值对需要用@Part来注解键名,随后的对象需要提供值。
具体使用:
public interface GetRequest_Interface {
/**
*表明是一个表单格式的请求(Content-Type:application/x-www-form-urlencoded)
* <code>Field("username")</code> 表示将后面的 <code>String name</code> 中name的取值作为 username 的值
*/
@POST("/form")
@FormUrlEncoded
Call<ResponseBody> testFormUrlEncoded1(@Field("username") String name, @Field("age") int age);
/**
* {@link Part} 后面支持三种类型,{@link RequestBody}、{@link okhttp3.MultipartBody.Part} 、任意类型
* 除 {@link okhttp3.MultipartBody.Part} 以外,其它类型都必须带上表单字段({@link okhttp3.MultipartBody.Part} 中已经包含了表单字段的信息),
*/
@POST("/form")
@Multipart
Call<ResponseBody> testFileUpload1(@Part("name") RequestBody name, @Part("age") RequestBody age, @Part MultipartBody.Part file);
}
// 具体使用
GetRequest_Interface service = retrofit.create(GetRequest_Interface.class);
// @FormUrlEncoded
Call<ResponseBody> call1 = service.testFormUrlEncoded1("Carson", 24);
// @Multipart
RequestBody name = RequestBody.create(textType, "Carson");
RequestBody age = RequestBody.create(textType, "24");
MultipartBody.Part filePart = MultipartBody.Part.createFormData("file", "test.txt", file);
Call<ResponseBody> call3 = service.testFileUpload1(name, age, filePart);
3.3、网络请求参数
a. @Headers
作用:添加请求头 &添加不固定的请求头
//@Header
@GET("user")
Call<user> getUser(@Header("Authorization") String authorization)
// @Headers
@Headers("Authorization: authorization")
@GET("user")
Call<User> getUser()
b.@Body
作用:以 Post方式 传递 自定义数据类型 给服务器
特别注意:如果提交的是一个Map,那么作用相当于 @Field
不过Map要经过 FormBody.Builder 类处理成为符合 Okhttp 格式的表单,如:
FormBody.Builder builder = new FormBody.Builder();
builder.add("key","value");
给大家举个例子,看一下,body的用法:
@POST("api/demo/login")
fun getPersonInfo(@Body requestBody: RequestBody): Observable<Response<LoginBean>>
这个地方参数为RequestBody,所以后面的入参我们得想办法转换成questBody的形式
c. @Field & @FieldMap
作用:发送Post请求时提交请求的表单字段
使用:与 @FormUrlEncoded 注解配合使用
public interface GetRequest_Interface {
/**
*表明是一个表单格式的请求(Content-Type:application/x-www-form-urlencoded)
* <code>Field("username")</code> 表示将后面的 <code>String name</code> 中name的取值作为 username 的值
*/
@POST("/form")
@FormUrlEncoded
Call<ResponseBody> testFormUrlEncoded1(@Field("username") String name, @Field("age") int age);
/**
* Map的key作为表单的键
*/
@POST("/form")
@FormUrlEncoded
Call<ResponseBody> testFormUrlEncoded2(@FieldMap Map<String, Object> map);
}
// 具体使用
// @Field
Call<ResponseBody> call1 = service.testFormUrlEncoded1("Carson", 24);
// @FieldMap
// 实现的效果与上面相同,但要传入Map
Map<String, Object> map = new HashMap<>();
map.put("username", "Carson");
map.put("age", 24);
Call<ResponseBody> call2 = service.testFormUrlEncoded2(map);
d.@Part & @PartMap
1.作用:发送 Post请求时提交请求的表单字段
2.与@Field的区别:功能相同,但携带的参数类型更加丰富,包括数据流,所以适用于有文件上传的场景
3.使用:与 @Multipart 注解配合使用
public interface GetRequest_Interface {
/**
* {@link Part} 后面支持三种类型,{@link RequestBody}、{@link okhttp3.MultipartBody.Part} 、任意类型
* 除 {@link okhttp3.MultipartBody.Part} 以外,其它类型都必须带上表单字段({@link okhttp3.MultipartBody.Part} 中已经包含了表单字段的信息),
*/
@POST("/form")
@Multipart
Call<ResponseBody> testFileUpload1(@Part("name") RequestBody name, @Part("age") RequestBody age, @Part MultipartBody.Part file);
/**
* PartMap 注解支持一个Map作为参数,支持 {@link RequestBody } 类型,
* 如果有其它的类型,会被{@link retrofit2.Converter}转换,如后面会介绍的 使用{@link com.google.gson.Gson} 的 {@link retrofit2.converter.gson.GsonRequestBodyConverter}
* 所以{@link MultipartBody.Part} 就不适用了,所以文件只能用<b> @Part MultipartBody.Part </b>
*/
@POST("/form")
@Multipart
Call<ResponseBody> testFileUpload2(@PartMap Map<String, RequestBody> args, @Part MultipartBody.Part file);
@POST("/form")
@Multipart
Call<ResponseBody> testFileUpload3(@PartMap Map<String, RequestBody> args);
}
// 具体使用 :https://javajgs.com/archives/75280
MediaType textType = MediaType.parse("text/plain");
RequestBody name = RequestBody.create(textType, "Carson");
RequestBody age = RequestBody.create(textType, "24");
RequestBody file = RequestBody.create(MediaType.parse("application/octet-stream"), "这里是模拟文件的内容");
// @Part
MultipartBody.Part filePart = MultipartBody.Part.createFormData("file", "test.txt", file);
Call<ResponseBody> call3 = service.testFileUpload1(name, age, filePart);
ResponseBodyPrinter.printResponseBody(call3);
// @PartMap
// 实现和上面同样的效果
Map<String, RequestBody> fileUpload2Args = new HashMap<>();
fileUpload2Args.put("name", name);
fileUpload2Args.put("age", age);
//这里并不会被当成文件,因为没有文件名(包含在Content-Disposition请求头中),但上面的 filePart 有
//fileUpload2Args.put("file", file);
Call<ResponseBody> call4 = service.testFileUpload2(fileUpload2Args, filePart); //单独处理文件
ResponseBodyPrinter.printResponseBody(call4);
}
e. @Query和@QueryMap
1.作用:用于@GET 方法的查询参数(Query = Url 中 ‘?’ 后面的 key-value)
如:url = http://www.println.net/?cate=android,其中,Query = cate
2.使用:配置时只需要在接口方法中增加一个参数即可
@GET("/")
Call<String> cate(@Query("cate") String cate);
}
// 其使用方式同 @Field与@FieldMap,这里不作过多描述
f. @Path
作用:URL地址的缺省值
使用:
public interface GetRequest_Interface {
@GET("users/{user}/repos")
Call<ResponseBody> getBlog(@Path("user") String user );
// 访问的API是:https://api.github.com/users/{user}/repos
// 在发起请求时, {user} 会被替换为方法的第一个参数 user(被@Path注解作用)
}
g. @Url
作用:直接传入一个请求的 URL变量 用于URL设置
使用:
public interface GetRequest_Interface {
@GET
Call<ResponseBody> testUrlAndQuery(@Url String url, @Query("showAll") boolean showAll);
// 当有URL注解时,@GET传入的URL就可以省略
// 当GET、POST...HTTP等方法中没有设置Url时,则必须使用 {@link Url}提供
}
4、创建Retrofit实例
var retrofit : Retrofit? = null
init{
val build = OkHttpClient.Builder().build()
retrofit = Retrofit.Builder()
.baseUrl(AppConfig.BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.client(build)
.build()
}
//创建单例模式,来获取retrofit
companion object{
private val retrofit : RetrofitTest by lazy {
RetrofitTest()
}
//单例模式获取接口实例
fun<T> createService(service:Class<T>) : T? {
return retrofit.retrofit?.create(service)
}
}
a.关于数据解析器(Converter)
1.Retrofit支持多种数据解析方式
2.使用时需要在Gradle添加依赖
数据解析器:Gson,Jackson,Simple XML,Protobuf,Moshi ,Wire ,Scalars
b.关于网络请求适配器(CallAdapter)
Retrofit支持多种网络请求适配器方式:guava、Java8和rxjava
使用时如使用的是 Android 默认的 CallAdapter,则不需要添加网络请求适配器的依赖,否则需要按照需求进行添加Retrofit 提供的 CallAdapter
网络请求适配器:guava,java8,rxjava
5、创建网络请求接口实例
interface TestService {
@GET
fun login(@Body requestBody: RequestBody) : Call<Reception>
}
//创建接口实例化
val createService = RetrofitTest.createService(TestService::class.java)
//创建接口实例化
val createService = RetrofitTest.createService(TestService::class.java)
//对 发送请求 进行封装
var myCall = createService?.login1()
6、发送网络请求
//发送网络请求,并处理请求后的结果
myCall?.enqueue(object : Callback<Reception>{
override fun onResponse(
call: retrofit2.Call<Reception>,
response: Response<Reception>
) {
TODO("Not yet implemented")
}
override fun onFailure(call: retrofit2.Call<Reception>, t: Throwable) {
TODO("Not yet implemented")
}
})
}
二、OkHttp
概况详解:
OkHttp主要是负责请求过程的,其实就是一个请求的客户端,所以要配置一个OkHttpClient.
OkHttpClient支持HTTP/2,允许所有同一个主机地址的请求共享同一个socket连接
连接池减少请求延时
透明的GZIP压缩减少响应数据的大小
缓存响应内容,避免一些完全重复的请求
在这里就不过多讲解OkHttp的使用,主要讲一下怎样配合Retrifit的使用,:
//******** 一、 创建 httpClient 对象***********
OkHttpClient httpClient = new OkHttpClient.Builder()
.connectTimeout(CONNECT_TIMEOUT, TimeUnit.SECONDS) //链接超时时间
.readTimeout(READ_TIMEOUT, TimeUnit.SECONDS) //读取超时时间
.writeTimeout(WRITE_TIMEOUT, TimeUnit.SECONDS) //上传超时时间
.addInterceptor(logging)//设置需要打印的log
.addInterceptor(headerInterceptor()) //设置头部拦截器
.build();
//打印请求log
val logging = HttpLoggingInterceptor()
logging.level = if (BuildConfig.DEBUG) {
HttpLoggingInterceptor.Level.BODY
} else {
HttpLoggingInterceptor.Level.NONE
}
/**
* 设置拦截器 :[https://www.cnblogs.com/Robin132929/p/12151889.html]
*/
private fun headerInterceptor() : Interceptor{
return Interceptor { chain ->
var request = chain.request()
//TODO 根据项目适配调整
request = request.newBuilder()
.addHeader("key","value")
.build()
chain.proceed(request)
}
}
//其实在retrofit中使用很简单,只需要配置一个客户端即可,代码如下:
retrofit = Retrofit.Builder()
“.client(httpClient)”
.build()
三、RxJava && RxAndroid
一、RxJava是什么?
Rx(Reactive Extensions)是一个库,用来处理【事件】和【异步】任务,在很多语言上都有实现,RxJava是Rx在Java上的实现。简单来说,RxJava就是处理异步的一个库,最基本是基于观察者模式来实现的。通过Obserable和Observer的机制,实现所谓响应式的编程体验。它扩展了观察者模式以支持数据/事件序列,并添加了运算符,使您可以声明性地将序列组合在一起,同时抽象化了对低级线程,同步,线程安全和并发数据结构等问题的关注。 2、RxAndroid是基于RxJava适用于Android的封装;官方给的这句大概会让你明白适配封装了啥:More specifically, it provides a Scheduler that schedules on the main thread or any given Looper.(更具体地说,它提供了一个调度程序,在主线程或任何给定的循环机上进行调度)
二、概念
1.Observable 被观察者 ,事件源。是一个抽象类。
------ 1.1 ObservableEmitter 发射器;
2.Observer 观察者,事件接收处理。是一个接口。
3.subscribe 订阅,把被观察者和观察者关联起来。
三、基础使用
1.集成依赖
build.gradle中dependencies 下依赖;代码如下(示例):
dependencies {
implementation "io.reactivex.rxjava3:rxjava:3.0.12"
implementation 'io.reactivex.rxjava3:rxjava:3.0.0'
}
2.用法及分析
fun main(){
RxJavaAndroid.doRxJava()
}
object RxJavaAndroid {
fun doRxJava() {
//通过 Observable.create 创建被观察者,Observable是一个抽象类
val observalbe = Observable.create(ObservableOnSubscribe<String> { emitter ->
//发射器发送消息
emitter.onNext("hello world");
//通过发射器发射异常
//emitter.onError(new Throwable("模拟一个异常"));
//发射完成
emitter.onComplete();
})
//通过 new Observer 创建观察者;Observer是一个 interface
val observer : Observer<String> = object : Observer<String>{
override fun onSubscribe(d: Disposable) {
//第一个执行的方法
println("onSubscribe")
}
override fun onNext(t: String) {
println("onNext>>>$t")
}
override fun onError(e: Throwable) {
println("onError>>>${e.message}")
}
override fun onComplete() {
println("onComplete")
}
}
//通过被观察者 Observable 的 subscribe (订阅) 绑定观察者 Observer
observalbe.subscribe(observer)
}
}
运行结果如下:
3、RxJava 流程总结
Observable.create 创建事件源,但并不生产也不发射事件。 2.实现 observer 接口,但此时没有也无法接受到任何发射来的事件。 3.订阅observable.subscribe(observer), , 此时会调用具体 Observable 的实现类中的 subscribeActual 方法, 此时会才会真正触发事件源生产事件,事件源生产出来的事件通过 Emitter 的 的 onNext ,onError ,onComplete 发射给 observer 对应的方法由下游 observer 消费掉。从而完成整个事件流的处理。 4.observer 中的 onSubscribe 在订阅时即被调用,并传回了 Disposable, observer 中可以利用 Disposable 来随时中断事件流的发射。
4、RxJava 的线程调度机制
这里用rxjava的Builder建造者模式来书写代码。
/**
* 建造者模式构建RxJava 流式API
*/
fun doRxJava2(){
Observable.create(ObservableOnSubscribe<String> {
println("我在这里发射文字-这是运行的线程:--${Thread.currentThread().name}")
it.onNext("相遇就是一辈子!!!")
}).subscribe(object : Observer<String>{
override fun onSubscribe(d: Disposable) {
}
override fun onNext(t: String) {
println("我在这里接收到的文字-这是接收到文字运行的线程:--${Thread.currentThread().name}")
}
override fun onError(e: Throwable) {
}
override fun onComplete() {
}
})
}
运行肯定都是在main线程中,rxjava默认主线程。如下图:
当我们把.subscribeOn(Schedulers.io()) 加上后线程就会切换到子线程,执行结果如下:
由此可见RxJava是通过subscribeOn()来控制线程切换的,我们知道RxThreadFactory是用来创建线程的类。IoScheduler 创建和缓存线程池;大概就明白rxjava是通过 Scheduler (调度器)调度这些线程的就可以了。
四、RxJava+okHttp+Retrofit
上面分别一一讲解了RxJava 、 okHttp 、 Retrofit ,接下来我们将三者的优点聚合起来编写一套现在最流行的网络框架。
1、导入依赖
相关版本号,可根据版本需要添加:
ext.kotlin_version = "1.6.0" //kotlin版本
ext.okhttp3_version = "3.8.1" //okhttp版本
ext.retrofit_version = "2.9.0" //retrofit版本
ext.rx_version = "2.0.9" //rxjava版本
ext.ra_version = "2.1.1" //rxjava版本
//Retrofit
implementation "com.squareup.okhttp3:logging-interceptor:${okhttp3_version}"
implementation "com.squareup.retrofit2:retrofit:${retrofit_version}"
implementation "com.squareup.retrofit2:converter-gson:${retrofit_version}"
implementation "com.squareup.retrofit2:adapter-rxjava2:${retrofit_version}"
//RxJava
implementation "io.reactivex.rxjava2:rxjava:${rx_version}"
2、建立retrofit工厂类,并且加上我们需要的一些拦截,日志等。
class RetrofitFactory {
val retrofit : Retrofit = TODO()
init {
//打印请求log
// setlevel用来设置日志打印的级别,共包括了四个级别:NONE,BASIC,HEADER,BODY
// BASEIC:请求/响应行
// HEADER:请求/响应行 + 头
// BODY:请求/响应行 + 头 + 体
val logging = HttpLoggingInterceptor()
logging.level = if (BuildConfig.DEBUG) {
HttpLoggingInterceptor.Level.BODY
} else {
HttpLoggingInterceptor.Level.NONE
}
val okHttpclient = OkHttpClient.Builder()
.addInterceptor(logging)
.addInterceptor(headerInterceptor())
.build()
retrofit = Retrofit.Builder()
.baseUrl(AppConfig.BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.client(okHttpclient)
.build()
}
/**
* 拦截头部
*/
private fun headerInterceptor() : Interceptor{
return Interceptor { chain ->
var request = chain.request()
request = request.newBuilder()
.addHeader("key","value")
.build()
chain.proceed(request)
}
}
companion object{
// Double check
private val instances : RetrofitFactory by lazy {
RetrofitFactory()
}
fun <T> createService(service :Class<T>) : T {
return instances.retrofit.create(service)
}
/**
* 在RetrofitFactory文件中加入针对Observable的扩展函数
*/
fun <T> Observable<Response<T>>.executeResult(subscriber: ResultObserver<T>){
this.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(subscriber)
}
}
}
我们使用lazy委托的方式实现单例,见委托。这样我们在每一次想要获取到retrofit接口时,直接调用RetrofitFactory.createService(apiInterface)即可。
3、引入了Rxjava
先自定义一个全局网络请求的Observer,封装网络请求的各种情况,成功,失败,完成等等。
abstract class ResultObserver<T> : Observer<Response<T>>{
override fun onSubscribe(d: Disposable) {
if (!d.isDisposed){
onRequestStart()
}
}
override fun onNext(t: Response<T>) {
onResquestEnd()
if (t.isSuccessful){
try {
onSuccess(t.body())
}catch (e : Exception){
e.printStackTrace()
}
}else{
try {
onBusinessFail(t.code(),t.message())
}catch (e : Exception){
e.printStackTrace()
}
}
}
override fun onError(e: Throwable) {
onResquestEnd()
if (e is ConnectException
|| e is TimeoutException
|| e is NetworkErrorException
|| e is UnknownHostException){
onFailure(e,true)
}else{
onFailure(e,false)
}
}
override fun onComplete() {
TODO("Not yet implemented")
}
/**
* 请求开始
*/
open fun onRequestStart(){}
/**
* 请求结束
*/
open fun onResquestEnd(){}
/**
* 返回成功
*/
@Throws(Exception::class)
abstract fun onSuccess(requst : T?)
/**
* 返回失败
*/
@Throws(Exception::class)
abstract fun onFailure(e : Throwable,isNetWorkError : Boolean)
/**
* 业务错误:->返回成功了,但是code错误
*/
@Throws(Exception::class)
open fun onBusinessFail(code:Int,message : String) {
}
}
首先是继承Observer,复写四个接口方法:onSubscribe
onNext
onError``onComplete。然后加入了使用需要复写的请求结果业务方法:成功onSuccess,失败onFailure。以及给出了一些定制化选项:开始请求onRequestStart,请求结束onRequestEnd,业务错误onBusinessFail
4、调接口请求网络
RetrofitFactory.createService(UserService::class.java).getPersonInfo()
.executeResult(object : ResultObserver<User>(){
override fun onSuccess(requst: User?) {
TODO("Not yet implemented")
}
override fun onFailure(e: Throwable, isNetWorkError: Boolean) {
TODO("Not yet implemented")
}
})