Okhttp

作者: cxlin007 | 来源:发表于2017-09-25 14:56 被阅读22次

    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源码分析复用连接池

    相关文章

      网友评论

          本文标题:Okhttp

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