天天看点

Retrofit-Okhttp-Okio-RxJava

先来一张Jake Wharton 关于 A Few “OK” Libraries 的简介,几个框架的关系

Retrofit-Okhttp-Okio-RxJava

前面先分析了RxJava的实现,第一感觉就是和Java 8里面的stream很像,以函数式编程的形式来处理数据,个人理解的核心思想有2个:

  • 函数接口 在函数的参数里面,定义了各种函数接口(functional interface),当需要这些参数的时候,有3种方式可以实现
    • 通过一个匿名内部类
    • 传递lambda表达式
    • 在某些情况下,传递方法引用(如System.out::println)而不是lambda表达式。
  • Builder模式 每次对数据的处理,都是生成的一个新的对象

RaJava更多的是可能是配合其它组件一起使用,如

Retrofit-Okhttp-Okio-RxJava 4个组件组合,在Android开发中基本上已经标配。在一般网络编程中,主要是处理http连接以及request(需要什么数据)、reponse(返回结果)。

HTTP 简介

HTTP请求报文 一个HTTP请求报文由请求行(request line)、请求头部(header)、空行和请求数据4个部分组成

请求行分为三个部分:请求方法、请求地址和协议版本

  1. 请求方法 HTTP/1.1 定义的请求方法有8种:GET、POST、PUT、DELETE、PATCH、HEAD、OPTIONS、TRACE。最常的两种GET和POST,如果是RESTful接口的话一般会用到GET、POST、DELETE、PUT。
  2. 请求地址 URL:统一资源定位符。 组成:<协议>://<主机>:<端口>/<路径>
  3. 协议版本:如HTTP/1.1,HTTP/2.0

请求头 为请求报文添加了一些附加信息,由“名/值”键值组成,每行一对,名和值之间使用冒号分

HTTP响应报文 主要由状态行、响应头部、空行以及响应数据组成。

状态行

由3部分组成,分别为:协议版本,状态码,状态码描述。其中协议版本与请求报文一致,状态码描述是对状态码的简单描述。

一些常用的状态码

  • 100~199:指示信息,表示请求已接收,继续处理
  • 200~299:请求成功,表示请求已被成功接收、理解
  • 300~399:重定向,要完成请求必须进行更进一步的操作
  • 400~499:客户端错误,请求有语法错误或请求无法实现
  • 500~599:服务器端错误,服务器未能实现合法的请求

响应头部 与请求头部类似,为响应报文添加了一些附加信息

几个框架的分工大概如下类图

Retrofit-Okhttp-Okio-RxJava

详细的流程图:

Retrofit-Okhttp-Okio-RxJava

其中Retrofit的实现依赖于Okhttp,Okhttp的实现依赖于Okio

另外在Retrofit里面,可以使用将返回结果转换成RxJava里面的基础类,如Flowable/Observable,充分利用RxJava响应式编程的优势,提升数据处理效率

下面简单介绍一下4个框架的主要功能

1.Retrofit

Retrofit 是一个 RESTful 的 HTTP 网络请求框架的封装。因为和网络连接的处理是由Okhttp处理的,与服务器的Socket读写是通过Okio来实现的,是在这2个框架上的进一步封装

核心功能有2个:对request的处理以及对response结果的转换处理

1.1 使用annotation来描述request

Retrofit里面定义了很多注解,使用annotation来描述一个request应该怎么被处理

  • request 方法 可以使用的注解有HTTP, GET, POST, PUT, PATCH, DELETE, OPTIONS and HEAD
  • url操作 一个请求url可以通过替换方法里面的代码块和参数来动态更新,一个替换块必须用{}包起来,且在参数里面使用同样的名字用@path注解
@GET("group/{id}/users")
Call<List<User>> groupList(@Path("id") int groupId);

// query查询也可以添加
@GET("group/{id}/users")
Call<List<User>> groupList(@Path("id") int groupId, @Query("sort") String sort);

// 对于更复杂的请求参数可以使用@QueryMap
           
  • 通过@Body注解,可以将一个对象作为request的body来使用
  • 通过Headers注解可以设置请求头
@Headers({
    "Accept: application/vnd.github.v3.full+json",
    "User-Agent: Retrofit-Sample-App"
})
@GET("users/{username}")
Call<User> getUser(@Path("username") String username);
           
1.2 数据类型转换器converters

默认情况下,Retrofit只支持把HTTP body作为Okhttp的ResponseBody类型,通过@body注解只能接收RequestBody 类型

可以使用转换器来支持其它 类型:

  • Gson: com.squareup.retrofit2:converter-gson
  • Jackson: com.squareup.retrofit2:converter-jackson
  • Moshi: com.squareup.retrofit2:converter-moshi
  • Protobuf: com.squareup.retrofit2:converter-protobuf
  • Wire: com.squareup.retrofit2:converter-wire
  • Simple XML: com.squareup.retrofit2:converter-simplexml
  • JAXB: com.squareup.retrofit2:converter-jaxb
  • Scalars (primitives, boxed, and String): com.squareup.retrofit2:converter-scalars

