美文网首页
Android中的网络库详解(一. HttpURLConnect

Android中的网络库详解(一. HttpURLConnect

作者: 只会敲代码的键盘手 | 来源:发表于2020-06-15 18:08 被阅读0次

1.前言

在上一章中,讲解了计算机网络协议及通信原理,这章结合实际http网络请求框架,讲解android平台网络框架的原理及实现

2.目录

目录

3.网络库的发展史

  • android应用中大都会使用Http协议来访问网络,底层网络库主要提供两种方式HttpURlConnectionHttpClient来进行Http操作
  • Google为了方便开发者,在2013年Google I/O推出了新的网络通信框架Volley,可进行数据请求和图片展示
  • Square公司的开源项目okhttp具有明显优势,是安卓端最火热的轻量级框架.android4.4以后,HttpURLConnection底层实现就是使用的OKhttp
  • Square的右移开源项目Retrofit是一个网络封装框架,默认底层使用的是OkHttp,其使用灵活,简单,大大增加了开发效率

3.1.HttpClient

  • Apache公司提供的原始http协议库
  • 比较稳定,bug数量少
  • api数量众多,难以升级和维护
  • android6.0Google官方Api移除了HttpClient

3.2.HttpURLConnection

  • 简单,容易使用和扩展
  • android version 2.2之前,调用inputStream的close()方法时,会导致连接池失效,解决方法是禁用连接池.System.setProperty("http.keepAlive","false")
  • android version 2.3引入了响应压缩和会话的机制,及失败重连服务器
  • android version 4.0添加缓存机制

3.3.Volley

  • 适合于数据量不大,通信频繁的网络操作
  • 不适用于大文件下载和上传
  • android version 2.3之前采用的是HttpClient,之后采用的是HttpURLConnection进行网络通信

3.4.OkHttp

  • Android4.4后,HttpURLConnection底层实现使用的就是OkHttp
  • 对同一主机发出的所有请求都可以共享相同的套接字连接
  • 使用连接池来复用连接以提高效率
  • 提供了对GZIP的默认支持来降低传输内容的大小
  • 对Http响应的缓存机制,可以避免不必要的网络请求
  • 当网络出现问题时,OkHttp会自动重试一个主机的多个IP地址

3.5.Retrofit

  • 封装良好,使用简单,大大增加了开发效率
  • 使用灵活,可以自定义底层网络请求库,自定义json解析库,可与RxJava结合使用

4.1.HttpURLConnection源码简单解析

HttpURLConnection的使用:

String urlAddress = "http://www.hao123.cn";
//分析2
URL url = new URL(urlAddress);
HttpURLConnection urlConnection = (HttpURLConnection) 
//分析1
url.openConnection();
urlConnection.setConnectTimeout(5000);
urlConnection.setReadTimeout(5000);
urlConnection.setUseCaches(true);
urlConnection.setRequestMethod("GET");
urlConnection.setRequestProperty("Content-Type","application/json");
urlConnection.addRequestProperty("Connection","Keep-Alive");
urlConnection.setRequestProperty("Charset", "gb2312");
urlConnection.connect();
if (urlConnection.getResponseCode() == 200) {
    //分析3
    String response = streamToString(urlConnection.getInputStream());
    Log.d("--TAG--", "requestGet: " + "请求成功:" + response);
    runOnUiThread(() -> mTvResponse.setText(response));
} else {
    Log.d("--TAG--", "requestGet: " + "请求失败");
}
urlConnection.disconnect();

HttpURLConnection的openConnection()实际调用的是handler的openConnection()

public URLConnection openConnection() throws java.io.IOException {
    return handler.openConnection(this);
}

再查看HttpURLConnection的创建,是通过new URL(urlAddress)

public URL(URL context, String spec, URLStreamHandler handler) throws MalformedURLException{
  ...
  //调用重载的构造器,context和handler默认为null
  if (handler == null && (handler = getURLStreamHandler(protocol)) == null) {
      throw new MalformedURLException("unknown protocol: "+protocol);
  }
  ...
  handler.parseURL(this, spec, start, limit);
  ...
}

然后查看getURLStreamHandler(protocol)

static URLStreamHandler getURLStreamHandler(String protocol) {
  ...
  if (handler == null) {
      try {
          handler = createBuiltinHandler(protocol);
      } catch (Exception e) {
          throw new AssertionError(e);
      }
  }
  ...
}

接着调用createBuiltinHandler(String protocol),创建HttpHandler

private static URLStreamHandler createBuiltinHandler(String protocol)
        throws ClassNotFoundException, InstantiationException, IllegalAccessException {
    URLStreamHandler handler = null;
    if (protocol.equals("file")) {
        handler = new sun.net.www.protocol.file.Handler();
    } else if (protocol.equals("ftp")) {
        handler = new sun.net.www.protocol.ftp.Handler();
    } else if (protocol.equals("jar")) {
        handler = new sun.net.www.protocol.jar.Handler();
    } else if (protocol.equals("http")) {
        //反射创建okhttp,实际使用的是com.squareup.okhttp包下的okHttp
        //4.3之前new HttpHandler();
        handler = (URLStreamHandler)Class.
                forName("com.android.okhttp.HttpHandler").newInstance();
    } else if (protocol.equals("https")) {
        //https加入TLS(安全传输协议),建立TCP连接后会握手
        handler = (URLStreamHandler)Class.
                forName("com.android.okhttp.HttpsHandler").newInstance();
    }
    return handler;
}

然后回到分析1,查看HttpHandleropenConnection()方法

@Override 
protected URLConnection openConnection(URL url) throws IOException {
    return newOkUrlFactory(null /* proxy */).open(url);
}

继续查看HttpHandlernewOkUrlFactory(null /* proxy */)方法

protected OkUrlFactory newOkUrlFactory(Proxy proxy) {
    OkUrlFactory okUrlFactory = createHttpOkUrlFactory(proxy);
    okUrlFactory.client().setConnectionPool(configAwareConnectionPool.get());
    return okUrlFactory;
}

接着查看createHttpOkUrlFactory(proxy)

public static OkUrlFactory createHttpOkUrlFactory(Proxy proxy) {
    OkHttpClient client = new OkHttpClient();
    //...OkHttpClient的一些配置信息
    ...
    OkUrlFactory okUrlFactory = new OkUrlFactory(client);
    ...
    return okUrlFactory;
}

然后回去查看OkUrlFactoryopen(url)方法

public HttpURLConnection open(URL url) {
    return open(url,client.getProxy);
}

HttpURLConnection open(URL url, Proxy proxy) {
    String protocol = url.getProtocol();
    OkHttpClient copy = client.copyWithDefaults();
    copy.setProxy(proxy);
    //返回HttpURLConnection的具体实现类
    if (protocol.equals("http")) return new HttpURLConnectionImpl(url, copy, urlFilter);
    if (protocol.equals("https")) return new HttpsURLConnectionImpl(url, copy, urlFilter);
    throw new IllegalArgumentException("Unexpected protocol: " + protocol);
}

进行下一步查看分析3, HttpURLConnectionImplconnect()连接方法

@Override 
public final void connect() throws IOException {
    //初始化http引擎
    initHttpEngine();
    boolean success;
    do {
           success = execute(false);
    } while (!success);
}

然后再查看execute(false)

private boolean execute(boolean readResponse) throws IOException {
  ...
  try {
    // 发起请求
    httpEngine.sendRequest();
    Connection connection = httpEngine.getConnection();
    if (connection != null) {
      route = connection.getRoute();
      handshake = connection.getHandshake();
    } else {
      route = null;
      handshake = null;
    }
    if (readResponse) {
      // 读取响应
      httpEngine.readResponse();
    }
    releaseConnection = false;
    return true;
  } 
  ...
}

查看发送请求,httpEngine.sendRequest()

public void sendRequest() throws RequestException, RouteException, IOException {
    ...
    // 当networkRequest不等于null时,需从服务端重新获取
  if (networkRequest != null) {
    // 建立Socket连接
    httpStream = connect();
    httpStream.setHttpEngine(this);
    ...
}

接着看HttpEngineconnect()方法

private HttpStream connect() throws RouteException, RequestException, IOException {
  boolean doExtensiveHealthChecks = !networkRequest.method().equals("GET");
  return streamAllocation.newStream(client.getConnectTimeout(),
      client.getReadTimeout(), client.getWriteTimeout(),
      client.getRetryOnConnectionFailure(), doExtensiveHealthChecks);
}

然后查看StreamAllocationnewStream()方法

public HttpStream newStream(int connectTimeout, int readTimeout, int writeTimeout,
    boolean connectionRetryEnabled, boolean doExtensiveHealthChecks)
    throws RouteException, IOException {
  try {
    // 寻找一个健康的连接
    RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
        writeTimeout, connectionRetryEnabled, doExtensiveHealthChecks);

    // 在寻找到的连接上设置HttpStream,HttpStream是用来发送请求和接收响应的
    HttpStream resultStream;
    if (resultConnection.framedConnection != null) { // 对应于HTTP/2协议
        resultStream = new Http2xStream(this, resultConnection.framedConnection);
    } else { // 对应于HTTP/1.x协议
      resultConnection.getSocket().setSoTimeout(readTimeout);
      resultConnection.source.timeout().timeout(readTimeout, MILLISECONDS);
      resultConnection.sink.timeout().timeout(writeTimeout, MILLISECONDS);
      resultStream = new Http1xStream(this, resultConnection.source, resultConnection.sink);
    }
    synchronized (connectionPool) {
      resultConnection.streamCount++;
      stream = resultStream;
      return resultStream;
    }
  } catch (IOException e) {
    throw new RouteException(e);
  }
}

接下来看StreamAllocationfindHealthyConnection()方法

private RealConnection findHealthyConnection(int connectTimeout, int readTimeout,
    int writeTimeout, boolean connectionRetryEnabled, boolean doExtensiveHealthChecks)
    throws IOException, RouteException {
  while (true) {
    RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,
        connectionRetryEnabled);
    // 如果candidate是一个全新的连接(即连接上面Steam的个数为0),则跳过下面的健康检查直接返回。
    synchronized (connectionPool) {
      if (candidate.streamCount == 0) {
        return candidate;
      }
    }
    ...
  }
}

接着查看StreamAllocation的findConnection方法

private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
    boolean connectionRetryEnabled) throws IOException, RouteException {
  ...
  // RouteSelector的next方法中首先获取proxies中的第一个代理(在没有设置代理的情况下,
  // 该代理为Proxy.NO_PROXY),然后用该代理创建Route实例
  Route route = routeSelector.next();
  RealConnection newConnection = new RealConnection(route);
  acquire(newConnection);
  // 将新创建的连接放到连接池中以备后用
  synchronized (connectionPool) {
    Internal.instance.put(connectionPool, newConnection);
    this.connection = newConnection;
    if (canceled) throw new IOException("Canceled");
  }
  // 通过新创建的连接与服务端建立Socket连接
  newConnection.connect(connectTimeout, readTimeout, writeTimeout, address.getConnectionSpecs(),
      connectionRetryEnabled);
  routeDatabase().connected(newConnection.getRoute());
  return newConnection;
}

