网络七大层
标准的OSI参考模型:
- 应用层:HTTP(Pull),XMPP(Push),WebSocket
- 表现层:数据格式化,没有协议。
- 会话层:解除或建立接点的联系,没有协议。
- 传输层:传输控制协议:Socket(TCP,UDP)
- 网络层:为数据包选择路由:IP
- 数据链路层:传输有地址的帧,检测错误。
- 物理层:比特流传输数据。
TCP,UDP
区别
- TCP 是面向连接的,UDP 是面向无连接的
- TCP 是面向字节流的,UDP 是基于数据报的
- TCP 保证数据正确性,UDP 可能丢包
- TCP 保证数据顺序,UDP 不保证
- TCP传输效率相对较低,UDP传输效率高
问题:TCP 如何实现可靠传输?
所谓的可靠,就是能保证数据的正确性,无差错、不丢失、不重复、并且按序达到。
-
TCP首先采用三次握手来建立连接、四次挥手来释放连接。
-
其次TCP采用了连续ARQ协议,即自动重传请求(Automatic Repeat-reQuest)来保证数据传输的正确性;
-
使用滑动窗口协议来保证接收方能够及时处理接收到的数据;
-
进行流量控制。
-
最后TCP使用慢开始、拥塞避免、快重传、快恢复来进行拥塞控制,避免网络拥堵。
什么是三次握手和四次挥手?
我们首先明确,三次握手和四次挥手是TCP/IP协议的内容,它是属于传输层的,是网络请求在传输数据时建立连接和断开连接的方式
如何建立一个网络连接
在了解什么是三次握手之前,我们先探讨一个问题
现在有客户端client和服务端serve,我们怎么样建立一个网络连接?
最简单的当然是客户端向服务端发送一个建立连接的请求,然后爱管不管,我就当连接已经建立了,美滋滋的原地等待,等服务端给我发送数据
这样可以吗?
当然不行了兄弟
万一服务端压根就没开机,万一人家理都不理你呢
就像给妹妹写情书一样,妹妹回应你这事才有戏。你不管妹妹回不回应一个劲给人家写,还傻傻的在那等。
那不叫爱,那是X骚扰和备胎
所以我们得出建立连接的一个基本准则
“请求必须要有回应”
继续往下想。既然要有回应,那我客户端向你发个请求,等接收到服务端的回应,再发下个请求。依次进行,是不是显得非常有秩序?
不愧是你,这就是“停止等待协议”
超时重传
“停止等待”就是网络请求最简单的版本
简单就意味着问题,因为网络环境是不稳定的
比如客户端的每个请求都要等收到上个请求的回复才发送,如果上个请求因为网络波动延迟了,或者干脆丢掉了,那客户端的下个请求岂不是永远发不出去了?数据就卡在这里了?
为了解决这个问题,TCP协议里有个超时重传机制,如果长时间没收到回复就把原来的包再发一遍。还没收到再发,直到正常。
序列号
明白了网络连接建立时,“请求必须有回应”,“超时重传”,接下来我们再思考一个问题
我们知道,连接请求传输的数据是以数据包的形式传输的。也就是说,一次请求的数据是由多个数据包组成的。
如果一个数据包因为网络波动延迟了,在这延迟的期间触发了超时重传,这时候服务端就会接收到两个相同的包,服务端怎么处理呢?
处理的方法很简单。就是客户端在发送数据包的时候往里面加点“东西”,让服务端能够辨别这个包是从哪里来的
这个过程就是给数据包编号。这个号叫做数据包的序列号,这是接下来三次握手的关键
三次握手
终于到三次握手了,要不兄弟们先点个赞,换换脑子?
还是先上一个简单的前提。三次握手本质上其实是四次握手,只是把第二次握手和第三次握手合并了而已
因为建立连接是连续的 服务端在第一次握手收到客户端的请求后,要马上发送回应, 同时发送自己的请求,所以把回应和请求合并成一次请求,也就是第二次握手
为什么不是两次握手而是三次握手呢?
那么问题来了,根据上面的“请求必须要有回应”基本准则
为什么不是两次握手而是三次握手呢?
谢希仁著的《计算机网络》第四版中,讲到:
“三次握手”的目的是“为了防止已失效的连接请求报文段突然又传送到了服务端,因而产生错误”
这句话当做面试八股文背下来也没什么问题。但其实这里面跳过了一个关键原因
还记得我们说数据包为什么要有序列号吗?是为了让服务端能够辨别数据包的来源,区分是否是过时的数据包。
我们把眼光从数据包上升到一次网络连接。如果一个数据包延迟比较久,久到这次连接断开,下次连接建立,这时候服务端收到了旧连接的包,如何区分这两次连接呢?
答案是再加一个序列号
这个序列号叫初始序列号,用来标识一个连接
这样结论就很清晰了。初始序列号用来标识是否是同一个连接,数据包序列号用来标识一次连接中数据包的来源
所以客户端需要向服务端发送表示本地连接的初始序列号
慢着,你还是不懂为什么是三次握手不是两次?
哈呀,你忘了TCP协议是全双工通信吗?客户端和服务端的概念是相对的
客户端给服务端发送初始序列号是客户端建立的连接,服务端同时也可以作为客户端,也要向对方发送建立连接的初始序列号再加上之前说的握手合并,不就是三次握手了嘛
四次挥手
漫漫长征走过了一半,接下来我们看断开连接的四次挥手
其实四次挥手和三次握手差不多,不同的是第二次和第三次挥手不能合并
这是为什么呢?
前面说三次握手能合并的原因是 建立连接是连续的 ,服务端收到第一次握手请求后要马上回应同时发送请求。但因为TCP是全双工通信,客户端断开连接后服务端可能还需要发送数据。
所以客户端一次 “请求–回应” 只是断开了 客户端–>服务端 的连接,服务端过一会后才发送请求断开 服务端–>客户端 的连接
为什么要等待2MSL
这里有个小知识点,客户端在最后一次挥手发送回应后,会等待2msl(msl就是一个报文在网络上的最大存活时间)时间才完全关闭,这是为什么呢?
主动断开的一侧为A,被动断开的一侧为B。
第一个消息:A发FIN
第二个消息:B回复ACK
第三个消息:B发出FIN
此时此刻:B单方面认为自己与A达成了共识,即双方都同意关闭连接。
此时,B能释放这个TCP连接占用的内存资源吗?不能,B一定要确保A收到自己的ACK、FIN。
所以B需要静静地等待A的第四个消息的到来:
第四个消息:A发出ACK,用于确认收到B的FIN
当B接收到此消息,即认为双方达成了同步:双方都知道连接可以释放了,此时B可以安全地释放此TCP连接所占用的内存资源、端口号。
所以被动关闭的B无需任何wait time,直接释放资源。
但,A并不知道B是否接到自己的ACK,A是这么想的:
1)如果B没有收到自己的ACK,会超时重传FiN
那么A再次接到重传的FIN,会再次发送ACK
2)如果B收到自己的ACK,也不会再发任何消息,包括ACK
无论是1还是2,A都需要等待,要取这两种情况等待时间的最大值,以应对最坏的情况发生,这个最坏情况是:
去向ACK消息最大存活时间(MSL) + 来向FIN消息的最大存活时间(MSL)。
这恰恰就是2MSL( Maximum Segment Life)。
等待2MSL时间,A就可以放心地释放TCP占用的资源、端口号,此时可以使用该端口号连接任何服务器。
为何一定要等2MSL?
如果不等,释放的端口可能会重连刚断开的服务器端口,这样依然存活在网络里的老的TCP报文可能与新TCP连接报文冲突,造成数据冲突,为避免此种情况,需要耐心等待网络老的TCP连接的活跃报文全部死翘翘,2MSL时间可以满足这个需求(尽管非常保守)!
停止等待协议和自动重传请求(ARQ)
所谓停止等待协议就是每发送完一个分组就停止发送,等待对方的确认,在收到确认后再发送下一个分组。
但是在传输过程中可能出现意外,这时候就需要用到ARQ协议了,比如说:
1,B可能没有收到A发送的M1
这时候A就会有个超时计时器,当超时计时器到期时没收到B的确认报文,则A重新发送M1,因此必须保证以下几点:
- A发送完一个分组后,必须暂时保留已发送的分组副本,只有收到确认后才能清除分组副本
- 分组和确认分组都必须进行编号
- 超时计时器设置的重传时间应当比数据在分组传输的往返时间更长一些
2,A没有收到B的确认报文或确认报文迟到
发生确认丢失时,B会再一次收到A的重传分组M1,此时B会进行如下行动:
- 丢弃这个重复分组M1
- 向A发送确认报文
发生确认迟到时,B会再一次收到A的重传分组M1,A会再一次收到B的确认报文,这时候A收下并丢弃这个确认报文,并不做什么。
-
超时重传时间的确定(RTO)
TCP的发送方在规定的时间内没有收到确认就要重传已发送的报文段。但是由于TCP的下层互联网环境,发送的报文段可能只经过一个高速率的局域网,也可能经过多个低速率的网络,并且每个IP数据报所选择的路由还可能不同,因此注定超时重传时间要动态变化。TCP采用的一种自适应算法:计算加权平均往返时间(RTTs)。
超时重传时间(RTO)应略大于加权平均往返时间(RTTs)。
-
假设发送出一个报文段。设定的重传时间到了,还没有收到确认。于是重传报文段。经过了一段时间后,收到了确认报文段。
那么,如何判定收到的确认报文是对先发送的报文段的确认,还是对后来重传的报文段的确认?如果收到的确认是对重传报文段的确认,但却被源主机当成是对原来的报文段的确认,则计算出的RTTs和RTO会偏大。如此重复会导致RTO越来越长。
如果收到的确认是对原来的报文段的确认,但却被源主机当成是对重传报文段的确认,则计算出的RTTs和RTO会偏小。如此重复会导致RTO越来越短。
于是有了Karn算法,即在计算机加权平均RTTs时,只要报文段重传了,就不采用其往返时间样本。这样子得出的加权平均RTTs和RTO就较准确
停止等待协议的优点是简单,缺点是信道利用率太低。
滑动窗口协议和连续ARQ协议
为了提高信道的利用率,实际上采用了流水线传输的方案。如图:
发送方:维持着发送窗口。发送方每收到一个确认,就把发送窗口向前滑动一个分组的位置
接收方:维持着接受窗口。采用累计确认方式,即接收方不必对收到的分组逐个发送确认,而是可以在收到几个分组后,对按序到达的最后一个分组发送确认,这样就表示:到这个分组位置的所有分组都已经正确收到了。
流量控制
链接建立时,B根据自己的接受缓存大小确定窗口值大小,然后告诉A,我的窗口有多大,然后A就根据B给出的窗口值构造自己的发送窗口(发送缓存不一定比接受缓存大)。
流量控制往往是在给定的发送端和接收端之间的点对点通信量的控制,他所要做的就是抑制发送端发送数据的速率,以便使接收端来得及接收。
拥塞控制
网络拥塞就是指路由器在同一个线路中,传输过程中的流量太大,造成线路拥堵。资源总和大于可用的资源,发送端并不知道拥塞,还是继续保持速率发送,但是接收端来不及处理。
拥塞控制是一个全局性的过程,涉及到所有的主机,所有的路由器,以及与降低网络传输性能有关的所有因素。
-
慢重传(已废弃)
首先A向B发送一个分组消息,如果成功接收B的确认消息;那么就改变拥塞窗口的大小,使其变成以前的2倍,如果还是成功收到了B的确认消息,说明线路没发生拥堵,然后在扩大拥塞窗口的大小使其变为原来的2倍,依次发送这些数据分组。那他会一直以指数形式增长下去吗?答案是不会的,慢开始有一个门限的初始值,只要拥塞窗口大于或等于这个值,以后的增长就不再是以指数形式增长了,而是依次+1,这叫做“拥塞避免”。
当加到这条线路的最大值也就是发生了网络拥塞,拥塞窗口的值会重新变为1,然后再以指数的形式增长,达到新的门限值时,又变为以自增1的形式增大。
一条TCP连接有时会因等待重传计时器的超时而空闲较长的时间,慢开始和拥塞避免无法很好的解决这类问题,因此提出了快重传和快恢复的拥塞控制方法。
-
快重传和快恢复
快重传:例如A向B发送数据,A连续发送n次,然后B只发送一次确认消息,如果出现丢包后,比如发送的12345中,3丢失了,按照常规,B要等到A发送5结束后,才向A发送确认消息,但是他不会这么做,因为这样耗费时间,如果发现中间的一个数据包丢失,B会连续发送3次确认消息,“啊,3没了,请重传3!”,“啊,3没了,请重传3!”,“啊,3没了,请重传3!”。然后A就会重新再传一次3。即:如果当发送端接收到三个重复的确认ACK时,则断定分组丢失,立即重传丢失的报文段,而不必等待重传计时器超时。
快恢复:当发生了网络拥堵,A如果收到B的3个重复的确认,就会执行快重传算法,即拥塞窗口不再从1开始以指数形式增加,而是直接从新的门限值开始自增,新的门限值是执行“乘法减小”算法,把慢开始门限减半。这个过程被称为快恢复。
什么场景使用UDP
实时性要求很高,并且几乎不能容忍重传。
例如:实时音视频通信,多人动作类游戏中人物动作、位置。如果采用TCP,那么在出现丢包的时候,就可能会出现比较大的延时。
HTTP
HTTP有哪些方法?
- HTTP1.0定义了三种请求方法: GET, POST 和 HEAD方法
- HTTP1.1新增了五种请求方法:OPTIONS, PUT, DELETE, TRACE 和 CONNECT
这些方法的具体作用是什么?
- GET: 通常用于请求服务器发送某些资源
- POST: 发送数据给服务器
- PUT: 用于新增资源或者使用请求中的有效负载替换目标资源的表现形式
- DELETE: 用于删除指定的资源
- HEAD: 请求资源的头部信息, 并且这些头部与 HTTP GET 方法请求时返回的一致. 该请求方法的一个使用场景是在下载一个大文件前先获取其大小再决定是否要下载, 以此可以节约带宽资源
- OPTIONS: 用于获取目的资源所支持的通信选项
- PATCH: 用于对资源进行部分修改
- CONNECT: HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器
- TRACE: 回显服务器收到的请求,主要用于测试或诊断
GET和POST有什么区别?
- 数据传输方式不同:GET请求通过URL传输数据,而POST的数据通过请求体传输。
- 安全性不同:POST的数据因为在请求主体内,所以有一定的安全性保证,而GET的数据在URL中,通过历史记录,缓存很容易查到数据信息。
- 数据类型不同:GET只允许 ASCII 字符,而POST无限制
- GET无害: 刷新、后退等浏览器操作GET请求是无害的,POST可能重复提交表单
- 特性不同:GET是安全(这里的安全是指只读特性,就是使用这个方法不会引起服务器状态变化)且幂等(幂等的概念是指同一个请求方法执行多次和仅执行一次的效果完全相同),而POST是非安全非幂等
PUT和POST都是给服务器发送新增资源,有什么区别?
-
PUT 和POST方法的区别是,PUT方法是幂等的:连续调用一次或者多次的效果相同(无副作用),而POST方法是非幂等的。
-
通常情况下,PUT的URI指向是具体单一资源,而POST可以指向资源集合
举个例子,我们在开发一个博客系统,当我们要创建一篇文章的时候往往用POST https://www.xxx.com/articles,这个请求的语义是,在articles的资源集合下创建一篇新的文章,如果我们多次提交这个请求会创建多个文章,这是非幂等的。
而PUT https://www.xxx.com/articles/xxxxxxx的语义是更新对应文章下的资源(比如修改作者名称等),这个URI指向的就是单一资源,而且是幂等的,比如你把『刘德华』修改成『张学友』,提交多少次都是修改成『张学友』
所以『POST表示创建资源,PUT表示更新资源』这种说法是错误的,两个都能创建资源,根本区别就在于幂等性
PUT和PATCH都是给服务器发送修改资源,有什么区别?
PUT和PATCH都是更新资源,而PATCH用来对已知资源进行局部更新。
比如我们有一篇文章的地址https://www.xxx.com/articles/xxxxxx,这篇文章的可以表示为:
article = {
author: '刘德华',
creationDate: '2019-6-12',
content: '我写文章像蔡徐坤',
id: 820357430
}
当我们要修改文章的作者时,我们可以直接发送PUT https://www.xxx.com/articles/xxxxxx,这个时候的数据应该是:
{
author:'张学友',
creationDate: '2019-6-12',
content: '我写文章像蔡徐坤',
id: 820357430
}
这种直接覆盖资源的修改方式应该用put,但是你觉得每次都带有这么多无用的信息,那么可以发送PATCH https://www.xxx.com/articles/xxx,这个时候只需要:
{
author:'张学友',
}
http的请求报文是什么样的?
- 请求行
- 请求头部
- 空行
- 请求体
http的响应报文是什么样的?
- 响应行
- 响应头
- 空行
- 响应体
HTTP的keep-alive是干什么的?
在早期的HTTP/1.0中,每次http请求都要创建一个连接,而创建连接的过程需要消耗资源和时间,为了减少资源消耗,缩短响应时间,就需要重用连接。
在后来的HTTP/1.0中以及HTTP/1.1中,引入了重用连接的机制,就是在http请求头中加入Connection: keep-alive来告诉对方这个请求响应完成后不要关闭,下一次咱们还用这个请求继续交流。
Http与Https的区别
- HTTP 的URL 以http:// 开头,而HTTPS 的URL 以https:// 开头
- HTTP 是不安全的,而 HTTPS 是安全的
- HTTP 标准端口是80 ,而 HTTPS 的标准端口是443
- HTTP 无法加密,而HTTPS 对传输的数据进行加密
- HTTP无需证书,而HTTPS 需要CA机构颁发的SSL证书
HTTPS是如何保证安全的?
Https主要采用了对称加密+非对称加密+CA认证去保证安全。
-
将对称加密的密钥使用非对称加密的公钥进行加密,然后发送出去,接收方使用私钥进行解密得到对称加密的密钥,然后双方可以使用对称加密来进行沟通。
公钥加密,私钥解密。保证信息无法被中间人获得,但是不保证信息被他人截获篡改
- 问题:为什么不直接用非对称加密对数据进行加密?
因为非对称加密比较复杂,加密效率低,所以用非对称加密对对称加密的密钥进行加密,用对称加密加密数据,在保证安全性的同时又保证了性能。
- 问题:为什么不直接用非对称加密对数据进行加密?
-
CA证书+数字签名校验身份
CA机构用CA证书–>得到一个HASH值–>HASH值用CA的私钥进行加密–>得到数字签名–>CA证书+数字签名(附在数字证书的末尾)传输给服务器–>服务器又传给客户端–>由于客户端内部已经植入常用认证机关的公钥–>客户端用公钥解密证书末尾的数字签名,得到证书的HASH值(这是真实的)–>再用证书中的HASH算法,对证书的内容求HASH值–>比较这两个HASH值是否相同,若相同,则认证通过。私钥加密,公钥解密。用于公钥的持有者验证通过私钥加密的内容是否被篡改,但是不保证内容是否被他人获得。
HTTP1.0和HTTP1.1和HTTP2.0的区别?
retrofit2 + rxjava2使用和分析
步骤1:自定义请求接口
public interface ApiService {
@GET("user/getSmsCode")
Observable<HttpResult<SmsCodeEntity >> getSmsCode();
}
符合RESTful风格的数据接收类:
public class HttpResult<T> implements Serializable {
/**
* 1 成功
*/
private String code;
/**
* 封装需要返回的数据
*/
private T content;
/**
* 给用户的提示信息
*/
private String message;
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public T getContent() {
return content;
}
public void setContent(T content) {
this.content = content;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
真实需要接收转化的数据
public class SmsCodeEntity implements Serializable {
private String smsCode;
public String getSmsCode() {
return smsCode;
}
public void setSmsCode(String smsCode) {
this.smsCode= smsCode;
}
}
步骤2:创建retrofit对象
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.readTimeout(60000, TimeUnit.MILLISECONDS)
.writeTimeout(60000, TimeUnit.MILLISECONDS)
.connectTimeout(60000, TimeUnit.MILLISECONDS)
.retryOnConnectionFailure(true)
.addInterceptor(new HeaderInterceptor())
.addInterceptor(new LoggingInterceptor())
.cache(cache)
.build();
ApiService apiService = new Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create(gson))
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.client(okHttpClient)
.build()
.create(ApiService.class);
步骤3:进行网络请求
apiService .getSmsCode()
.map(new Function<HttpResult, SmsCodeEntity>() {
@Override
public SmsCodeEntity apply(HttpResult httpResult) throws Exception {
if(httpResult.getCode().equals(HTTP_SUCCESS_CODE)){//HTTP_SUCCESS_CODE是自定义的网络请求成功码
return (SmsCodeEntity) httpResult.getContent();
}
// 如果是错误码,自行处理
return null;
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<SmsCodeEntity>() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(SmsCodeEntity smsCodeEntity) {
}
@Override
public void onError(Throwable e) {
}
@Override
public void onComplete() {
}
});
看到这里,可能有人会有很多疑问:
1,为什么ApiService 里的返回值是Observable。
2,Observable的泛型有什么用。
3,网络请求应该在子线程,回调应该到主线程,怎么没看见相关操作。
4,调用该方法为什么就能直接将HttpResult中的T泛型转化为真实的SmsCodeEntity数据。
下面就这些问题,展开分析:
retrofit中最重要的两个方法:
-
Retrofit.Builder().build():创建Retrofit对象
retrofit对象的创建采用的是建造者模式,一般需要传入的参数有:
-
baseUrl:基类url,如不传入,会抛出IllegalStateException异常
-
OkHttpClient:OkHttpClient客户端,如不传入,内部会自己实现一个默认客户端。如需设置超时时间、拦截器等,需自己实现并传参。
-
CallAdapter.Factory(重点):结果回调相关。如果没有设置callbackExecutor,则Platform会根据平台创建内部Executor和内部CallAdapter.Factory。Android平台下为:MainThreadExecutor和ExecutorCallAdapterFactory。一般会设置为RxJava2CallAdapterFactory。有了CallAdapter.Factory,后期ServiceMethod中会通过CallAdapter.Factory的get()方法获取到对应的CallAdapter。
Retrofit.Builder里的platform其实已经是Android了。所以调用platform.defaultCallbackExecutor()和 platform.defaultCallAdapterFactory(callbackExecutor),其实是调用Android里的方法。
但是一般我们会设置为RxJava2CallAdapterFactory。 -
Converter.Factory:数据反序列化相关。内部有GsonConverterFactory。也可以自己实现并传入。
-
-
Retrofit.create(final Class service):获取请求接口方法的实现
该方法用了动态代理,开发者自定义请求接口(用注解表示请求方式、返回值类型、参数等)并传入create()方法。
动态代理中主要有三个重要的操作:
-
loadServiceMethod:
通过接口方法生成ServiceMethod对象,有一个重要操作:
-
createCallAdapter();
上面我们已经设置了真正的CallAdapter.Factory,即RxJava2CallAdapterFactory,现在要通过工厂类生成真正的CallAdapter,即RxJava2CallAdapter。如果没设置,用的内部的ExecutorCallAdapterFactory,代码没有写一个子类继承CallAdapter,而是直接new了一个子类。
看到没,最终会调用CallAdapter.Factory的get()方法获取到对应的CallAdapter。这里我们已经设置了RxJava2CallAdapterFactory,它的gei()方法会返回真正的CallAdapter,即:RxJava2CallAdapter
如果是用的内部的ExecutorCallAdapterFactory,它也会返回它真正的CallAdapter。
-
-
new OkHttpCall();
生成网络请求的Call对象。到这里就应该会想到,有了Call对象,就可以执行网络请求Enqueue(异步)或者Execute(同步)。 -
serviceMethod.callAdapter.adapt(okHttpCall)(关键的地方来了)
执行网络请求。-
如果是RxJava2CallAdapterFactory的RxJava2CallAdapter,调用adapt,返回的是Observable对象。所以开发者自定义的请求接口返回值要写Observable。
-
如果是ExecutorCallAdapterFactory,调用adapt,返回的是ExecutorCallbackCall,它是Call的子类。所以开发者自定义的请求接口返回值要写Call。
-
-
到这里,上面提出的问题中,第一个问题:为什么ApiService 里的返回值是Observable。已经解决了。
那么Observable的泛型有什么用呢?这里就要讲到retrofit和rxjava搭配使用了。
rxjava中有个map()操作符,也叫转化操作符。它可以将Observable中拿到的数据进行格式转换或者处理。
那么当进行网络请求后,拿到后台返回的HttpResult数据,我们可以通过错误码,将其中的content解析出来。这里是将A转化为B,所以只需要用到Function。
当然还有Function3,Function4…Function9。
到这里,上面提出的问题中,第二个问题:Observable的泛型有什么用。已经解决了。那么,第四个问题:调用该方法为什么就能直接将HttpResult中的T泛型转化为真实的SmsCodeEntity数据,自然也解决了。
在rxjava链式编程中,我们看到了这行代码:
没错,它们就是用来切换线程的。subscribeOn(Schedulers.io())是用来将网络请求切换到子线程中去;observeOn(AndroidSchedulers.mainThread())是用来将返回结果切换到主线程中去。就不用用retrofit内部的handler那么麻烦了。
所以上面第三个问题:网络请求应该在子线程,回调应该到主线程,怎么没看见相关操作,也解决了。
最终需要调用subscribe订阅,获取真实的SmsCodeEntity数据。