可以自定义converters,来处理非以上几种类型的

1.3 Call Adapter Call适配器

在retrofit里面,一般会定义一个接口,如下

interface Service {  
  @GET("/user")
  Call<User> user();
}
           

对象的转换过程:

Call <-- CallAdapter <-- Call <-- Call <-- Call.Factory (如 OkHttpClient)

通过Call.Factory的抽象,来兼容Okhtttp/RxJava等

如果想把获取的结果转换成RxJava里面可以处理的Observable,可以将接口的定义修改如下:

interface Service {
  @GET("/user")
  Observable<User> user();
}
           

然后在里面Retrofit里面配置CallAdapter为Rxjava

Retrofit retrofit = new Retrofit.Builder()

    .baseUrl("http://example.com") 
    .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) 
    .build();
           

默认情况下,create方法会创建一个响应式类型,在后台线程里面异步执行一个http请求,有2种方式可以指定运行的线程

  • 使用createSynchronous(),然后调用subscribeOn在想要运行的Scheduler上
  • 使用createWithScheduler来提供一个默认的订阅Scheduler

2.Okhttp

HTTP client主要工作是接收request,然后生成一个response

Requests

每个http请求包含一个URL,一个方法(像GET/POST),和一系列headers,可能包含有一个body:一种特定类型的数据流

Responses

Response响应request,返回一个code(像200/404),一系列headers和一个可选的header

当把一个request发送到OkHttp时, OkHttp会重写request然后再发送,OkHttp可能会增加一些headers,在原始的请求里面没有的,包括Content-Length, Transfer-Encoding, User-Agent, Host, Connection, and Content-Type

也在增加一个Accept-Encoding,除非原始request里面已经有了,当需要cookies时,OkHttp会增加一个Cookie header

有些request可能有缓存的response,当缓存不是最新的时候,OkHttp会做一个有条件的GET来下载更新,这种场景下,要求headers里面加上

If-Modified-Since
If-None-Match
           

如果传输使用了压缩,OkHttp会丢弃响应头Content-Encoding and Content-Length,因为适用于解压后的response body

rediect

当你请求的URL被移动,服务器可能会返回一个类似302的响应,来表明文档的新URL,OkHttp会rediect来获取一个最终的response

Authenticator

如果需要鉴权,OkHttp会调用Authenticator 来处理,如果Authenticator 提供了证书,请求的时候会包含此证书

重试

有时连接会失败,OkHttp会使用一个不同的路由来重试

Call

OkHttp使用Call来建模,不管需要多少中间请求和响应,来满足request

Call执行有2种方式:

  • 同步:当前线程会阻塞,直到response可读
  • 异步:把请求放在另外的线程,当response可读时使用callback来回调

Calls可以在任意线程里面取消.Call的管理

  • 对于同步调用,在单独的线程里面管理同时发出的请求数量
  • 对于异步Call,Dispatch实现了请求数量管理策略

response里面有一个string()方法,对于小文件,可以简单高效的将response body转换成string。如果response body比较大,超过1M,则避免使用string(),因为此方法会将内容全部加载到memory里面,在这种场景下,应该把respnse当作stream来处理

同步调用,直接等待response然后再处理client.newCall(request).execute().use

