Okhttp

2017-09-25  本文已影响22人  cxlin007

android-async-http 基于httpClient封装的网络库,android6.0后httpclient不是系统自带的,目前已不维护,尽管Google在大部分安卓版本推荐使用HttpURLConnection,但是这个类太难用了,而OKhttp是一个相对成熟的网络库,在android4.4的源码中HttpURLConnection已经替换成OKHttp实现了,很多大的第三方库都支持它(fresco、retrofit)

api调用方便
OkHttpClient client = new OkHttpClient();
 
String run(String url) throws IOException {
    Request request = new Request.Builder().url(url).build();
    Response response = client.newCall(request).execute();
    if (response.isSuccessful()) {
        return response.body().string();
    } else {
        throw new IOException("Unexpected code " + response);
    }
}

通过Request的Builder辅助类,创建请求对象,再传递给OkHttpClient执行,Response为返回的内容,这样get请求就完成了。
如果要是实现post请求,则创建一个RequestBody对象,赋值给Request,则完成了post请求

public static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
OkHttpClient client = new OkHttpClient();
String post(String url, String json) throws IOException {
     RequestBody body = RequestBody.create(JSON, json);
      Request request = new Request.Builder()
      .url(url)
      .post(body)
      .build();
      Response response = client.newCall(request).execute();
    f (response.isSuccessful()) {
        return response.body().string();
    } else {
        throw new IOException("Unexpected code " + response);
    }
}
支持各种形式的post请求

post提交字符串

String postBody = ""
        + "Releases\n"
        + "--------\n"
        + "\n"
        + " * _1.0_ May 6, 2013\n"
        + " * _1.1_ June 15, 2013\n"
        + " * _1.2_ August 11, 2013\n";
 
    Request request = new Request.Builder()
        .url("https://api.github.com/markdown/raw")
        .post(RequestBody.create(MEDIA_TYPE_MARKDOWN, postBody))
        .build();

post提交流

RequestBody requestBody = new RequestBody() {
      @Override public MediaType contentType() {
        return MEDIA_TYPE_MARKDOWN;
      }
 
      @Override public void writeTo(BufferedSink sink) throws IOException {
        sink.writeUtf8("Numbers\n");
        sink.writeUtf8("-------\n");
        for (int i = 2; i <= 997; i++) {
          sink.writeUtf8(String.format(" * %s = %s\n", i, factor(i)));
        }
      }
 
      private String factor(int n) {
        for (int i = 2; i < n; i++) {
          int x = n / i;
          if (x * i == n) return factor(x) + " × " + i;
        }
        return Integer.toString(n);
      }
    };
 
    Request request = new Request.Builder()
        .url("https://api.github.com/markdown/raw")
        .post(requestBody)
        .build();

post提交文件

File file = new File("README.md");
 
    Request request = new Request.Builder()
        .url("https://api.github.com/markdown/raw")
        .post(RequestBody.create(MEDIA_TYPE_MARKDOWN, file))
        .build();

post提交表单

RequestBody formBody = new FormEncodingBuilder()
        .add("search", "Jurassic Park")
        .build();
    Request request = new Request.Builder()
        .url("https://en.wikipedia.org/w/index.php")
        .post(formBody)
        .build();
OkHttpClient 的执行

调用execute方法是同步执行,调用enqueue时异步执行

响应头
private final OkHttpClient client = new OkHttpClient();
 
public void run() throws Exception {
    Request request = new Request.Builder()
        .url("https://api.github.com/repos/square/okhttp/issues")
        .header("User-Agent", "OkHttp Headers.java")
        .addHeader("Accept", "application/json; q=0.5")
        .addHeader("Accept", "application/vnd.github.v3+json")
        .build();
 
    Response response = client.newCall(request).execute();
    if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
 
    System.out.println("Server: " + response.header("Server"));
    System.out.println("Date: " + response.header("Date"));
    System.out.println("Vary: " + response.headers("Vary"));
}

post分块请求