接下来继续查看RealConnectionconnect()方法

public void connect(int connectTimeout, int readTimeout, int writeTimeout,
    List<ConnectionSpec> connectionSpecs, boolean connectionRetryEnabled) throws RouteException {
    ...
  while (protocol == null) {
    try {
      // 根据上面findConnection的注释可知,默认情况下不设置代理,即proxy.type() == Proxy.Type.DIRECT是成立的,
      // 因此通过address.getSocketFactory().createSocket()创建一个Socket实例
      rawSocket = proxy.type() == Proxy.Type.DIRECT || proxy.type() == Proxy.Type.HTTP
          ? address.getSocketFactory().createSocket()
          : new Socket(proxy);
      // 利用rawSocket实例根据指定host和port发起与服务端的TCP连接
      connectSocket(connectTimeout, readTimeout, writeTimeout, connectionSpecSelector);
    } catch (IOException e) {
      ...
    }
  }
}

接下来就来看看RealConnectionconnectSocket()方法

private void connectSocket(int connectTimeout, int readTimeout, int writeTimeout,
    ConnectionSpecSelector connectionSpecSelector) throws IOException {
  rawSocket.setSoTimeout(readTimeout);
  try {
    // 利用rawSocket实例根据指定host和port的服务端建立TCP连接
    Platform.get().connectSocket(rawSocket, route.getSocketAddress(), connectTimeout);
  } catch (ConnectException e) {
    throw new ConnectException("Failed to connect to " + route.getSocketAddress());
  }
  // 这两个实例就是用来读取响应和发送请求的
  source = Okio.buffer(Okio.source(rawSocket));
  sink = Okio.buffer(Okio.sink(rawSocket));
  // 只有scheme为https的情况下,下面条件才会成立
  if (route.getAddress().getSslSocketFactory() != null) {
    // 进行TLS握手
    connectTls(readTimeout, writeTimeout, connectionSpecSelector);
  } else { // 对于scheme为http情况下,则将协议版本设置为HTTP/1.1
    protocol = Protocol.HTTP_1_1;
    socket = rawSocket;
  }
  ...
  }
}