异步调用,client.newCall(request).enqueue(object : Callback

则传入一个callback,当reponse可用时,回调此callbase,okhttp不支持异步API把response分块处理

访问请求头

使用addHeader来增加新的header,如果存在,不需要删除,addHeader在处理的时候,如果value存在,则先remove掉,然后再添加新的value

使用header(name)来获取一个head,使用headers(name),获取所有值,作为一个List返回

EventListener

可以用来获取http call的调用数量,使用EventListener可以监听

应用内部调用Http call的大小和频率,当调用Call太多次或Call太大,应该了解这些情况

在当前网络情况下的性能,如果网络性能不够好,应该增强网络或减少调用

Interceptor

Interceptor 是一个功能强大的机制,可以监听、重写、重试Call

OkHttp 中的拦截器分为 Application Interceptor(应用拦截器) 和 NetWork Interceptor(网络拦截器)两种,支持自定义,注册方式的区别:

  • 通过调用 OkHttpClient.Builder 的 addInterceptor() 方法来注册应用拦截器
  • 通过调用OkHttpClient.Builder 的 addNetworkInterceptor() 方法来注册网络拦截器

2个可以自定义的Interceptor,5个内置的Interceptor.执行顺序和功能简介

  1. Application Interceptor:每次网络请求它只会执行一次拦截,而且它是第一个触发拦截的,这里拦截到的url请求的信息都是最原始的信息
  2. RetryAndFollowUpInterceptor 的作用,一个负责失败重连的拦截器。它是 Okhttp 内置的第一个拦截器,通过 while (true) 的死循环来进行对异常结果或者响应结果判断是否要进行重新请求。
  3. BridgeInterceptor 为用户构建的一个 Request 请求转化为能够进行网络访问的请求,同时将网络请求回来的响应 Response 转化为用户可用的 Response
  4. CacheInterceptor 根据 OkHttpClient 对象的配置以及缓存策略对请求值进行缓存
  5. ConnectInterceptor 在 OKHTTP 底层是通过 SOCKET 的方式于服务端进行连接的,并且在连接建立之后会通过 OKIO 获取通向 server 端的输入流 Source 和输出流 Sink
  6. NetWork Interceptor:它位于倒数第二层,会经过 RetryAndFollowIntercptor 进行重定向并且也会通过 BridgeInterceptor 进行 request请求头和 响应 resposne 的处理
  7. CallServerInterceptor 在 ConnectInterceptor 拦截器的功能就是负责与服务器建立 Socket 连接,并且创建了一个 HttpStream 它包括通向服务器的输入流和输出流

3.Okio

Okio是一个实现java.io和java.nio的库,更方便访问、存储和处理数据。作为OkHttp组件的一部分,在Android中引入支持HTTP的客户端

Buffer and ByteString 持有数据
  • ByteString 代表一个不可变的字节序列。对于char数据,String是基础类型。
  • Buffer 可变的字节序列,像ArrayList,读写Buffer的操作与queue类似,从尾部写,从头部读,不需要管理position/limit/capacities

在内部实现,ByteString和Buffer做了一些优化来节约CPU和内存,如果把一个UTF-8字符串编码为ByteString,保存了一个引用,后面如果需要解码的时候可以直接使用,在encodeUtf8方法里面

Buffer被实现为一个segment链表,当你从一个Buffer移动数据到另外一个Buffer的时候,重新设置了segment的所属队列,没有直接copy一份数据,在多线程场景下很有帮助,在buffer.write方法里面

class Buffer implements BufferedSink,BufferedSource
           
Source and Sink 移动数据

在okio里面,有自己的stream类型Source和Sink,使用方式类似于InputStream和OutputStream

Source对应为输入,在BufferedSource里面封装了一个InputStream

interface Source {
 long read(Buffer sink, long byteCount);
 Timeout timeout();
 void close();
}

interface BufferedSource extends Source {
 byte readByte();
 short readShort();
 short readShortLe();
 int readInt();
 int readIntLe();
 long readLong();
 long readLongLe();
 long readDecimalLong();
 long readHexadecimalUnsignedLong();
 ByteString readByteString();
 ByteString readByteString(long byteCount);
 byte[] readByteArray();
 byte[] readByteArray(long byteCount);
 int read(byte[] sink);
 void readFully(byte[] sink);
 int read(byte[] sink, int offset, int byteCount);
 void readFully(Buffer sink, long byteCount);
 long readAll(Sink sink);
 String readUtf8();
 String readUtf8(long byteCount);
 String readUtf8Line();
 String readUtf8LineStrict();
 String readString(Charset charset);
 String readString(long byteCount, Charset charset);
 long indexOf(byte b);
 long indexOf(byte b, long fromIndex);
 long indexOfElement(ByteString targetBytes);
 long indexOfElement(ByteString targetBytes, long fromIndex);
 boolean exhausted();
 void require(long byteCount);
 boolean request(long byteCount);
 void skip(long byteCount);
 Buffer buffer();
 InputStream inputStream();
}
           

Sink对应为输出,在BufferedSink里面封装了一个outputStream

interface Sink {
 void write(Buffer sink, long byteCount);
 Timeout timeout();
 void close();
void flush();
}

interface BufferedSink extends Sink {
 BufferedSink write(ByteString byteString);
 BufferedSink write(byte[] source);
 BufferedSink write(byte[] source, int offset, int byteCount);
 long writeAll(Source source);
 BufferedSink write(Source source, long byteCount);
 BufferedSink writeUtf8(String string);
 BufferedSink writeString(String string, Charset charset);
 BufferedSink writeByte(int b);
 BufferedSink writeShort(int s);
 BufferedSink writeShortLe(int s);
 BufferedSink writeInt(int i);
 BufferedSink writeIntLe(int i);
 BufferedSink writeLong(long v);
 BufferedSink writeLongLe(long v);
 BufferedSink writeDecimalLong(long v);
 BufferedSink writeHexadecimalUnsignedLong(long v);
 BufferedSink emitCompleteSegments();
 BufferedSink emit();
 Buffer buffer();
 OutputStream outputStream();
}
           

主要差异:

  • 提供了IO语义层面的超时
  • 容易实现 ,容易使用
  • 没有人为区分字符流和字节流,都作为数据处理,读写都按字节来操作
  • 容易测试

Buffer 内部保存的数据在Segment Pool里面

BufferedSource and BufferedSink 更高效的移动数据,具体的细节,可以参考前面pdf里面的文档,后面会专门针对几个框架来分析实现