美文网首页
Android 冷知识

Android 冷知识

作者: 阿木Robert | 来源:发表于2022-01-05 14:10 被阅读0次

    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绘制完成之后添加其他依赖于这个ViewView,当然这个View#post()也能实现,区别就是前者会在消息队列空闲时执行。
    3.发生一个返回trueIdleHandler,在里面让某个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

    1. 提供面向连接的,可靠的字节流服务
    2. 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来确认对方已经收到了哪些数据包.

    1. 数据包丢失:
      当某一段报文丢失之后, 发送端会一直收到 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

    相关文章

      网友评论

          本文标题:Android 冷知识

          本文链接:https://www.haomeiwen.com/subject/jiwbeltx.html