最后查看PlatformconnectSocket()方法,建立TCP连接

public void connectSocket(Scoket socket, InetSocketAdress adress, int connectTimeout) throws IOException {
    socket.connect(address, connectTimeout);
}

然后再看分析3,HttpURLConnectionImplgetInputStream()方法

@Override 
public final InputStream getInputStream() throws IOException {
  ...
  HttpEngine response = getResponse();
  ...
  return response.getResponse().body().byteStream();
}

接着查看HttpURLConnectionImpl中的getResponse()方法

private HttpEngine getResponse() throws IOException {
  initHttpEngine();
  if (httpEngine.hasResponse()) {
    return httpEngine;
  }
  while (true) {
    if (!execute(true)) {
      continue;
    }
    ...
  }
}

然后再次查看HttpURLConnectionImpl中的execute()方法

private boolean execute(boolean readResponse) throws IOException {
    ...
    if (readResponse) {
      httpEngine.readResponse();
    }
    ...
}
http的缓存策略

缓存过程发生在发送请求和处理响应中,对应HttpEnginesendRequest()readResponse()方法

public void sendRequest() throws RequestException, RouteException, IOException {
  if (cacheStrategy != null) return; // Already sent.
  if (httpStream != null) throw new IllegalStateException();
  // 为请求添加默认的请求头
  Request request = networkRequest(userRequest);
   //获取client的Cache,同时Cache在初始化的时候会去读取缓存目录中关于曾经请求过得所有信息
  InternalCache responseCache = Internal.instance.internalCache(client);
  Response cacheCandidate = responseCache != null
      ? responseCache.get(request)
      : null;
  long now = System.currentTimeMillis();
  // 给出请求和请求对应的缓存的响应,然后根据缓存策略得出符合缓存策略的请求和响应
  cacheStrategy = new CacheStrategy.Factory(now, request, cacheCandidate).get();
  // 当得出的请求不为null时(即networkRequest不为null),代表该请求对应的缓存的响应不存在或者已经过期,
  // 此时需要从服务端获取,否则使用请求对应的缓存的响应
  networkRequest = cacheStrategy.networkRequest;
  cacheResponse = cacheStrategy.cacheResponse;

  if (responseCache != null) {
    responseCache.trackResponse(cacheStrategy);
  }

  if (cacheCandidate != null && cacheResponse == null) {
    closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
  }
  // 当networkRequest不等于null时,代表该请求对应的缓存响应不存在或者已经过期,需要从新从服务端获取
  if (networkRequest != null) {
    // 建立Socket连接
    httpStream = connect();
    httpStream.setHttpEngine(this);
  ...
  }
}
public void readResponse() throws IOException {
  ...
  Response networkResponse;

  if (forWebSocket) {
    httpStream.writeRequestHeaders(networkRequest);
    networkResponse = readNetworkResponse();
  } else if (!callerWritesRequestBody) {
    networkResponse = new NetworkInterceptorChain(0, networkRequest).proceed(networkRequest);
  } else {
    // 由第13步中的中newHttpEngine方法可知,forWebSocket为false、callerWritesRequestBody为true,因此会运行到这里
    // 将bufferedRequestBody中剩下的数据(即不够一个Segment的数据)全部移动到requestBodyOut中。
    if (bufferedRequestBody != null && bufferedRequestBody.buffer().size() > 0) {
      bufferedRequestBody.emit();
    }

    if (sentRequestMillis == -1) { 
      // sentRequestMillis等于-1代表请求头还没有写入到第21步中的提到的sink字段,没有写入的原因是请求体的长度是未知的。
      if (OkHeaders.contentLength(networkRequest) == -1
          && requestBodyOut instanceof RetryableSink) {
        long contentLength = ((RetryableSink) requestBodyOut).contentLength();
        networkRequest = networkRequest.newBuilder()
            // 添加头字段Content-Length,用于告诉服务端请求体的长度
            .header("Content-Length", Long.toString(contentLength))
            .build();
      }
      // writeRequestHeaders最终将请求头写入到RealBufferedSink实例中,该RealBufferedSink实例是下面第21步中的提到的sink字段,对应第33步
      httpStream.writeRequestHeaders(networkRequest);
    }

    if (requestBodyOut != null) {
      if (bufferedRequestBody != null) {
        // This also closes the wrapped requestBodyOut.
        bufferedRequestBody.close();
      } else {
        requestBodyOut.close();
      }
      if (requestBodyOut instanceof RetryableSink) {
        // 由第15步可知,当requestBodyOut的类型是RetryableSink时,写入到requestBodyOut中的请求体数据是保持在内存中,下面的方法就是将内存中的请求体数据写入到第21步中的提到的sink字段中,对应第34步。
        httpStream.writeRequestBody((RetryableSink) requestBodyOut);
      }
    }

    // 从服务端读取响应
    networkResponse = readNetworkResponse();
  }

  receiveHeaders(networkResponse.headers());

  // If we have a cache response too, then we're doing a conditional get.
  if (cacheResponse != null) {
    if (validate(cacheResponse, networkResponse)) {
      // 执行到这里,说明过期的缓存的响应仍然是可用的,这时直接使用缓存的响应,关闭网络连接,释放连接
      userResponse = cacheResponse.newBuilder()
          .request(userRequest)
          .priorResponse(stripBody(priorResponse))
          // combine方法用于更新缓存的响应的新鲜度,比如修改Last-Modified字段,对应2.3中的第7步
          .headers(combine(cacheResponse.headers(), networkResponse.headers()))
          .cacheResponse(stripBody(cacheResponse))
          .networkResponse(stripBody(networkResponse))
          .build();
      networkResponse.body().close();
      releaseStreamAllocation();

      // Update the cache after combining headers but before stripping the
      // Content-Encoding header (as performed by initContentStream()).
      InternalCache responseCache = Internal.instance.internalCache(client);
      responseCache.trackConditionalCacheHit();
      //更新缓存
      responseCache.update(cacheResponse, stripBody(userResponse));
      //解压缩response
      userResponse = unzip(userResponse);
      return;
    } else {
      closeQuietly(cacheResponse.body());
    }
  }
  // 运行到这里,说明缓存响应已经不新鲜了,这时使用networkResponse,对应于2.3中第5步否定的情况
  userResponse = networkResponse.newBuilder()
      .request(userRequest)
      .priorResponse(stripBody(priorResponse))
      .cacheResponse(stripBody(cacheResponse))
      .networkResponse(stripBody(networkResponse))
      .build();

  if (hasBody(userResponse)) {
    // maybeCache是用于对网络响应的缓存处理,只有有响应体的响应才会被缓存
    maybeCache();
    userResponse = unzip(cacheWritingResponse(storeRequest, userResponse));
  }
}

上面就是HttpURLConnection的大致流程:

  • 通过Socket接口与服务端建立TCP连接
  • 确定应用层协议,https需TLS握手
  • TCP连接发生请求和接受响应

5.总结

这一章主要讲解了android平台的网络编程库,包括底层网络框架,HttpClient,HttpURLConnection,OkHttp,网络封装库Volley和Retrofit,也对HttpURLConnection的底层实现进行了简单分析,由于篇幅有限,下一节将继续分析后面开源库的底层实现


参考:
https://www.jianshu.com/p/35ecbc09c160

相关文章

网友评论

      本文标题:Android中的网络库详解(一. HttpURLConnect

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