看啥推荐读物
专栏名称: Cang_Wang
Android研发架构师
目录
今天看啥  ›  专栏  ›  Cang_Wang

[Android]Okhttp心跳策略研究

Cang_Wang  · 掘金  · android  · 2019-03-13 06:53
阅读 34

[Android]Okhttp心跳策略研究

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




原文地址:访问原文地址
快照地址: 访问文章快照