[Android]Okhttp心跳策略研究

作者: CangWang | 来源:发表于2019-03-13 11:08 被阅读52次
    Android组件化架构

    我是苍王,以下是我这个系列的相关文章,有兴趣可以参考一下,可以给个喜欢或者关注我的文章。
    [Android]如何做一个崩溃率少于千分之三噶应用app--章节列表

    这一张非常经典的心跳策略图示


    心跳pingpong机制

    现在一般的心跳策略,都是从客户端发送一个ping信号给服务器,告诉服务器是长连接存活。
    服务器会返回一个pong信号给客户端,让其更新心跳线程时间。如果超时没有接收到信号,那么客户端考虑重连机制。

    这里说一下okhttp有提供了WebSocket的封装,我们的应用也是使用了WebSocket,那就直接看一下WebSocket对pingpong的封装

     public Builder() {
          ……
          //这里默认的ping的时间间隔为0,因为okhttp也可以有如http短连接
          pingInterval = 0;
        }
    
        Builder(OkHttpClient okHttpClient) {
           ……
          //builder函数提供封装
          this.pingInterval = okHttpClient.pingInterval;
        }
        //设置间隔
    public Builder pingInterval(long interval, TimeUnit unit) {
          pingInterval = checkDuration("interval", interval, unit);
          return this;
        }
    

    在RealWebSocket中启动循环发送ping信号

    public void initReaderAndWriter(String name, Streams streams) throws IOException {
        synchronized (this) {
          this.streams = streams;
          this.writer = new WebSocketWriter(streams.client, streams.sink, random);
          this.executor = new ScheduledThreadPoolExecutor(1, Util.threadFactory(name, false));
          if (pingIntervalMillis != 0) {
            //循环定时任务
            executor.scheduleAtFixedRate(
                new PingRunnable(), pingIntervalMillis, pingIntervalMillis, MILLISECONDS);
          }
          if (!messageAndCloseQueue.isEmpty()) {
            runWriter(); // Send messages that were enqueued before we were connected.
          }
        }
    
        reader = new WebSocketReader(streams.client, streams.source, this);
      }
    
    
    private final class PingRunnable implements Runnable {
        PingRunnable() {
        }
    
        @Override public void run() {
          //写入ping信号
          writePingFrame();
        }
      }
    
    void writePingFrame() {
        WebSocketWriter writer;
        int failedPing;
        synchronized (this) {
          if (failed) return;
          writer = this.writer;
          //是否等待pong信号
          failedPing = awaitingPong ? sentPingCount : -1;
          //等待ping计数
          sentPingCount++;
          //等待pong
          awaitingPong = true;
        }
        
        //ping失败,长连接失效
        if (failedPing != -1) {
          failWebSocket(new SocketTimeoutException("sent ping but didn't receive pong within "
              + pingIntervalMillis + "ms (after " + (failedPing - 1) + " successful ping/pongs)"),
              null);
          return;
        }
    
        try {
          //写入空支付到websocket头部
          writer.writePing(ByteString.EMPTY);
        } catch (IOException e) {
          failWebSocket(e, null);
        }
      }
    

    在RealWebSocket的call中执行loopReader监听读取接收到的信息

    /** Receive frames until there are no more. Invoked only by the reader thread. */
      public void loopReader() throws IOException {
        //监听信息
        while (receivedCloseCode == -1) {
          // This method call results in one or more onRead* methods being called on this thread.
          reader.processNextFrame();
        }
      }
    
      void processNextFrame() throws IOException {
       //读取头部
        readHeader();
        if (isControlFrame) {
          //读取头部信息体
          readControlFrame();
        } else {
          readMessageFrame();
        }
      }
    
    

    读取到是顶部信息

    private void readControlFrame() throws IOException {
        if (frameLength > 0) {
          source.readFully(controlFrameBuffer, frameLength);
    
          if (!isClient) {
            controlFrameBuffer.readAndWriteUnsafe(maskCursor);
            maskCursor.seek(0);
            toggleMask(maskCursor, maskKey);
            maskCursor.close();
          }
        }
    
        switch (opcode) {
          //读取ping信号
          case OPCODE_CONTROL_PING:
            frameCallback.onReadPing(controlFrameBuffer.readByteString());
            break;
          //读取pong信号
          case OPCODE_CONTROL_PONG:
            frameCallback.onReadPong(controlFrameBuffer.readByteString());
            break;
         //读取到关闭连接信号
          case OPCODE_CONTROL_CLOSE:
            int code = CLOSE_NO_STATUS_CODE;
            String reason = "";
            long bufferSize = controlFrameBuffer.size();
            if (bufferSize == 1) {
              throw new ProtocolException("Malformed close payload length of 1.");
            } else if (bufferSize != 0) {
              code = controlFrameBuffer.readShort();
              reason = controlFrameBuffer.readUtf8();
              String codeExceptionMessage = WebSocketProtocol.closeCodeExceptionMessage(code);
              if (codeExceptionMessage != null) throw new ProtocolException(codeExceptionMessage);
            }
            frameCallback.onReadClose(code, reason);
            closed = true;
            break;
          default:
            throw new ProtocolException("Unknown control opcode: " + toHexString(opcode));
        }
      }
    

    读取到pong信号,等待pong置为false

    @Override public synchronized void onReadPong(ByteString buffer) {
        // This API doesn't expose pings.
        receivedPongCount++;
        awaitingPong = false;
      }
    

    这就是使用OkHttp的WebSocket keepAlive的流程,而基本的okhttp的socket连接也是通过类似发送这种pingpong信号来维持,之需要设置维护的时间。
    然后keepAlive的经验以前的经验值是59秒,微信的大神的方案是通过记录socket连接和断开时间,适配出最适当的发送长链接时间,有兴趣可以自己实验写一个算法。
    微信的智能心跳方案

    这边做IM的应用的,说一下这边方案,仅供参考。
    手机进入后台后十秒后主动关闭长连接,通过推送来维护消息,这里有个问题就是国内的Umeng推送可达率大家懂的,小米和华为还好点,如果是其他山寨机,到8.0后后台很难保活了。如果是国外FCM送达率是非常高的,如果是最推国外平台,直接依靠推送也非常可靠。

    Android组件化群2

    相关文章

      网友评论

        本文标题:[Android]Okhttp心跳策略研究

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