RequestBody requestBody = new MultipartBuilder()
        .type(MultipartBuilder.FORM)
        .addPart(
            Headers.of("Content-Disposition", "form-data; name=\"title\""),
            RequestBody.create(null, "Square Logo"))
        .addPart(
            Headers.of("Content-Disposition", "form-data; name=\"image\""),
            RequestBody.create(MEDIA_TYPE_PNG, new File("website/static/logo-square.png")))
        .build();
 
    Request request = new Request.Builder()
        .header("Authorization", "Client-ID " + IMGUR_CLIENT_ID)
        .url("https://api.imgur.com/3/image")
        .post(requestBody)
        .build();

能够方便的设置和获取响应头。

Request request = new Request.Builder()
        .url("https://api.github.com/repos/square/okhttp/issues")
        .header("User-Agent", "OkHttp Headers.java")
        .addHeader("Accept", "application/json; q=0.5")
        .addHeader("Accept", "application/vnd.github.v3+json")
        .build();
 
    Response response = client.newCall(request).execute();
    if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
 
    System.out.println("Server: " + response.header("Server"));
    System.out.println("Date: " + response.header("Date"));
    System.out.println("Vary: " + response.headers("Vary"));
支持同步和异步请求

enqueue发起异步请求,execute发起同步请求

请求拦截

Okhttp支持定义各种拦截器对整个网络请求流程进行拦截(监视、重写、重试调用)

class LoggingInterceptor implements Interceptor {
  @Override public Response intercept(Interceptor.Chain chain) throws IOException {
   Request request = chain.request();

   long t1 = System.nanoTime();
   logger.info(String.format("Sending request %s on %s%n%s",
   request.url(), chain.connection(), request.headers()));

   Response response = chain.proceed(request);

   long t2 = System.nanoTime();
    logger.info(String.format("Received response for %s in %.1fms%n%s",
   response.request().url(), (t2 - t1) / 1e6d, response.headers()));

return response;
  }
}

OkHttpClient client = new OkHttpClient.Builder()
     .addInterceptor(new LoggingInterceptor())
     .build();

可以定义多个拦截器,按顺序调用

缓存

Okhttp已经内置了缓存,使用DiskLruCache,使用缓存需要在创建OKhttpClient进行配置

int cacheSize = 10 * 1024 * 1024; // 10 MiB
File cacheDirectory = new File("cache");
//出于安全性的考虑,在Android中我们推荐使用Context.getCacheDir()来作为缓存的存放路径
if (!cacheDirectory.exists()) {
    cacheDirectory.mkdirs();
}
Cache cache = new Cache(cacheDirectory, cacheSize);
OkHttpClient newClient = okHttpClient.newBuilder()
        .Cache(cache)
        .connectTimeout(20, TimeUnit.SECONDS)
        .readTimeout(20, TimeUnit.SECONDS)
        .build();

如果服务器支持缓存,请求返回的Response会带有Header:Cache-Control, max-age=xxx,Okhttp会自动执行缓存,如果服务器不支持,则要通过拦截Response,给其设置Cache-Control信息

public class CacheInterceptor implements Interceptor {
    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        Response response = chain.proceed(request);
        Response response1 = response.newBuilder()
                .removeHeader("Pragma")
                .removeHeader("Cache-Control")
                //cache for 30 days
            .header("Cache-Control", "max-age=" + 3600 * 24 * 30)
                .build();
        return response1;
    }
}

OkHttpClient okHttpClient = new OkHttpClient();

OkHttpClient newClient = okHttpClient.newBuilder()
        .addNetworkInterceptor(new CacheInterceptor())
        .cache(cache)
        .connectTimeout(20, TimeUnit.SECONDS)
        .readTimeout(20, TimeUnit.SECONDS)
        .build();

ssl支持

SSL位于TCP/IP和http协议之间,他的作用
1、认证用户和服务器,确保数据发送到正确的客户机和服务器;(验证证书)
2、加密数据以防止数据中途被窃取;(加密)
3、维护数据的完整性,确保数据在传输过程中不被改变。(摘要算法)
Okhttp默认是可以访问通过CA认证的https链接,如果是自签名的证书,则应用需要存放对应的证书,并添加到okhttp设置中。

