1.binder oneway
oneway:调用方(Client)非阻塞(non-block)
非 oneway:调用方(Client)阻塞(休眠)
2.Handler之IdleHandler
使用和原理:
Looper.myQueue().addIdleHandler(new IdleHandler(){
@Override
public boolean queueIdle() {
// TODO Auto-generated method stub
//你想做的任何事情
//
doSomeThing()
//需要注意,当queueIdle返回true,将会在每次空闲的时候都会调用 queueIdle,
//反之要是返回false,则只调用一次。
return false;
}
});
目前可以想到的场景:
1.Activity
启动优化:onCreate,onStart,onResume
中耗时较短但非必要的代码可以放到IdleHandler
中执行,减少启动时间
2.想要一个View绘制完成之后添加其他依赖于这个View
的View
,当然这个View#post()
也能实现,区别就是前者会在消息队列空闲时执行。
3.发生一个返回true
的IdleHandler
,在里面让某个View
不停闪烁,这样当用户发呆时就可以诱导用户点击这个View
,这也是种很酷的操作。
4.一些第三方库中使用,比如LeakCanary,Glide中使用到,做内存泄漏检测。
3文件锁
FileLock 用来表示文件区域锁定标记,是锁操作的辅助类,可以通过 FileChannel.lock() 或者 FileChannel.tryLock() 等方法获取文件锁,一旦获取锁则持续有效,直到被 release() 或 JVM 退出,可以通过 FileLock.isValid() 方法来检测文件锁的有效性。
文件锁要么是共享锁,要么是独占锁。可以通过 FileLock.isShared() 方法来检测锁的模式。
共享锁指的是允许多个并发进行文件的读取操作,独占锁指的是只允许一个进行文件的读/写操作。
FileChannel channel = FileChannel.open(path);
//阻塞直至可获得锁。
FileLock lock = channel.lock();
//立即返回,要么返回锁,要么在锁不可获得的情况下返回null。
FileLock lock = channel.tryLock();
//如果shared为false,则锁定文件的目的是独占锁。
//如果shared为true,则是一个共享锁,允许多个进程从文件中读入,并阻止任何进程获得独占的锁。
FileLock.isShared();
//如果锁定了文件的尾部,而这个文件的长度随后增长超过了锁定的部分,那么增长出来的额外区域是未锁定的。
//想要锁定所有的字节可以使用 Long.MAX_VALUE 来表示 size。
FileLock lock = channel.lock(start, size, shared);
FileLock lock = channel.tryLock(start, size, shared);
//锁释放
lock.release();
lock = null;
应用:App保活
原理如下:
1.利用Linux文件锁的原理,使用2个进程互相监听各自的文件锁,来感知彼此的死亡。
2.通过 fork 产生子进程,fork 的进程同属一个进程组,一个被杀之后会触发另外一个进程被杀,从而被文件锁感知。
具体来说,创建 2 个进程 p1, p2,这两个进程通过文件锁互相关联,一个被杀之后拉起另外一个;同时 p1 经过 2 次 fork 产生孤儿进程 c1,p2 经过 2 次 fork 产生孤儿进程 c2,c1 和 c2 之间建立文件锁关联。这样假设 p1 被杀,那么 p2 会立马感知到,然后 p1 和 c1 同属一个进程组,p1 被杀会触发 c1 被杀,c1 死后 c2 立马感受到从而拉起 p1,因此这四个进程三三之间形成了铁三角,从而保证了存活率。
拉起进程方案:
过 native code 给 ams 发送 binder 调用;
当然,如果再底层一点,我们甚至可以通过 ioctl 直接给 binder 驱动发送数据进而完成调用。
4.日志库核心
1.存储,使用mmap,优化io. java提供了map的方法,但是没有unmap,可以反射调用。
MappedByteBuffer map unmap
rafi = new RandomAccessFile(cacheFile, "rw");
rafo = new RandomAccessFile(logFile, "rw");
fci = rafi.getChannel();
fco = rafo.getChannel();
long cacheSize = fci.size();
long logSize = fco.size();
MappedByteBuffer mbbi = fci.map(FileChannel.MapMode.READ_WRITE, 0, cacheSize);
MappedByteBuffer mbbo = fco.map(FileChannel.MapMode.READ_WRITE, logSize, cacheSize);
for (int i = 0; i < cacheSize; i++) {
mbbo.put(mbbi.get(i));
}
//解除内存映射
unmap(mbbi);
unmap(mbbo);
2.加解密
5.IO
1.NIO
多线程阻塞式 I/O 会增加系统开销
非阻塞的 NIO 将 I/O 以事件的方式通知,减少线程切换的开销.
应用程序的实现会变得更复杂,有的时候异步改造并不容易.
NIO 的最大作用不是减少读取文件的耗时,而是最大化提升应用整体的 CPU 利用率。在 CPU 繁忙的时候,我们可以将线程等待磁盘 I/O 的时间来做部分 CPU 操作
读写分离,Buffer 的大小一般推荐使用 4KB 以上
2.Okio:
http://www.javashuo.com/article/p-hlddrbjq-cd.html
3.读写分离+Buffer IO+阻塞队列
6.网络优化
DNS优化核心需要解决的问题有两点:
【1】由于DNS劫持或故障造成的服务不可用,进而影响用户体验,影响公司的收入。
【2】由于DNS调度不准确导致的性能退化,进而影响用户体验。
解决方案:HttpDns
要点:
1.IPv4/IPv6协议栈探测:使用UDP探测
2.使用客户端重试3次机制;
3.使用缓存;
TCP
- 提供面向连接的,可靠的字节流服务
-
tcp的可靠性:
分块传送:数据被分割成最合适的数据块(UDP的数据报长度不变)
等待确认:通过定时器等待接收端发送确认请求,收不到确认则重发
确认回复:收到确认后发送确认回复(不是立即发送,通常推迟几分之一秒)
数据校验:保持首部和数据的校验和,检测数据传输过程有无变化
乱序排序:接收端能重排序数据,以正确的顺序交给应用端
重复丢弃:接收端能丢弃重复的数据包
滑动窗口:两端有固定大小的缓冲区(滑动窗口),防止速度不匹配丢数据;
3.三次握手
image
1)客户端发送SYN,表明要向服务器建立连接。同时带上序列号ISN
2)服务器返回ACK(序号为客户端序列号+1)作为确认。同时发送SYN作为应答(SYN的序列号为服务端唯一的序号)
3)客户端发送ACK确认收到回复(序列号为服务端序列号+1);
why?
4.四次挥手
image
tcp连接是全双工的,数据在两个方向上能同时传递。要确保双方,同时能发数据和收数据
第一次握手:证明了发送方能发数据
第二次握手:ack确保了接收方能收数据,syn确保了接收方能发数据
第三次握手:确保了发送方能收数据
四次握手浪费,两次握手不能保证“双方同时具备收发功能”.
ime_wait状态
也称为2MSL等待状态,报文段最大生存时间。常用值为30s,1min,2min。linux一般为30s。
主动关闭的一方发送最后一个ack所处的状态
这个状态必须维持2MSL等待时间.
目的:预留足够的时间给接收端收ack。同时保证,这个连接不会和后续的连接乱套
1)保证客户端或关闭方发送的最后一个ACK报文能够到达服务器或接受方,因为这个ACK报文可能丢失。
服务器的角度看来,S发送了FIN+ACK报文请求断开,C没有回应,应该是S发送的请求断开报文C没有收到,于是S又会重新发送一次,而C就能在这个2MSL时间段内收到这个重传的报文,接着给出回应报文,并且会重启2MSL计时器。如果客户端收到服务端的FIN+ACK报文后,发送一个ACK给服务端之后就“自私”地立马进入CLOSED状态,可能会导致服务端无法确认收到最后的ACK指令,无法进入CLOSED状态.
2)防止失效请求。客户端发送完最后一个确认报文后,在这个2MSL时间中,就可以使本连接持续的时间内所产生的所有报文段都从网络中消失。这样新的连接中不会出现旧连接的请求报文。
5.重要特性
确认应答机制(ACK机制):
每一个ACK都带有对应的确认序列号, 意思是告诉发送者, 我已经收到了哪些数据; 下一次你要从哪里开始发.
超时重传机制
主机A发送数据给B之后, 可能因为网络拥堵等原因, 数据无法到达主机B
如果主机A在一个特定时间间隔内没有收到B发来的确认应答, 就会进行重发
但是主机A没收到确认应答也可能是ACK丢失了.这种情况下, 主机B会收到很多重复数据.
这时候利用前面提到的序列号, 就可以做到去重.
超时时间如何确定?
动态计算:以500ms为一个单位进行控制, 每次判定超时重发的超时时间都是500ms的整数倍.
如果重发一次之后, 仍然得不到应答, 等待 2500ms 后再进行重传. 如果仍然得不到应答, 等待 4500ms 进行重传.
依次类推, 以指数形式递增. 累计到一定的重传次数, TCP认为网络异常或者对端主机出现异常, 强制关闭连接.
滑动窗口
如果出现了丢包, 那么该如何进行重传呢?
此时分两种情况讨论:
1, 数据包已经收到, 但确认应答ACK丢了.这种情况下, 部分ACK丢失并无大碍, 因为还可以通过后续的ACK来确认对方已经收到了哪些数据包.
- 数据包丢失:
当某一段报文丢失之后, 发送端会一直收到 1001 这样的ACK, 就像是在提醒发送端 “我想要的是 1001”
如果发送端主机连续三次收到了同样一个 “1001” 这样的应答, 就会将对应的数据 1001 - 2000 重新发送
这个时候接收端收到了 1001 之后, 再次返回的ACK就是7001了
因为2001 - 7000接收端其实之前就已经收到了, 被放到了接收端操作系统内核的接收缓冲区中.
这种机制被称为 “高速重发控制” ( 也叫 “快重传” )
流量控制
接收端将自己可以接收的缓冲区大小放入 TCP 首部中的 “窗口大小” 字段,
通过ACK通知发送端;
窗口大小越大, 说明网络的吞吐量越高;
接收端一旦发现自己的缓冲区快满了, 就会将窗口大小设置成一个更小的值通知给发送端;
发送端接受到这个窗口大小的通知之后, 就会减慢自己的发送速度;
如果接收端缓冲区满了, 就会将窗口置为0;
这时发送方不再发送数据, 但是需要定期发送一个窗口探测数据段, 让接收端把窗口大小再告诉发送端.
拥塞控制:是TCP协议想尽可能快的把数据传输给对方, 但是又要避免给网络造成太大压力的折中方案
TCP引入 慢启动 机制, 先发少量的数据, 探探路, 摸清当前的网络拥堵状态以后, 再决定按照多大的速度传输数据.
拥塞窗口:
发送开始的时候, 定义拥塞窗口大小为1;
每次收到一个ACK应答, 拥塞窗口加1;
每次发送数据包的时候, 将拥塞窗口和接收端主机反馈的窗口大小做比较, 取较小的值作为实际发送的窗口
慢启动的阈值, 当拥塞窗口的大小超过这个阈值的时候, 不再按照指数方式增长, 而是按照线性方式增长.
延迟应答
如果接收数据的主机立刻返回ACK应答, 这时候返回的窗口可能比较小.
假设接收端缓冲区为1M. 一次收到了500K的数据;
如果立刻应答, 返回的窗口大小就是500K;
但实际上可能处理端处理的速度很快, 10ms之内就把500K数据从缓冲区消费掉了; 在这种情况下, 接收端处理还远没有达到自己的极限, 即使窗口再放大一些, 也能处理过来;
数量限制: 每隔N个包就应答一次
时间限制: 超过最大延迟时间就应答一次
一般 N 取2, 最大延迟时间取200ms
捎带应答
ACK就可以搭顺风车, 和服务器回应的 “Fine, thank you” 一起发送给客户端。
面向字节流
创建一个TCP的socket, 同时在内核中创建一个 发送缓冲区 和一个 接收缓冲区;
调用write时, 数据会先写入发送缓冲区中;
如果发送的字节数太大, 会被拆分成多个TCP的数据包发出;
如果发送的字节数太小, 就会先在缓冲区里等待, 等到缓冲区大小差不多了, 或者到了其他合适的时机再发送出去;
接收数据的时候, 数据也是从网卡驱动程序到达内核的接收缓冲区;
然后应用程序可以调用read从接收缓冲区拿数据;
另一方面, TCP的一个连接, 既有发送缓冲区, 也有接收缓冲区,
那么对于这一个连接, 既可以读数据, 也可以写数据, 这个概念叫做 全双工
粘包问题
站在应用层的角度, 应用层的数据包,是一串连续的字节数据,没有明确的边界。
客户端心跳包使用
方案一
最简单的策略当然是客户端定时n秒发送心跳包,服务端收到心跳包后,回复客户端的心跳,如果客户端连续m秒没有收到心跳包,则主动断开连接,然后重连,将正常的业务请求暂时不发送的该台服务器上。
方案二
这样传送一些无效的数据包有点多,可以做些优化。因为心跳就是一种探测请求,业务上的正常请求除了做业务处理外,还可以用作探测的功能,比如此时有请求需要发送到服务端,这个请求就可以当作是一次心跳,服务端收到请求,处理后回复,只要服务端有回复,就表明链路还是通的,如果客户端请求比较空闲的时候,服务端一直没有数据回复,就使用心跳进行探测,这样就有效利用了正常的请求来作为心跳的功能,减少无效的数据传输。
6.滑动窗口
TCP滑动窗口技术通过动态改变窗口大小来调节两台主机间数据传输。每个TCP/IP主机支持全双工数据传输,因此TCP有两个滑动窗口:一个用于接收数据,另一个用于发送数据。
7.为什么用socket而不是binder fork进程
fork不允许存在多线程。而非常巧的是Binder通讯偏偏就是多线程,所以干脆父进程(Zgote)这个时候就不使用binder线程
作者:kinnylee
链接:https://juejin.cn/post/6844903685563105293
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
————————————————
版权声明:本文为CSDN博主「朱小厮」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/u013256816/article/details/84001583
————————————————
版权声明:本文为CSDN博主「rugu-sco」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/sinat_36629696/article/details/80740678
网友评论