mContext = context;
        X509TrustManager trustManager;
        SSLSocketFactory sslSocketFactory;
        final InputStream inputStream;
        try {
            inputStream = mContext.getAssets().open("srca.cer"); // 得到证书的输入流
            try {

                trustManager = trustManagerForCertificates(inputStream);//以流的方式读入证书
                SSLContext sslContext = SSLContext.getInstance("TLS");
                sslContext.init(null, new TrustManager[]{trustManager}, null);
                sslSocketFactory = sslContext.getSocketFactory();

            } catch (GeneralSecurityException e) {
                throw new RuntimeException(e);
            }

            client = new OkHttpClient.Builder()
                    .sslSocketFactory(sslSocketFactory, trustManager)
                    .build();
        } catch (IOException e) {
            e.printStackTrace();
        }
dns支持

HTTP DNS通过将域名查询请求放入http中的一种域名解析方式,而不用系统自带的libc库去查询运营商的DNS服务器,有更大的自由度,https下不会存在任何问题,证书校验依然使用域名进行校验。目前微信,qq邮箱、等业务均使用了HTTP DNS。
在OKhttp中,提供DNS接口,实现DNS类,配置到OKhttp中就行了
主要优点:
能够准确地将站点解析到离用户最近的CDN站点,方便进行流量调度
解决部分运营商DNS无法解析国外站点的问题
TCP在一定程度可以防止UDP无校验导致的DNS欺诈(比如墙,运营商广告,404导航站),当然基于HTTP的话本质还是不安全的。

static Dns HTTP_DNS =  new Dns(){
  @Override public List<InetAddress> lookup(String hostname) throws UnknownHostException {
    //防御代码
    if (hostname == null) throw new UnknownHostException("hostname == null");
    //dnspod提供的dns服务
    HttpUrl httpUrl = new HttpUrl.Builder().scheme("http")
        .host("119.29.29.29")
        .addPathSegment("d")
        .addQueryParameter("dn", hostname)
        .build();
    Request dnsRequest = new Request.Builder().url(httpUrl).get().build();
    try {
      String s = getHTTPDnsClient().newCall(dnsRequest).execute().body().string();
      //避免服务器挂了却无法查询DNS
      if (!s.matches("\\b(?:\\d{1,3}\\.){3}\\d{1,3}\\b")) {
        return Dns.SYSTEM.lookup(hostname);
      }
      return Arrays.asList(InetAddress.getAllByName(s));
    } catch (IOException e) {
      return Dns.SYSTEM.lookup(hostname);
    }
  }
};

static public synchronized OkHttpClient getClient() {
  if (client == null) {
    final File cacheDir = GlobalContext.getInstance().getExternalCacheDir();
    client = new OkHttpClient.Builder().addNetworkInterceptor(getLogger())
        .cache(new Cache(new File(cacheDir, "okhttp"), 60 * 1024 * 1024))
        .dispatcher(getDispatcher())
        //配置DNS查询实现
        .dns(HTTP_DNS)
        .build();
  }
  return client;
}
支持连接池

Okhttp支持5个并发KeepAlive,默认链路生命为5分钟(链路空闲后,保持存活的时间)。
socket连接每次需要3次握手、释放要2次或4次,当访问复杂网络时,延时将成为非常重要的因素,使用连接池的好处就是优化网络性能,对于延迟降低与速度提升的有非常重要的作用。
KeepAlive缺点则是,在提高了单个客户端性能的同时,复用却阻碍了其他客户端的链路速度。

相关资料

OkHttp使用教程
Okhttp使用指南与源码分析
Okhttp-wiki 之 Interceptors 拦截器
OkHttp 3.x 源码解析之Interceptor 拦截器
OkHttp拦截器的实现原理
使用okHttp支持https
Android使用OkHttp请求自签名的https网站
OkHttp3应用HTTP DNS的实现
Android OkHttp实现HttpDns的最佳实践(非拦截器)
Android网络编程(七)源码解析OkHttp前篇请求网络
OkHttp3源码分析复用连接池

上一篇下一篇

猜你喜欢

热点阅读