okhttp的使用 - CSDN
精华内容
参与话题
  • OkHttp官方教程解析-彻底入门OkHttp使用

    万次阅读 多人点赞 2016-05-09 15:05:51
    最近半年来身边开发的朋友越来越多的提到OkHttp,上谷歌百度一下,确实OkHttp成了时下最火的HTTP框架,于是我也开始放下Volley,转而关注OkHttp,五一期间仔细看了官方WiKi介绍(我喜欢学习官方的文档),现在把自己...

           最近半年来身边开发的朋友越来越多的提到OkHttp,上谷歌百度一下,确实OkHttp成了时下最火的HTTP框架,于是我也开始放下Volley,转而关注OkHttp,五一期间仔细看了官方WiKi介绍(我喜欢学习官方的文档),现在把自己整理的官方教程分享给大家,希望给初学者带来帮助。
           OkHttp官网地址:http://square.github.io/okhttp/
           OkHttp GitHub地址:https://github.com/square/okhttp
    官网的自我介绍:
           HTTP is the way modern applications network. It’s how we exchange data & media. Doing HTTP efficiently makes your stuff load faster and saves bandwidth.
    OkHttp is an HTTP client that’s efficient by default:

    •   HTTP/2 support allows all requests to the same host to share a socket.
    •   Connection pooling reduces request latency (if HTTP/2 isn’t available).
    •   Transparent GZIP shrinks download sizes.
    •   Response caching avoids the network completely for repeat requests.

           OkHttp perseveres when the network is troublesome: it will silently recover from common connection problems. If your service has multiple IP addresses OkHttp will attempt alternate addresses if the first connect fails. This is necessary for IPv4+IPv6 and for services hosted in redundant data centers. OkHttp initiates new connections with modern TLS features (SNI, ALPN), and falls back to TLS 1.0 if the handshake fails.
           Using OkHttp is easy. Its request/response API is designed with fluent builders and immutability. It supports both synchronous blocking calls and async calls with callbacks.
           OkHttp supports Android 2.3 and above. For Java, the minimum requirement is 1.7.
           概括起来说OkHttp是一款优秀的HTTP框架,它支持get请求和post请求,支持基于Http的文件上传和下载,支持加载图片,支持下载文件透明的GZIP压缩,支持响应缓存避免重复的网络请求,支持使用连接池来降低响应延迟问题。

    配置方法

    (一)导入Jar包
    点击下面链接下载最新v3.2.0 JAR
    http://repo1.maven.org/maven2/com/squareup/okhttp3/okhttp/3.2.0/okhttp-3.2.0.jar
    (二)通过构建方式导入
    MAVEN

    <dependency>
      <groupId>com.squareup.okhttp3</groupId>
      <artifactId>okhttp</artifactId>
      <version>3.2.0</version>
    </dependency>

    GRADLE

    compile 'com.squareup.okhttp3:okhttp:3.2.0'

    基本要求

    Requests(请求)

           每一个HTTP请求中都应该包含一个URL,一个GET或POST方法以及Header或其他参数,当然还可以含特定内容类型的数据流。

    Responses(响应)

           响应则包含一个回复代码(200代表成功,404代表未找到),Header和定制可选的body。

    基本使用

           在日常开发中最常用到的网络请求就是GET和POST两种请求方式。

    HTTP GET

    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是OkHttp中访问的请求,Builder是辅助类,Response即OkHttp中的响应。
    Response类:

    public boolean isSuccessful()
    Returns true if the code is in [200..300), which means the request was successfully received, understood, and accepted.
    response.body()返回ResponseBody类

    可以方便的获取string

    public final String string() throws IOException
    Returns the response as a string decoded with the charset of the Content-Type header. If that header is either absent or lacks a charset, this will attempt to decode the response body as UTF-8.
    Throws:
    IOException

    当然也能获取到流的形式:
    public final InputStream byteStream()

    HTTP POST

    POST提交Json数据

    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);
        }
    }

    使用Request的post方法来提交请求体RequestBody
    POST提交键值对
    OkHttp也可以通过POST方式把键值对数据传送到服务器

    OkHttpClient client = new OkHttpClient();
    String post(String url, String json) throws IOException {
        RequestBody formBody = new FormEncodingBuilder()
        .add("platform", "android")
        .add("name", "bug")
        .add("subject", "XXXXXXXXXXXXXXX")
        .build();
    
        Request request = new Request.Builder()
          .url(url)
          .post(body)
          .build();
    
        Response response = client.newCall(request).execute();
        if (response.isSuccessful()) {
            return response.body().string();
        } else {
            throw new IOException("Unexpected code " + response);
        }
    }

    案例

    布局文件:

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
                  android:layout_width="match_parent"
                  android:layout_height="match_parent"
                  android:orientation="vertical">
    
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center_horizontal"
            android:orientation="horizontal">
    
            <Button
                android:id="@+id/bt_get"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="乌云Get请求"/>
    
            <Button
                android:id="@+id/bt_post"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="乌云Post请求"/>
    
        </LinearLayout>
    
        <TextView
            android:id="@+id/tv_show"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"/>
    </LinearLayout>

    这里写图片描述
    Java代码:
    由于android本身是不允许在UI线程做网络请求操作的,所以我们自己写个线程完成网络操作

    import android.os.Bundle;
    import android.support.v7.app.AppCompatActivity;
    import android.util.Log;
    import android.view.View;
    import android.widget.Button;
    
    import com.squareup.okhttp.FormEncodingBuilder;
    import com.squareup.okhttp.OkHttpClient;
    import com.squareup.okhttp.Request;
    import com.squareup.okhttp.RequestBody;
    import com.squareup.okhttp.Response;
    
    public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    
        private Button bt_get;
        private Button bt_post;
    
        final OkHttpClient client = new OkHttpClient();
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main2);
    
            bt_get=(Button)findViewById(R.id.bt_get);
            bt_post=(Button)findViewById(R.id.bt_post);
    
            bt_get.setOnClickListener(this);
            bt_post.setOnClickListener(this);
    
        }
    
        @Override
        public void onClick(View view) {
            switch (view.getId()){
                case R.id.bt_get:
                    getRequest();
                    break;
    
                case R.id.bt_post:
                    postRequest();
                    break;
    
            }
        }
    
        private void getRequest() {
    
            final Request request=new Request.Builder()
                    .get()
                    .tag(this)
                    .url("http://www.wooyun.org")
                    .build();
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    Response response = null;
                    try {
                        response = client.newCall(request).execute();
                        if (response.isSuccessful()) {
                            Log.i("WY","打印GET响应的数据:" + response.body().string());
                        } else {
                            throw new IOException("Unexpected code " + response);
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
    
        }
    
        private void postRequest() {
    
            RequestBody formBody = new FormEncodingBuilder()
                    .add("","")
                    .build();
    
            final Request request = new Request.Builder()
                    .url("http://www.wooyun.org")
                    .post(formBody)
                    .build();
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    Response response = null;
                    try {
                        response = client.newCall(request).execute();
                        if (response.isSuccessful()) {
                            Log.i("WY","打印POST响应的数据:" + response.body().string());
                        } else {
                            throw new IOException("Unexpected code " + response);
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
    
        }
    
    }

    执行结果:
    这里写图片描述

    官方Recipes

    Synchronous Get(同步Get)

    下载一个文件,打印他的响应头,以string形式打印响应体。
           响应体的 string() 方法对于小文档来说十分方便、高效。但是如果响应体太大(超过1MB),应避免适应 string()方法 ,因为他会将把整个文档加载到内存中。对于超过1MB的响应body,应使用流的方式来处理body。

    private final OkHttpClient client = new OkHttpClient();
    
      public void run() throws Exception {
        Request request = new Request.Builder()
            .url("http://publicobject.com/helloworld.txt")
            .build();
    
        Response response = client.newCall(request).execute();
        if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
    
        Headers responseHeaders = response.headers();
        for (int i = 0; i < responseHeaders.size(); i++) {
          System.out.println(responseHeaders.name(i) + ": " + responseHeaders.value(i));
        }
    
        System.out.println(response.body().string());
      }

    Asynchronous Get(异步Get)

           在一个工作线程中下载文件,当响应可读时回调Callback接口。读取响应时会阻塞当前线程。OkHttp现阶段不提供异步api来接收响应体。

    private final OkHttpClient client = new OkHttpClient();
    
      public void run() throws Exception {
        Request request = new Request.Builder()
            .url("http://publicobject.com/helloworld.txt")
            .build();
    
        client.newCall(request).enqueue(new Callback() {
          @Override public void onFailure(Call call, IOException e) {
            e.printStackTrace();
          }
    
          @Override public void onResponse(Call call, Response response) throws IOException {
            if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
    
            Headers responseHeaders = response.headers();
            for (int i = 0, size = responseHeaders.size(); i < size; i++) {
              System.out.println(responseHeaders.name(i) + ": " + responseHeaders.value(i));
            }
    
            System.out.println(response.body().string());
          }
        });
      }

    Accessing Headers(提取响应头)

    典型的HTTP头 像是一个 Map

    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"));
      }

    Posting a String(Post方式提交String)

           使用HTTP POST提交请求到服务。这个例子提交了一个markdown文档到web服务,以HTML方式渲染markdown。因为整个请求体都在内存中,因此避免使用此api提交大文档(大于1MB)。

    public static final MediaType MEDIA_TYPE_MARKDOWN
          = MediaType.parse("text/x-markdown; charset=utf-8");
    
      private final OkHttpClient client = new OkHttpClient();
    
      public void run() throws Exception {
        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();
    
        Response response = client.newCall(request).execute();
        if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
    
        System.out.println(response.body().string());
      }

    Post Streaming(Post方式提交流)

           以流的方式POST提交请求体。请求体的内容由流写入产生。这个例子是流直接写入Okio的BufferedSink。你的程序可能会使用OutputStream,你可以使用BufferedSink.outputStream()来获取。.

    public static final MediaType MEDIA_TYPE_MARKDOWN
          = MediaType.parse("text/x-markdown; charset=utf-8");
    
      private final OkHttpClient client = new OkHttpClient();
    
      public void run() throws Exception {
        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();
    
        Response response = client.newCall(request).execute();
        if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
    
        System.out.println(response.body().string());
      }

    Posting a File(Post方式提交文件)

    以文件作为请求体是十分简单的。

    public static final MediaType MEDIA_TYPE_MARKDOWN
          = MediaType.parse("text/x-markdown; charset=utf-8");
    
      private final OkHttpClient client = new OkHttpClient();
    
      public void run() throws Exception {
        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();
    
        Response response = client.newCall(request).execute();
        if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
    
        System.out.println(response.body().string());
      }

    Posting form parameters(Post方式提交表单)

           使用FormEncodingBuilder来构建和HTML标签相同效果的请求体。键值对将使用一种HTML兼容形式的URL编码来进行编码。

    private final OkHttpClient client = new OkHttpClient();
    
      public void run() throws Exception {
        RequestBody formBody = new FormBody.Builder()
            .add("search", "Jurassic Park")
            .build();
        Request request = new Request.Builder()
            .url("https://en.wikipedia.org/w/index.php")
            .post(formBody)
            .build();
    
        Response response = client.newCall(request).execute();
        if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
    
        System.out.println(response.body().string());
      }

    Posting a multipart request(Post方式提交分块请求)

           MultipartBuilder可以构建复杂的请求体,与HTML文件上传形式兼容。多块请求体中每块请求都是一个请求体,可以定义自己的请求头。这些请求头可以用来描述这块请求,例如他的Content-Disposition。如果Content-Length和Content-Type可用的话,他们会被自动添加到请求头中。

    private static final String IMGUR_CLIENT_ID = "...";
      private static final MediaType MEDIA_TYPE_PNG = MediaType.parse("image/png");
    
      private final OkHttpClient client = new OkHttpClient();
    
      public void run() throws Exception {
        // Use the imgur image upload API as documented at https://api.imgur.com/endpoints/image
        RequestBody requestBody = new MultipartBody.Builder()
            .setType(MultipartBody.FORM)
            .addFormDataPart("title", "Square Logo")
            .addFormDataPart("image", "logo-square.png",
                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();
    
        Response response = client.newCall(request).execute();
        if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
    
        System.out.println(response.body().string());
      }

    Parse a JSON Response With Gson(使用GSON解析JSON响应)

           Gson是一个在JSON和Java对象之间转换非常方便的api。这里我们用Gson来解析Github API的JSON响应。
    注意:ResponseBody.charStream()使用响应头Content-Type指定的字符集来解析响应体。默认是UTF-8。

    private final OkHttpClient client = new OkHttpClient();
      private final Gson gson = new Gson();
    
      public void run() throws Exception {
        Request request = new Request.Builder()
            .url("https://api.github.com/gists/c2a7c39532239ff261be")
            .build();
        Response response = client.newCall(request).execute();
        if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
    
        Gist gist = gson.fromJson(response.body().charStream(), Gist.class);
        for (Map.Entry<String, GistFile> entry : gist.files.entrySet()) {
          System.out.println(entry.getKey());
          System.out.println(entry.getValue().content);
        }
      }
    
      static class Gist {
        Map<String, GistFile> files;
      }
    
      static class GistFile {
        String content;
      }

    Response Caching(响应缓存)

           为了缓存响应,你需要一个你可以读写的缓存目录,和缓存大小的限制。这个缓存目录应该是私有的,不信任的程序应不能读取缓存内容。
           一个缓存目录同时拥有多个缓存访问是错误的。大多数程序只需要调用一次new OkHttp(),在第一次调用时配置好缓存,然后其他地方只需要调用这个实例就可以了。否则两个缓存示例互相干扰,破坏响应缓存,而且有可能会导致程序崩溃。
           响应缓存使用HTTP头作为配置。你可以在请求头中添加Cache-Control: max-stale=3600 ,OkHttp缓存会支持。你的服务通过响应头确定响应缓存多长时间,例如使用Cache-Control: max-age=9600。

    private final OkHttpClient client;
    
      public CacheResponse(File cacheDirectory) throws Exception {
        int cacheSize = 10 * 1024 * 1024; // 10 MiB
        Cache cache = new Cache(cacheDirectory, cacheSize);
    
        client = new OkHttpClient.Builder()
            .cache(cache)
            .build();
      }
    
      public void run() throws Exception {
        Request request = new Request.Builder()
            .url("http://publicobject.com/helloworld.txt")
            .build();
    
        Response response1 = client.newCall(request).execute();
        if (!response1.isSuccessful()) throw new IOException("Unexpected code " + response1);
    
        String response1Body = response1.body().string();
        System.out.println("Response 1 response:          " + response1);
        System.out.println("Response 1 cache response:    " + response1.cacheResponse());
        System.out.println("Response 1 network response:  " + response1.networkResponse());
    
        Response response2 = client.newCall(request).execute();
        if (!response2.isSuccessful()) throw new IOException("Unexpected code " + response2);
    
        String response2Body = response2.body().string();
        System.out.println("Response 2 response:          " + response2);
        System.out.println("Response 2 cache response:    " + response2.cacheResponse());
        System.out.println("Response 2 network response:  " + response2.networkResponse());
    
        System.out.println("Response 2 equals Response 1? " + response1Body.equals(response2Body));
      }

           为了防止使用缓存的响应,可以用CacheControl.FORCE_NETWORK。为了防止它使用网络,使用CacheControl.FORCE_CACHE。需要注意的是:如果您使用FORCE_CACHE和网络的响应需求,OkHttp则会返回一个504提示,告诉你不可满足请求响应。
           Canceling a Call(取消一个Call)
           使用Call.cancel()可以立即停止掉一个正在执行的call。如果一个线程正在写请求或者读响应,将会引发IOException。当call没有必要的时候,使用这个api可以节约网络资源。例如当用户离开一个应用时。不管同步还是异步的call都可以取消。
           你可以通过tags来同时取消多个请求。当你构建一请求时,使用RequestBuilder.tag(tag)来分配一个标签。之后你就可以用OkHttpClient.cancel(tag)来取消所有带有这个tag的call。.

     private final ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
      private final OkHttpClient client = new OkHttpClient();
    
      public void run() throws Exception {
        Request request = new Request.Builder()
            .url("http://httpbin.org/delay/2") // This URL is served with a 2 second delay.
            .build();
    
        final long startNanos = System.nanoTime();
        final Call call = client.newCall(request);
    
        // Schedule a job to cancel the call in 1 second.
        executor.schedule(new Runnable() {
          @Override public void run() {
            System.out.printf("%.2f Canceling call.%n", (System.nanoTime() - startNanos) / 1e9f);
            call.cancel();
            System.out.printf("%.2f Canceled call.%n", (System.nanoTime() - startNanos) / 1e9f);
          }
        }, 1, TimeUnit.SECONDS);
    
        try {
          System.out.printf("%.2f Executing call.%n", (System.nanoTime() - startNanos) / 1e9f);
          Response response = call.execute();
          System.out.printf("%.2f Call was expected to fail, but completed: %s%n",
              (System.nanoTime() - startNanos) / 1e9f, response);
        } catch (IOException e) {
          System.out.printf("%.2f Call failed as expected: %s%n",
              (System.nanoTime() - startNanos) / 1e9f, e);
        }
      }

    Timeouts(超时)

           没有响应时使用超时结束call。没有响应的原因可能是客户点链接问题、服务器可用性问题或者这之间的其他东西。OkHttp支持连接,读取和写入超时。

    private final OkHttpClient client;
    
      public ConfigureTimeouts() throws Exception {
        client = new OkHttpClient.Builder()
            .connectTimeout(10, TimeUnit.SECONDS)
            .writeTimeout(10, TimeUnit.SECONDS)
            .readTimeout(30, TimeUnit.SECONDS)
            .build();
      }
    
      public void run() throws Exception {
        Request request = new Request.Builder()
            .url("http://httpbin.org/delay/2") // This URL is served with a 2 second delay.
            .build();
    
        Response response = client.newCall(request).execute();
        System.out.println("Response completed: " + response);
      }

    Per-call Configuration(每个Call的配置)

           使用OkHttpClient,所有的HTTP Client配置包括代理设置、超时设置、缓存设置。当你需要为单个call改变配置的时候,clone 一个 OkHttpClient。这个api将会返回一个浅拷贝(shallow copy),你可以用来单独自定义。下面的例子中,我们让一个请求是500ms的超时、另一个是3000ms的超时。

    private final OkHttpClient client = new OkHttpClient();
    
      public void run() throws Exception {
        Request request = new Request.Builder()
            .url("http://httpbin.org/delay/1") // This URL is served with a 1 second delay.
            .build();
    
        try {
          // Copy to customize OkHttp for this request.
          OkHttpClient copy = client.newBuilder()
              .readTimeout(500, TimeUnit.MILLISECONDS)
              .build();
    
          Response response = copy.newCall(request).execute();
          System.out.println("Response 1 succeeded: " + response);
        } catch (IOException e) {
          System.out.println("Response 1 failed: " + e);
        }
    
        try {
          // Copy to customize OkHttp for this request.
          OkHttpClient copy = client.newBuilder()
              .readTimeout(3000, TimeUnit.MILLISECONDS)
              .build();
    
          Response response = copy.newCall(request).execute();
          System.out.println("Response 2 succeeded: " + response);
        } catch (IOException e) {
          System.out.println("Response 2 failed: " + e);
        }
      }

    Handling authentication(处理验证)

           OkHttp会自动重试未验证的请求。当响应是401 Not Authorized时,Authenticator会被要求提供证书。Authenticator的实现中需要建立一个新的包含证书的请求。如果没有证书可用,返回null来跳过尝试。

     private final OkHttpClient client;
    
      public Authenticate() {
        client = new OkHttpClient.Builder()
            .authenticator(new Authenticator() {
              @Override public Request authenticate(Route route, Response response) throws IOException {
                System.out.println("Authenticating for response: " + response);
                System.out.println("Challenges: " + response.challenges());
                String credential = Credentials.basic("jesse", "password1");
                return response.request().newBuilder()
                    .header("Authorization", credential)
                    .build();
              }
            })
            .build();
      }
    
      public void run() throws Exception {
        Request request = new Request.Builder()
            .url("http://publicobject.com/secrets/hellosecret.txt")
            .build();
    
        Response response = client.newCall(request).execute();
        if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
    
        System.out.println(response.body().string());
      }

    To avoid making many retries when authentication isn’t working, you can return null to give up. For example, you may want to skip the retry when these exact credentials have already been attempted:

    if (credential.equals(response.request().header("Authorization"))) {
        return null; // If we already failed with these credentials, don't retry.
       }
    You may also skip the retry when you’ve hit an application-defined attempt limit:
      if (responseCount(response) >= 3) {
        return null; // If we've failed 3 times, give up.
      }
    This above code relies on this responseCount() method:
      private int responseCount(Response response) {
        int result = 1;
        while ((response = response.priorResponse()) != null) {
          result++;
        }
        return result;
      }

    OkHttp官方文档:https://github.com/square/okhttp/wiki
    参考链接:http://www.cnblogs.com/ct2011/p/3997368.html

    下一篇文章,我将会从具体开发的角度,封装一个高效的OkHttp框架分享给大家。

    安卓开发高级技术交流QQ群:108721298 欢迎入群

    微信公众号:mobilesafehome

    (本公众号支持投票)

    Android安全技术大本营

    展开全文
  • OkHttp使用完全教程

    万次阅读 2017-03-08 22:29:07
    OkHttp使用完全教程标签 : Http请求, OkHttp 时间 : 2016.08.02上一节我们讲述了Http请求的过程, 这一节我们就讲述下OkHttp是怎么完成Http请求的. 1. 历史上Http请求库优缺点在讲述OkHttp之前, 我们看下没有OkHttp...

    OkHttp使用完全教程

    标签 : Http请求, OkHttp
    时间 : 2016.08.02


    上一节我们讲述了Http请求的过程, 这一节我们就讲述下OkHttp是怎么完成Http请求的.
    为了更好的理解OKHttp,强烈推荐先看一下http的整个请求过程~文加图, 理解Http请求与响应,提升软实力。

    1. 历史上Http请求库优缺点

    在讲述OkHttp之前, 我们看下没有OkHttp的时代, 我们是如何完成http请求的.
    在没有OkHttp的日子, 我们使用HttpURLConnection或者HttpClient. 那么这两者都有什么优缺点呢? 为什么不在继续使用下去呢?
    HttpClient是Apache基金会的一个开源网络库, 功能十分强大, API数量众多, 但是正是由于庞大的API数量使得我们很难在不破坏兼容性的情况下对它进行升级和扩展, 所以Android团队在提升和优化HttpClient方面的工作态度并不积极.
    HttpURLConnection是一种多用途, 轻量极的HTTP客户端, 提供的API比较简单, 可以容易地去使用和扩展. 不过在Android 2.2版本之前, HttpURLConnection一直存在着一些令人厌烦的bug. 比如说对一个可读的InputStream调用close()方法时,就有可能会导致连接池失效了。那么我们通常的解决办法就是直接禁用掉连接池的功能:

    private void disableConnectionReuseIfNecessary() {    
        // 这是一个2.2版本之前的bug    
        if (Integer.parseInt(Build.VERSION.SDK) < Build.VERSION_CODES.FROYO) {    
            System.setProperty("http.keepAlive", "false");    
        }    
    }    

    因此, 一般的推荐是在2.2之前, 使用HttpClient, 因为其bug较少. 在2.2之后, 推荐使用HttpURLConnection, 因为API简单, 体积小, 并且有压缩和缓存机制, 并且Android团队后续会继续优化HttpURLConnection.

    但是, 上面两个类库和OkHttp比起来就弱爆了, 因为OkHttp不仅具有高效的请求效率, 并且提供了很多开箱即用的网络疑难杂症解决方案.
    - 支持HTTP/2, HTTP/2通过使用多路复用技术在一个单独的TCP连接上支持并发, 通过在一个连接上一次性发送多个请求来发送或接收数据
    - 如果HTTP/2不可用, 连接池复用技术也可以极大减少延时
    - 支持GZIP, 可以压缩下载体积
    - 响应缓存可以直接避免重复请求
    - 会从很多常用的连接问题中自动恢复
    - 如果您的服务器配置了多个IP地址, 当第一个IP连接失败的时候, OkHttp会自动尝试下一个IP
    - OkHttp还处理了代理服务器问题和SSL握手失败问题

    使用 OkHttp 无需重写您程序中的网络代码。OkHttp实现了几乎和java.net.HttpURLConnection一样的API。如果你用了 Apache HttpClient,则OkHttp也提供了一个对应的okhttp-apache 模块。

    还有一个好消息, 从Android 4.4起, 其HttpURLConnection的内部实现已经变为OkHttp, 您可以参考这两个网页:爆栈网Twitter.

    2. OkHttp类与http请求响应的映射

    在讲解OkHttp使用之前, 再看下我们Http请求和响应都有哪些部分组成.

    2.1 http请求


    所以一个类库要完成一个http请求, 需要包含 请求方法, 请求地址, 请求协议, 请求头, 请求体这五部分. 这些都在okhttp3.Request的类中有体现, 这个类正是代表http请求的类. 看下图:

    其中HttpUrl类代表请求地址, String method代表请求方法, Headers代表请求头, RequestBody代表请求体. Object tag这个是用来取消http请求的标志, 这个我们先不管. 这里也许你在疑惑, 请求协议呢? 为什么没有请求协议对应的类. 且听我慢慢道来, 下面就会讲到这个问题.

    2.1.1 请求协议的协商升级

    目前, Http/1.1在全世界大范围的使用中, 直接废弃跳到http/2肯定不现实. 不是每个用户的浏览器都支持http/2的, 也不是每个服务器都打算支持http/2的, 如果我们直接发送http/2格式的协议, 服务器又不支持, 那不是挂掉了! 总不能维护一个全世界的网站列表, 表示哪些支持http/2, 哪些不支持?
    为了解决这个问题, 从稍高层次上来说, 就是为了更方便地部署新协议, HTTP/1.1 引入了 Upgrade 机制. 这个机制在 RFC7230 的「6.7 Upgrade」这一节中有详细描述.
    简单说来, 就是先问下你支持http/2么? 如果你支持, 那么接下来我就用http/2和你聊天. 如果你不支持, 那么我还是用原来的http/1.1和你聊天.

    1.客户端在请求头部中指定ConnectionUpgrade两个字段发起 HTTP/1.1 协议升级. HTTP/2 的协议名称是 h2c, 代表 HTTP/2 ClearText.

    GET / HTTP/1.1
    Host: example.com
    Connection: Upgrade, HTTP2-Settings
    Upgrade: h2c
    HTTP2-Settings: <base64url encoding of HTTP/2 SETTINGS payload>

    2.如果服务端不同意升级或者不支持 Upgrade 所列出的协议,直接忽略即可(当成 HTTP/1.1 请求,以 HTTP/1.1 响应).

    HTTP/1.1 200 OK
    Content-Length: 243
    Content-Type: text/html
    
    ...

    如果服务端同意升级,那么需要这样响应:

    HTTP/1.1 101 Switching Protocols
    Connection: Upgrade
    Upgrade: h2c
    
    [ HTTP/2 connection ... ]

    HTTP Upgrade 响应的状态码是 101,并且响应正文可以使用新协议定义的数据格式。

    这样就可以完成从http/1.1升级到http/2了. 同样也可以从http/1.1升级到WebSocket.

    这样, 你就了解了为什么OkHttp没有指定具体请求协议了吧. 因为OkHttp使用了请求协议的协商升级, 无论是1.1还是2, 都先只以1.1来发送, 并在发送的信息头里包含协议升级字段. 接下来就看服务器是否支持协议升级了. OkHttp使用的协议升级字段是ALPN, 如果有兴趣, 可以更深入的查阅相关资料.

    2.1.2 OkHttp请求

    接下来我们构造一个http请求, 并查看请求具体内容.

    final Request request = new Request.Builder().url("https://github.com/").build();

    我们看下在内存中, 这个请求是什么样子的, 是否如我们上文所说和请求方法, 请求地址, 请求头, 请求体一一对应.

    2.2 http响应

    我们看下一个http响应由哪些部分组成, 先看下响应组成图:

    可以看到大体由应答首行, 应答头, 应答体构成. 但是应答首行表达的信息过多, HTTP/1.1表示访问协议, 200是响应码, OK是描述状态的消息. 根据单一职责, 我们不应该把这么多内容用一个应答首行来表示. 这样的话, 我们的响应就应该由访问协议, 响应码, 描述信息, 响应头, 响应体来组成.

    2.2.1 OkHttp响应

    我们看下OkHttp库怎么表示一个响应:

    可以看到Response类里面有Protocol代表请求协议, int code代表响应码, String message代表描述信息, Headers代表响应头, ResponseBody代表响应体. 当然除此之外, 还有Request代表持有的请求, Handshake代表SSL/TLS握手协议验证时的信息, 这些额外信息我们暂时不问.

    有了刚才说的OkHttp响应的类组成, 我们看下OkHttp请求后响应在内存中的内容:

    final Request request = new Request.Builder().url("https://github.com/").build();
    Response response = client.newCall(request).execute();


    可以看到和我们的分析十分一致.

    讲了OkHttp里的请求类和响应类, 我们接下来就可以直接讲述OkHttp的使用方法了.

    3 HTTP GET

    3.1 同步GET

    同步GET的意思是一直等待http请求, 直到返回了响应. 在这之间会阻塞进程, 所以通过get不能在Android的主线程中执行, 否则会报错.

    private final OkHttpClient client = new OkHttpClient();
    
    public void run() throws Exception {
        Request request = new Request.Builder()
            .url("http://publicobject.com/helloworld.txt")
            .build();
    
        Response response = client.newCall(request).execute();
        if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
    
        Headers responseHeaders = response.headers();
        for (int i = 0; i < responseHeaders.size(); i++) {
          System.out.println(responseHeaders.name(i) + ": " + responseHeaders.value(i));
        }
    
        System.out.println(response.body().string());
    }

    OkHttpClient实现了Call.Factory接口, 是Call的工厂类, Call负责发送执行请求和读取响应.
    Request代表Http请求, 通过Request.Builder辅助类来构建.
    client.newCall(request)通过传入一个http request, 返回一个Call调用. 然后执行execute()方法, 同步获得
    Response代表Http请求的响应. response.body()是ResponseBody类, 代表响应体, 可以通过responseBody.string()获得字符串的表达形式, 或responseBody.bytes()获得字节数组的表达形式, 这两种形式都会把文档加入到内存. 也可以通过responseBody.charStream()和responseBody.byteStream()返回流来处理.

    上述代码完成的功能是下载一个文件, 打印他的响应头, 以string形式打印响应体.
    响应体的string()方法对于小文档来说十分方便高效. 但是如果响应体太大(超过1MB), 应避免使用 string()方法, 因为它会将把整个文档加载到内存中.
    对于超过1MB的响应body, 应使用流的方式来处理响应body. 这和我们处理xml文档的逻辑是一致的, 小文件可以载入内存树状解析, 大文件就必须流式解析.

    3.2 异步GET

    异步GET是指在另外的工作线程中执行http请求, 请求时不会阻塞当前的线程, 所以可以在Android主线程中使用.
    下面是在一个工作线程中下载文件, 当响应可读时回调Callback接口. 当响应头准备好后, 就会调用Callback接口, 所以读取响应体时可能会阻塞. OkHttp现阶段不提供异步api来接收响应体。

    private final OkHttpClient client = new OkHttpClient();
    
    public void run() throws Exception {
        Request request = new Request.Builder()
            .url("http://publicobject.com/helloworld.txt")
            .build();
    
        client.newCall(request).enqueue(new Callback() {
          @Override public void onFailure(Request request, Throwable throwable) {
            throwable.printStackTrace();
          }
    
          @Override public void onResponse(Response response) throws IOException {
            if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
    
            Headers responseHeaders = response.headers();
            for (int i = 0; i < responseHeaders.size(); i++) {
              System.out.println(responseHeaders.name(i) + ": " + responseHeaders.value(i));
            }
    
            System.out.println(response.body().string());
          }
        });
    }

    4 HTTP POST

    4.1 Post方式提交String

    下面是使用HTTP POST提交请求到服务. 这个例子提交了一个markdown文档到web服务, 以HTML方式渲染markdown. 因为整个请求体都在内存中, 因此避免使用此api提交大文档(大于1MB).

    public static final MediaType MEDIA_TYPE_MARKDOWN
      = MediaType.parse("text/x-markdown; charset=utf-8");
    
    private final OkHttpClient client = new OkHttpClient();
    
    public void run() throws Exception {
        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();
    
        Response response = client.newCall(request).execute();
        if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
    
        System.out.println(response.body().string());
    }

    4.2 Post方式提交流

    以流的方式POST提交请求体. 请求体的内容由流写入产生. 这个例子是流直接写入Okio的BufferedSink. 你的程序可能会使用OutputStream, 你可以使用BufferedSink.outputStream()来获取. OkHttp的底层对流和字节的操作都是基于Okio库, Okio库也是Square开发的另一个IO库, 填补I/O和NIO的空缺, 目的是提供简单便于使用的接口来操作IO.

    public static final MediaType MEDIA_TYPE_MARKDOWN
          = MediaType.parse("text/x-markdown; charset=utf-8");
    
    private final OkHttpClient client = new OkHttpClient();
    
    public void run() throws Exception {
        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();
    
        Response response = client.newCall(request).execute();
        if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
    
        System.out.println(response.body().string());
    }

    4.3 Post方式提交文件

    以文件作为请求体是十分简单的。

    public static final MediaType MEDIA_TYPE_MARKDOWN
      = MediaType.parse("text/x-markdown; charset=utf-8");
    
    private final OkHttpClient client = new OkHttpClient();
    
    public void run() throws Exception {
        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();
    
        Response response = client.newCall(request).execute();
        if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
    
        System.out.println(response.body().string());
    }

    4.4 Post方式提交表单

    使用FormEncodingBuilder来构建和HTML<form>标签相同效果的请求体. 键值对将使用一种HTML兼容形式的URL编码来进行编码.

     private final OkHttpClient client = new OkHttpClient();
    
      public void run() throws Exception {
        RequestBody formBody = new FormBody.Builder()
            .add("search", "Jurassic Park")
            .build();
        Request request = new Request.Builder()
            .url("https://en.wikipedia.org/w/index.php")
            .post(formBody)
            .build();
    
        Response response = client.newCall(request).execute();
        if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
    
        System.out.println(response.body().string());
      }

    4.5 Post方式提交分块请求

    MultipartBody.Builder可以构建复杂的请求体, 与HTML文件上传形式兼容. 多块请求体中每块请求都是一个请求体, 可以定义自己的请求头. 这些请求头可以用来描述这块请求, 例如它的Content-Disposition. 如果Content-Length和Content-Type可用的话, 他们会被自动添加到请求头中.

    private static final String IMGUR_CLIENT_ID = "...";
      private static final MediaType MEDIA_TYPE_PNG = MediaType.parse("image/png");
    
      private final OkHttpClient client = new OkHttpClient();
    
      public void run() throws Exception {
        // Use the imgur image upload API as documented at https://api.imgur.com/endpoints/image
        RequestBody requestBody = new MultipartBody.Builder()
            .setType(MultipartBody.FORM)
            .addFormDataPart("title", "Square Logo")
            .addFormDataPart("image", "logo-square.png",
                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();
    
        Response response = client.newCall(request).execute();
        if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
    
        System.out.println(response.body().string());
      }

    5. 其他用法

    5.1 提取响应头

    典型的HTTP头像是一个Map<String, String> : 每个字段都有一个或没有值. 但是一些头允许多个值, 像Guava的Multimap.
    例如: HTTP响应里面提供的Vary响应头, 就是多值的. OkHttp的api试图让这些情况都适用.
    当写请求头的时候, 使用header(name, value)可以设置唯一的name、value. 如果已经有值, 旧的将被移除, 然后添加新的. 使用addHeader(name, value)可以添加多值(添加, 不移除已有的).
    当读取响应头时, 使用header(name)返回最后出现的name、value. 通常情况这也是唯一的name、value. 如果没有值, 那么header(name)将返回null. 如果想读取字段对应的所有值, 使用headers(name)会返回一个list.
    为了获取所有的Header, Headers类支持按index访问.

    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"));
    }

    5.2 使用Gson来解析JSON响应

    Gson是一个在JSON和Java对象之间转换非常方便的api库. 这里我们用Gson来解析Github API的JSON响应.
    注意: ResponseBody.charStream()使用响应头Content-Type指定的字符集来解析响应体. 默认是UTF-8.

    private final OkHttpClient client = new OkHttpClient();
      private final Gson gson = new Gson();
    
      public void run() throws Exception {
        Request request = new Request.Builder()
            .url("https://api.github.com/gists/c2a7c39532239ff261be")
            .build();
        Response response = client.newCall(request).execute();
        if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
    
        Gist gist = gson.fromJson(response.body().charStream(), Gist.class);
        for (Map.Entry<String, GistFile> entry : gist.files.entrySet()) {
          System.out.println(entry.getKey());
          System.out.println(entry.getValue().content);
        }
      }
    
      static class Gist {
        Map<String, GistFile> files;
      }
    
      static class GistFile {
        String content;
      }

    5.3 响应缓存

    为了缓存响应, 你需要一个你可以读写的缓存目录, 和缓存大小的限制. 这个缓存目录应该是私有的, 不信任的程序应不能读取缓存内容.
    一个缓存目录同时拥有多个缓存访问是错误的. 大多数程序只需要调用一次new OkHttp(), 在第一次调用时配置好缓存, 然后其他地方只需要调用这个实例就可以了. 否则两个缓存示例互相干扰, 破坏响应缓存, 而且有可能会导致程序崩溃.
    响应缓存使用HTTP头作为配置. 你可以在请求头中添加Cache-Control: max-stale=3600 , OkHttp缓存会支持. 你的服务通过响应头确定响应缓存多长时间, 例如使用Cache-Control: max-age=9600.

    private final OkHttpClient client;
    
    public CacheResponse(File cacheDirectory) throws Exception {
        int cacheSize = 10 * 1024 * 1024; // 10 MiB
        Cache cache = new Cache(cacheDirectory, cacheSize);
    
        client = new OkHttpClient();
        client.setCache(cache);
    }
    
    public void run() throws Exception {
        Request request = new Request.Builder()
            .url("http://publicobject.com/helloworld.txt")
            .build();
    
        Response response1 = client.newCall(request).execute();
        if (!response1.isSuccessful()) throw new IOException("Unexpected code " + response1);
    
        String response1Body = response1.body().string();
        System.out.println("Response 1 response:          " + response1);
        System.out.println("Response 1 cache response:    " + response1.cacheResponse());
        System.out.println("Response 1 network response:  " + response1.networkResponse());
    
        Response response2 = client.newCall(request).execute();
        if (!response2.isSuccessful()) throw new IOException("Unexpected code " + response2);
    
        String response2Body = response2.body().string();
        System.out.println("Response 2 response:          " + response2);
        System.out.println("Response 2 cache response:    " + response2.cacheResponse());
        System.out.println("Response 2 network response:  " + response2.networkResponse());
    
        System.out.println("Response 2 equals Response 1? " + response1Body.equals(response2Body));
    }

    如果需要阻值response使用缓存, 使用CacheControl.FORCE_NETWORK. 如果需要阻值response使用网络, 使用CacheControl.FORCE_CACHE.
    警告: 如果你使用FORCE_CACHE, 但是response要求使用网络, OkHttp将会返回一个504 Unsatisfiable Request响应.

    5.3.1 Force a Network Response

    有些时候, 比如用户刚刚点击刷新按钮, 这时必须跳过缓存, 直接从服务器抓取数据. 为了强制全面刷新, 我们需要添加no-cache指令:

    connection.addRequestProperty("Cache-Control", "no-cache");

    这样就可以强制每次请求直接发送给源服务器, 而不经过本地缓存版本的校验, 常用于需要确认认证的应用和严格要求使用最新数据的应用.

    5.3.2 Force a Cache Response

    有时你会想立即显示资源. 这样即使在后台正下载着最新资源, 你的客户端仍然可以先显示原有资源, 毕竟有个东西显示比没有东西显示要好.
    如果需要限制让请求优先使用本地缓存资源, 需要增加only-if-cached指令:

    try {
         connection.addRequestProperty("Cache-Control", "only-if-cached");
         InputStream cached = connection.getInputStream();
         // the resource was cached! show it
      catch (FileNotFoundException e) {
         // the resource was not cached
     }
    }

    5.4 取消一个Call

    使用Call.cancel()可以立即停止掉一个正在执行的call. 如果一个线程正在写请求或者读响应, 将会引发IOException. 当call没有必要的时候, 使用这个api可以节约网络资源. 例如当用户离开一个应用时, 不管同步还是异步的call都可以取消.
    你可以通过tags来同时取消多个请求. 当你构建一请求时, 使用RequestBuilder.tag(tag)来分配一个标签, 之后你就可以用OkHttpClient.cancel(tag)来取消所有带有这个tag的call.

      private final ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
      private final OkHttpClient client = new OkHttpClient();
    
      public void run() throws Exception {
        Request request = new Request.Builder()
            .url("http://httpbin.org/delay/2") // This URL is served with a 2 second delay.
            .build();
    
        final long startNanos = System.nanoTime();
        final Call call = client.newCall(request);
    
        // Schedule a job to cancel the call in 1 second.
        executor.schedule(new Runnable() {
          @Override public void run() {
            System.out.printf("%.2f Canceling call.%n", (System.nanoTime() - startNanos) / 1e9f);
            call.cancel();
            System.out.printf("%.2f Canceled call.%n", (System.nanoTime() - startNanos) / 1e9f);
          }
        }, 1, TimeUnit.SECONDS);
    
        try {
          System.out.printf("%.2f Executing call.%n", (System.nanoTime() - startNanos) / 1e9f);
          Response response = call.execute();
          System.out.printf("%.2f Call was expected to fail, but completed: %s%n",
              (System.nanoTime() - startNanos) / 1e9f, response);
        } catch (IOException e) {
          System.out.printf("%.2f Call failed as expected: %s%n",
              (System.nanoTime() - startNanos) / 1e9f, e);
        }
      }

    5.5 超时

    没有响应时使用超时结束call. 没有响应的原因可能是客户点链接问题、服务器可用性问题或者这之间的其他东西. OkHttp支持连接超时, 读取超时和写入超时.

      private final OkHttpClient client;
    
      public ConfigureTimeouts() throws Exception {
        client = new OkHttpClient.Builder()
            .connectTimeout(10, TimeUnit.SECONDS)
            .writeTimeout(10, TimeUnit.SECONDS)
            .readTimeout(30, TimeUnit.SECONDS)
            .build();
      }
    
      public void run() throws Exception {
        Request request = new Request.Builder()
            .url("http://httpbin.org/delay/2") // This URL is served with a 2 second delay.
            .build();
    
        Response response = client.newCall(request).execute();
        System.out.println("Response completed: " + response);
      }

    5.6 每个call的配置

    使用OkHttpClient, 所有的HTTP Client配置包括代理设置、超时设置、缓存设置. 当你需要为单个call改变配置的时候, 调用OkHttpClient.newBuilder(). 这个api将会返回一个builder, 这个builder和原始的client共享相同的连接池, 分发器和配置.
    下面的例子中,我们让一个请求是500ms的超时、另一个是3000ms的超时。

      private final OkHttpClient client = new OkHttpClient();
    
      public void run() throws Exception {
        Request request = new Request.Builder()
            .url("http://httpbin.org/delay/1") // This URL is served with a 1 second delay.
            .build();
    
        try {
          // Copy to customize OkHttp for this request.
          OkHttpClient copy = client.newBuilder()
              .readTimeout(500, TimeUnit.MILLISECONDS)
              .build();
    
          Response response = copy.newCall(request).execute();
          System.out.println("Response 1 succeeded: " + response);
        } catch (IOException e) {
          System.out.println("Response 1 failed: " + e);
        }
    
        try {
          // Copy to customize OkHttp for this request.
          OkHttpClient copy = client.newBuilder()
              .readTimeout(3000, TimeUnit.MILLISECONDS)
              .build();
    
          Response response = copy.newCall(request).execute();
          System.out.println("Response 2 succeeded: " + response);
        } catch (IOException e) {
          System.out.println("Response 2 failed: " + e);
        }
      }

    5.7 处理验证

    这部分和HTTP AUTH有关.

    5.7.1 HTTP AUTH

    使用HTTP AUTH需要在server端配置http auth信息, 其过程如下:
    - 客户端发送http请求
    - 服务器发现配置了http auth, 于是检查request里面有没有”Authorization”的http header
    - 如果有, 则判断Authorization里面的内容是否在用户列表里面, Authorization header的典型数据为”Authorization: Basic jdhaHY0=”, 其中Basic表示基础认证, jdhaHY0=是base64编码的”user:passwd”字符串. 如果没有,或者用户密码不对,则返回http code 401页面给客户端.
    - 标准的http浏览器在收到401页面之后, 应该弹出一个对话框让用户输入帐号密码; 并在用户点确认的时候再次发出请求, 这次请求里面将带上Authorization header.

    一次典型的访问场景是:

    • 浏览器发送http请求(没有Authorization header)
    • 服务器端返回401页面
    • 浏览器弹出认证对话框
    • 用户输入帐号密码,并点确认
    • 浏览器再次发出http请求(带着Authorization header)
    • 服务器端认证通过,并返回页面
    • 浏览器显示页面

    5.7.2 OkHttp认证

    OkHttp会自动重试未验证的请求. 当响应是401 Not Authorized时,Authenticator会被要求提供证书. Authenticator的实现中需要建立一个新的包含证书的请求. 如果没有证书可用, 返回null来跳过尝试.
    使用Response.challenges()来获得任何authentication challenges的 schemes 和 realms. 当完成一个Basic challenge, 使用Credentials.basic(username, password)来解码请求头.

      private final OkHttpClient client;
    
      public Authenticate() {
        client = new OkHttpClient.Builder()
            .authenticator(new Authenticator() {
              @Override public Request authenticate(Route route, Response response) throws IOException {
                System.out.println("Authenticating for response: " + response);
                System.out.println("Challenges: " + response.challenges());
                String credential = Credentials.basic("jesse", "password1");
                return response.request().newBuilder()
                    .header("Authorization", credential)
                    .build();
              }
            })
            .build();
      }
    
      public void run() throws Exception {
        Request request = new Request.Builder()
            .url("http://publicobject.com/secrets/hellosecret.txt")
            .build();
    
        Response response = client.newCall(request).execute();
        if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
    
        System.out.println(response.body().string());
      }

    当认证无法工作时, 为了避免多次重试, 你可以返回空来放弃认证. 例如, 当exact credentials已经尝试过, 你可能会直接想跳过认证, 可以这样做:

      if (credential.equals(response.request().header("Authorization"))) {
        return null; // If we already failed with these credentials, don't retry.
       }

    当重试次数超过定义的次数, 你若想跳过认证, 可以这样做:

      if (responseCount(response) >= 3) {
        return null; // If we've failed 3 times, give up.
      }
    
      private int responseCount(Response response) {
        int result = 1;
        while ((response = response.priorResponse()) != null) {
          result++;
        }
        return result;
      }

    这样, 对OkHttp的使用我们就讲完了, 下一节会讲OkHttp内部实现.

    谢谢下列文章:
    http://www.blogjava.net/yongboy/archive/2015/03/18/423570.html
    http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0106/2275.html
    http://www.jianshu.com/p/aad5aacd79bf
    https://imququ.com/post/protocol-negotiation-in-http2.html
    http://blog.csdn.net/wwwsq/article/details/7255062

    展开全文
  • Okhttp的简单介绍和使用(一)

    万次阅读 热门讨论 2016-01-28 10:20:47
    通过本篇可以了解到: 1、okhttp是什么 2、okhttp的优点 3、okhttp的简单使用

    前言:

    通过本篇,可以了解一下几点:

    • Android中网络请求进化
    • okhttp是什么
    • okhttp的简单使用

    如果有兴趣,可以看下我的另一篇,okhttp的简单封装:

    网络请求发展:

    • HttpURLConnection—>Apache HTTP Client—>Volley—->okHttp

    OkHttp是什么:

    OkHttp是一个高效的HTTP库:

    • 1.支持 SPDY ,共享同一个Socket来处理同一个服务器的所有请求
    • 2.如果SPDY不可用,则通过连接池来减少请求延时
    • 3.无缝的支持GZIP来减少数据流量
    • 4.缓存响应数据来减少重复的网络请求

    优点:

    • OkHttp会从很多常用的连接问题中自动恢复。如果您的服务器配置了多个IP地址,当第一个IP连接失败的时候,会自动尝试下一个IP。OkHttp还处理了代理服务器问题和SSL握手失败问题。
    • 使用 OkHttp 无需重写您程序中的网络代码。OkHttp实现了几乎和java.net.HttpURLConnection一样的API。如果您用了 Apache HttpClient,则OkHttp也提供了一个对应的okhttp-apache 模块

    Okhttp的基本使用,从以下五方面讲解:

    • 1.Get请求(同步和异步)
    • 2.POST请求表单(key-value)
    • 3.POST请求提交(JSON/String/文件等)(这个有待研究)
    • 4.文件下载
    • 5.请求超时设置

    GET请求同步方法

           OkHttpClient client=new OkHttpClient();
           Request request = new Request.Builder().url(url) .build();
           Response response= client.newCall(request).execute();
           String message=response.body().string();

    GET请求异步方法

            OkHttpClient client=new OkHttpClient();
            Request request = new Request.Builder().url(url).build();
            client.newCall(request).enqueue(new Callback() {
                        @Override
                        public void onFailure(Request request, IOException e) {
                        }
                        @Override
                        public void onResponse(Response response) throws IOException {
                        }
             });
    

    POST请求提交

    在这里解释一下,我在用3.0的时候,去找FormEncodingBuilder(),这个方法,找不到了,就去了他的官网查看了信息,找到了一下一段解释:

    • We’ve replaced the opaque FormEncodingBuilder with the more powerful FormBody and FormBody.Builder combo. Similarly we’ve upgraded MultipartBuilder into MultipartBody, MultipartBody.Part, and MultipartBody.Builder.

    okhttp3.0之前:

          OkHttpClient client = new OkHttpClient();
          RequestBody forBody = new FormEncodingBuilder().add("username","tom").add("password", "1110").build();
                    Request request=new Request.Builder().url(url).post(forBody).build();
                    client.newCall(request).enqueue(new Callback() {
                        @Override
                        public void onFailure(Request request, IOException e) {
                        }
                        @Override
                        public void onResponse(Response response) throws IOException {
                            Log.d("xiaoming",response.body().string());
                        }
                    });   

    okhttp3.0之后

       OkHttpClient client = new OkHttpClient();
       FormBody formBody = new FormBody.Builder()
                    .add("type", "1")
                    .build();
      Request request=new Request.Builder().url(url).post(forBody).build();
                    client.newCall(request).enqueue(new Callback() {
                        @Override
                        public void onFailure(Request request, IOException e) {
                        }
                        @Override
                        public void onResponse(Response response) throws IOException {
                            Log.d("xiaoming",response.body().string());
                        }
                    });   

    文件下载

     String url = "http://www.0551fangchan.com/images/keupload/20120917171535_49309.jpg";
       //构建request对象 
            Request request = new Request.Builder().url(url).build();
            OkHttpClient client = new OkHttpClient();
            client.newCall(request).enqueue(new Callback() {
                @Override
                public void onFailure(Call call, IOException e) {
    
                }
    
                @Override
                public void onResponse(Call call, Response response) throws IOException {
                    InputStream inputStream = response.body().byteStream();
                    FileOutputStream fileOutputStream = new FileOutputStream(new File("/sdcard/logo.jpg"));
                    byte[] buffer = new byte[2048];
                    int len = 0;
                    while ((len = inputStream.read(buffer)) != -1) {
                        fileOutputStream.write(buffer, 0, len);
                    }
                    fileOutputStream.flush();
                    Log.d("wuyinlei", "文件下载成功...");
                }
            });

    超时设置:

    okhttp3.0之前:

        client.setConnectTimeout(10, TimeUnit.SECONDS);
        client.setWriteTimeout(10, TimeUnit.SECONDS);
        client.setReadTimeout(30, TimeUnit.SECONDS);

    okhttp3.0之后:

            client.newBuilder().connectTimeout(10, TimeUnit.SECONDS);
            client.newBuilder().readTimeout(10,TimeUnit.SECONDS);
            client.newBuilder().writeTimeout(10,TimeUnit.SECONDS);

    下面看下结果演示:
    这里写图片描述

    注:不知道为什么,我的写入sdcard的权限也加了,就是提示我下载的时候没有权限,我只能用真机了,这里通过log日志来反馈下载成功:

    下面来看下代码,布局很简单,四个按钮,一个textview,这里就不解释了

    package com.example.okhttpdemo;
    
    import android.os.Bundle;
    import android.support.v7.app.AppCompatActivity;
    import android.util.Log;
    import android.view.View;
    import android.widget.Button;
    import android.widget.TextView;
    
    import java.io.File;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.io.InputStream;
    import java.util.concurrent.TimeUnit;
    
    import okhttp3.Call;
    import okhttp3.Callback;
    import okhttp3.FormBody;
    import okhttp3.OkHttpClient;
    import okhttp3.Request;
    import okhttp3.Response;
    
    public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    
        private Button syncGet;
        private Button asyncget;
        private Button post;
        private Button fileDownload,fengzhuang;
        private TextView tvtext;
        private String result;
    
    
    
    
        private static OkHttpClient client = new OkHttpClient();
    
    
        /**
         * 在这里直接设置连接超时,静态方法内,在构造方法被调用前就已经初始话了
         */
        static {
            client.newBuilder().connectTimeout(10, TimeUnit.SECONDS);
            client.newBuilder().readTimeout(10, TimeUnit.SECONDS);
            client.newBuilder().writeTimeout(10, TimeUnit.SECONDS);
        }
    
        private Request request;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            initialize();
            initListener();
    
        }
    
        /**
         * 事件监听
         */
        private void initListener() {
            syncGet.setOnClickListener(this);
            asyncget.setOnClickListener(this);
            post.setOnClickListener(this);
            fileDownload.setOnClickListener(this);
            fengzhuang.setOnClickListener(this);
        }
    
    
        /**
         * 初始化布局控件
         */
        private void initialize() {
    
            syncGet = (Button) findViewById(R.id.syncGet);
            asyncget = (Button) findViewById(R.id.asyncget);
            post = (Button) findViewById(R.id.post);
            tvtext = (TextView) findViewById(R.id.tv_text);
            fileDownload = (Button) findViewById(R.id.fileDownload);
            fengzhuang = (Button) findViewById(R.id.fengzhuang);
        }
    
    
        @Override
        public void onClick(View v) {
            switch (v.getId()) {
                case R.id.syncGet:
                    initSyncData();
                    break;
                case R.id.asyncget:
                    initAsyncGet();
                    break;
                case R.id.post:
                    initPost();
                    break;
                case R.id.fileDownload:
                    downLoadFile();
                    break;
                case R.id.fengzhuang:
                    break;
            }
        }
    
    
    
        /**
         * get请求同步方法
         */
        private void initSyncData() {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        request = new Request.Builder().url(Contants.SYNC_URL).build();
                        Response response = client.newCall(request).execute();
                        result = response.body().string();
                        runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                tvtext.setText(result);
                                Log.d("MainActivity", "hello");
                            }
                        });
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }).start();
    
        }
    
        /**
         * 异步请求
         */
        private void initAsyncGet() {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    request = new Request.Builder().url(Contants.ASYNC_URL).build();
                    client.newCall(request).enqueue(new Callback() {
    
                        /**
                         * A call is a request that has been prepared for execution. A call can be canceled. As this object
                         * represents a single request/response pair (stream), it cannot be executed twice.
                         *
                         *
                         * @param call   是一个接口,  是一个准备好的可以执行的request
                         *               可以取消,对位一个请求对象,只能单个请求
                         * @param e
                         */
                        @Override
                        public void onFailure(Call call, IOException e) {
                            Log.d("MainActivity", "请求失败");
                        }
    
                        /**
                         *
                         * @param call
                         * @param response   是一个响应请求
                         * @throws IOException
                         */
                        @Override
                        public void onResponse(Call call, Response response) throws IOException {
                            /**
                             * 通过拿到response这个响应请求,然后通过body().string(),拿到请求到的数据
                             * 这里最好用string()  而不要用toString()
                             * toString()每个类都有的,是把对象转换为字符串
                             * string()是把流转为字符串
                             */
                            result = response.body().string();
                            runOnUiThread(new Runnable() {
                                @Override
                                public void run() {
                                    tvtext.setText(result);
                                }
                            });
                        }
                    });
                }
            }).start();
        }
    
        /**
         * 表单提交
         */
        private void initPost() {
    
            String url = "http://112.124.22.238:8081/course_api/banner/query";
    
    
            FormBody formBody = new FormBody.Builder()
                    .add("type", "1")
                    .build();
            request = new Request.Builder().url(url)
                    .post(formBody).build();
            new Thread(new Runnable() {
                @Override
                public void run() {
                    client.newCall(request).enqueue(new Callback() {
                        @Override
                        public void onFailure(Call call, IOException e) {
    
                        }
    
                        @Override
                        public void onResponse(Call call, final Response response) throws IOException {
                            runOnUiThread(new Runnable() {
                                @Override
                                public void run() {
                                    tvtext.setText("提交成功");
                                }
                            });
                        }
                    });
                }
            }).start();
    
        }
    
        /**
         * 文件下载地址
         */
        private void downLoadFile() {
            String url = "http://www.0551fangchan.com/images/keupload/20120917171535_49309.jpg";
            request = new Request.Builder().url(url).build();
            OkHttpClient client = new OkHttpClient();
            client.newCall(request).enqueue(new Callback() {
                @Override
                public void onFailure(Call call, IOException e) {
    
                }
    
                @Override
                public void onResponse(Call call, Response response) throws IOException {
    
                    //把请求成功的response转为字节流
                    InputStream inputStream = response.body().byteStream();
    
                    /**
                     * 在这里要加上权限   在mainfests文件中
                     * <uses-permission android:name="android.permission.INTERNET"/>
                     * <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
                     */
    
                    //在这里用到了文件输出流
                    FileOutputStream fileOutputStream = new FileOutputStream(new File("/sdcard/logo.jpg"));
                   //定义一个字节数组
                    byte[] buffer = new byte[2048];
                    int len = 0;
                    while ((len = inputStream.read(buffer)) != -1) {
                        //写出到文件
                        fileOutputStream.write(buffer, 0, len);
                    }
                    //关闭输出流
                    fileOutputStream.flush();
                    Log.d("wuyinlei", "文件下载成功...");
                }
            });
        }
    
    
    }
    
    

    好了,简单的okhttp使用就介绍到这里了,接下来会对其完成以下简单的封装。

    由于okhttp3.0较于之前的有些变化,大家使用的时候可以去看下他们官方的说明,这样在使用中就会少了不必要的麻烦了。

    展开全文
  • Okhttp使用详解

    万次阅读 多人点赞 2016-06-24 13:39:46
    SDK中自带的HttpURLConnection虽然能基本满足需求,但是在使用上有诸多不便,为此,square公司实现了一个HTTP客户端的类库——OkhttpOkhttp是一个支持HTTP 和 HTTP/2 的客户端,可以在Android和Java应用程序中...

    在Android开发中,发送HTTP请求是很常见的。SDK中自带的HttpURLConnection虽然能基本满足需求,但是在使用上有诸多不便,为此,square公司实现了一个HTTP客户端的类库——Okhttp 。

    Okhttp是一个支持HTTP 和 HTTP/2 的客户端,可以在Android和Java应用程序中使用,其具有以下特点:
    1. API设计轻巧,基本上通过几行代码的链式调用即可获取结果。
    2. 既支持同步请求,也支持异步请求。同步请求会阻塞当前线程,异步请求不会阻塞当前线程,异步执行完成后执行相应的回调方法。
    3. 其支持HTTP/2协议,通过HTTP/2,可以让客户端中到同一服务器的所有请求共用同一个Socket连接。
    4. 如果请求不支持HTTP/2协议,那么Okhttp会在内部维护一个连接池, 通过该连接池,可以对HTTP/1.x的连接进行重用,减少了延迟。
    5. 透明的GZIP处理降低了下载数据的大小。
    6. 请求的数据会进行相应的缓存处理,下次再进行请求时,如果服务器告知304(表明数据没有发生变化),那么就直接从缓存中读取数据,降低了重复请求的数量。

    当前Okhttp最新的版本是3.x,支持Android 2.3+。要想在Java应用程序中使用Okhttp,JRE的最低版本要求是1.7。

    Okhttp API: http://square.github.io/okhttp/3.x/okhttp/

    本文中的代码来自于Okhttp的官方Wiki中的示例代码。


    下载Okhttp

    可以点此下载最新的Okhttp的jar包,也可以通过Maven获取:

    <dependency>
      <groupId>com.squareup.okhttp3</groupId>
      <artifactId>okhttp</artifactId>
      <version>3.3.1</version>
    </dependency>

    也可以在Gradle中进行如下配置以便在Android Studio中使用:

    compile 'com.squareup.okhttp3:okhttp:3.3.1'

    核心类

    我们在使用Okhttp进行开发的时候,主要牵扯到以下几个核心类:OkHttpClient、Request、Call 和 Response。

    • OkHttpClient
      OkHttpClient表示了HTTP请求的客户端类,在绝大多数的App中,我们只应该执行一次new OkHttpClient(),将其作为全局的实例进行保存,从而在App的各处都只使用这一个实例对象,这样所有的HTTP请求都可以共用Response缓存、共用线程池以及共用连接池。

      • 默认情况下,直接执行OkHttpClient client = new OkHttpClient()就可以实例化一个OkHttpClient对象。

      • 可以配置OkHttpClient的一些参数,比如超时时间、缓存目录、代理、Authenticator等,那么就需要用到内部类OkHttpClient.Builder,设置如下所示:

        OkHttpClient client = new OkHttpClient.Builder().
                readTimeout(30, TimeUnit.SECONDS).
                cache(cache).
                proxy(proxy).
                authenticator(authenticator).
                build();

        OkHttpClient本身不能设置参数,需要借助于其内部类Builder设置参数,参数设置完成后,调用Builder的build方法得到一个配置好参数的OkHttpClient对象。这些配置的参数会对该OkHttpClient对象所生成的所有HTTP请求都有影响。

      • 有时候我们想单独给某个网络请求设置特别的几个参数,比如只想让某个请求的超时时间设置为一分钟,但是还想保持OkHttpClient对象中的其他的参数设置,那么可以调用OkHttpClient对象的newBuilder()方法,代码如下所示:

        OkHttpClient client = ...
        
        OkHttpClient clientWith60sTimeout = client.newBuilder().
                readTimeout(60, TimeUnit.SECONDS).
                build();

        clientWith60sTimeout中的参数来自于client中的配置参数,只不过它覆盖了读取超时时间这一个参数,其余参数与client中的一致。

    • Request
      Request类封装了请求报文信息:请求的Url地址、请求的方法(如GET、POST等)、各种请求头(如Content-Type、Cookie)以及可选的请求体。一般通过内部类Request.Builder的链式调用生成Request对象。

    • Call
      Call代表了一个实际的HTTP请求,它是连接Request和Response的桥梁,通过Request对象的newCall()方法可以得到一个Call对象。Call对象既支持同步获取数据,也可以异步获取数据。

      • 执行Call对象的execute()方法,会阻塞当前线程去获取数据,该方法返回一个Response对象。
      • 执行Call对象的enqueue()方法,不会阻塞当前线程,该方法接收一个Callback对象,当异步获取到数据之后,会回调执行Callback对象的相应方法。如果请求成功,则执行Callback对象的onResponse方法,并将Response对象传入该方法中;如果请求失败,则执行Callback对象的onFailure方法。
    • Response
      Response类封装了响应报文信息:状态吗(200、404等)、响应头(Content-Type、Server等)以及可选的响应体。可以通过Call对象的execute()方法获得Response对象,异步回调执行Callback对象的onResponse方法时也可以获取Response对象。


    同步GET

    以下示例演示了如何同步发送GET请求,输出响应头以及将响应体转换为字符串。

    private final OkHttpClient client = new OkHttpClient();
    
    public void run() throws Exception {
      Request request = new Request.Builder()
          .url("http://publicobject.com/helloworld.txt")
          .build();
    
      Response response = client.newCall(request).execute();
    
      if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
    
      Headers responseHeaders = response.headers();
      for (int i = 0; i < responseHeaders.size(); i++) {
        System.out.println(responseHeaders.name(i) + ": " + responseHeaders.value(i));
      }
    
      System.out.println(response.body().string());
    }

    下面对以上代码进行简单说明:

    • client执行newCall方法会得到一个Call对象,表示一个新的网络请求。

    • Call对象的execute方法是同步方法,会阻塞当前线程,其返回Response对象。

    • 通过Response对象的isSuccessful()方法可以判断请求是否成功。

    • 通过Response的headers()方法可以得到响应头Headers对象,可以通过for循环索引遍历所有的响应头的名称和值。可以通过Headers.name(index)方法获取响应头的名称,通过Headers.value(index)方法获取响应头的值。

    • 除了索引遍历,通过Headers.get(headerName)方法也可以获取某个响应头的值,比如通过headers.get(“Content-Type”)获得服务器返回给客户端的数据类型。但是服务器返回给客户端的响应头中有可能有多个重复名称的响应头,比如在某个请求中,服务器要向客户端设置多个Cookie,那么会写入多个Set-Cookie响应头,且这些Set-Cookie响应头的值是不同的,访问百度首页,可以看到有7个Set-Cookie的响应头,如下图所示:

      这里写图片描述

      为了解决同时获取多个name相同的响应头的值,Headers中提供了一个public List<String> values(String name)方法,该方法会返回一个List<String>对象,所以此处通过Headers对象的values(‘Set-Cookie’)可以获取全部的Cookie信息,如果调用Headers对象的get(‘Set-Cookie’)方法,那么只会获取最后一条Cookie信息。

    • 通过Response对象的body()方法可以得到响应体ResponseBody对象,调用其string()方法可以很方便地将响应体中的数据转换为字符串,该方法会将所有的数据放入到内存之中,所以如果数据超过1M,最好不要调用string()方法以避免占用过多内存,这种情况下可以考虑将数据当做Stream流处理。


    异步GET

    以下示例演示了如何异步发送GET网络请求,代码如下所示:

      private final OkHttpClient client = new OkHttpClient();
    
      public void run() throws Exception {
        Request request = new Request.Builder()
            .url("http://publicobject.com/helloworld.txt")
            .build();
    
        client.newCall(request).enqueue(new Callback() {
          @Override
          public void onFailure(Call call, IOException e) {
            e.printStackTrace();
          }
    
          @Override
          public void onResponse(Call call, Response response) throws IOException {
            if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
    
            Headers responseHeaders = response.headers();
            for (int i = 0, size = responseHeaders.size(); i < size; i++) {
              System.out.println(responseHeaders.name(i) + ": " + responseHeaders.value(i));
            }
    
            System.out.println(response.body().string());
          }
        });
      }

    下面对以上代码进行一下说明:

    • 要想异步执行网络请求,需要执行Call对象的enqueue方法,该方法接收一个okhttp3.Callback对象,enqueue方法不会阻塞当前线程,会新开一个工作线程,让实际的网络请求在工作线程中执行。一般情况下这个工作线程的名字以“Okhttp”开头,并包含连接的host信息,比如上面例子中的工作线程的name是"Okhttp http://publicobject.com/..."

    • 当异步请求成功后,会回调Callback对象的onResponse方法,在该方法中可以获取Response对象。当异步请求失败或者调用了Call对象的cancel方法时,会回调Callback对象的onFailure方法。onResponse和onFailure这两个方法都是在工作线程中执行的。


    请求头和响应头

    典型的HTTP请求头、响应头都是类似于Map<String, String>,每个name对应一个value值。不过像我们之前提到的,也会存在多个name重复的情况,比如相应结果中就有可能存在多个Set-Cookie响应头,同样的,也可能同时存在多个名称一样的请求头。响应头的读取我们在上文已经说过了,在此不再赘述。一般情况下,我们只需要调用header(name, value)方法就可以设置请求头的name和value,调用该方法会确保整个请求头中不会存在多个名称一样的name。如果想添加多个name相同的请求头,应该调用addHeader(name, value)方法,这样可以添加重复name的请求头,其value可以不同,例如如下所示:

      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"));
      }

    上面的代码通过addHeader方法添加了两个Accept请求头,且二者的值不同,这样服务器收到客户端发来的请求后,就知道客户端既支持application/json类型的数据,也支持application/vnd.github.v3+json类型的数据。


    用POST发送String

    可以使用POST方法发送请求体。下面的示例演示了如何将markdown文本作为请求体发送到服务器,服务器会将其转换成html文档,并发送给客户端。

      public static final MediaType MEDIA_TYPE_MARKDOWN
          = MediaType.parse("text/x-markdown; charset=utf-8");
    
      private final OkHttpClient client = new OkHttpClient();
    
      public void run() throws Exception {
        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();
    
        Response response = client.newCall(request).execute();
        if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
    
        System.out.println(response.body().string());
      }

    下面对以上代码进行说明:

    • Request.Builderpost方法接收一个RequestBody对象。

    • RequestBody就是请求体,一般可通过调用该类的5个重载的static的create()方法得到RequestBody对象。create()方法第一个参数都是MediaType类型,create()方法的第二个参数可以是String、File、byte[]或okio.ByteString。除了调用create()方法,还可以调用RequestBody的writeTo()方法向其写入数据,writeTo()方法一般在用POST发送Stream流的时候使用。

    • MediaType指的是要传递的数据的MIME类型,MediaType对象包含了三种信息:type、subtype以及CharSet,一般将这些信息传入parse()方法中,这样就能解析出MediaType对象,比如在上例中text/x-markdown; charset=utf-8,type值是text,表示是文本这一大类;/后面的x-markdown是subtype,表示是文本这一大类下的markdown这一小类;charset=utf-8则表示采用UTF-8编码。如果不知道某种类型数据的MIME类型,可以参见连接Media TypesMIME 参考手册,较详细地列出了所有的数据的MIME类型。以下是几种常见数据的MIME类型值:

      • json :application/json
      • xml:application/xml
      • png:image/png
      • jpg: image/jpeg
      • gif:image/gif
    • 在该例中,请求体会放置在内存中,所以应该避免用该API发送超过1M的数据。


    用POST发送Stream流

    下面的示例演示了如何使用POST发送Stream流。

      public static final MediaType MEDIA_TYPE_MARKDOWN
          = MediaType.parse("text/x-markdown; charset=utf-8");
    
      private final OkHttpClient client = new OkHttpClient();
    
      public void run() throws Exception {
        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();
    
        Response response = client.newCall(request).execute();
        if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
    
        System.out.println(response.body().string());
      }

    下面对以上代码进行说明:

    • 以上代码在实例化RequestBody对象的时候重写了contentType()writeTo()方法。

    • 覆写contentType()方法,返回markdown类型的MediaType。

    • 覆写writeTo()方法,该方法会传入一个OkiaBufferedSink类型的对象,可以通过BufferedSink的各种write方法向其写入各种类型的数据,此例中用其writeUtf8方法向其中写入UTF-8的文本数据。也可以通过它的outputStream()方法,得到输出流OutputStream,从而通过OutputSteram向BufferedSink写入数据。


    用POST发送File

    下面的代码演示了如何用POST发送文件。

      public static final MediaType MEDIA_TYPE_MARKDOWN
          = MediaType.parse("text/x-markdown; charset=utf-8");
    
      private final OkHttpClient client = new OkHttpClient();
    
      public void run() throws Exception {
        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();
    
        Response response = client.newCall(request).execute();
        if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
    
        System.out.println(response.body().string());
      }

    我们之前提到,RequestBody.create()静态方法可以接收File参数,将File转换成请求体,将其传递给post()方法。


    用POST发送Form表单中的键值对

    如果想用POST发送键值对字符串,可以使用在post()方法中传入FormBody对象,FormBody继承自RequestBody,类似于Web前端中的Form表单。可以通过FormBody.Builder构建FormBody

    示例代码如下所示:

      private final OkHttpClient client = new OkHttpClient();
    
      public void run() throws Exception {
        RequestBody formBody = new FormBody.Builder()
            .add("search", "Jurassic Park")
            .build();
        Request request = new Request.Builder()
            .url("https://en.wikipedia.org/w/index.php")
            .post(formBody)
            .build();
    
        Response response = client.newCall(request).execute();
        if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
    
        System.out.println(response.body().string());
      }

    需要注意的是,在发送数据之前,Android会对非ASCII码字符调用encodeURIComponent方法进行编码,例如”Jurassic Park”会编码成”Jurassic%20Park”,其中的空格符被编码成%20了,服务器端会其自动解码。


    用POST发送multipart数据

    我们可以通过Web前端的Form表单上传一个或多个文件,Okhttp也提供了对应的功能,如果我们想同时发送多个Form表单形式的文件,就可以使用在post()方法中传入MultipartBody对象。MultipartBody继承自RequestBody,也表示请求体。只不过MultipartBody的内部是由多个part组成的,每个part就单独包含了一个RequestBody请求体,所以可以把MultipartBody看成是一个RequestBody的数组,而且可以分别给每个RequestBody单独设置请求头。

    示例代码如下所示:

      private static final String IMGUR_CLIENT_ID = "...";
      private static final MediaType MEDIA_TYPE_PNG = MediaType.parse("image/png");
    
      private final OkHttpClient client = new OkHttpClient();
    
      public void run() throws Exception {
        // Use the imgur image upload API as documented at https://api.imgur.com/endpoints/image
        RequestBody requestBody = new MultipartBody.Builder()
            .setType(MultipartBody.FORM)
            .addFormDataPart("title", "Square Logo")
            .addFormDataPart("image", "logo-square.png",
                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();
    
        Response response = client.newCall(request).execute();
        if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
    
        System.out.println(response.body().string());
      }

    下面对以上代码进行说明:


    用Gson处理JSON响应

    Gson是Google开源的一个用于进行JSON处理的Java库,通过Gson可以很方面地在JSON和Java对象之间进行转换。我们可以将Okhttp和Gson一起使用,用Gson解析返回的JSON结果。

    下面的示例代码演示了如何使用Gson解析GitHub API的返回结果。

      private final OkHttpClient client = new OkHttpClient();
      private final Gson gson = new Gson();
    
      public void run() throws Exception {
        Request request = new Request.Builder()
            .url("https://api.github.com/gists/c2a7c39532239ff261be")
            .build();
        Response response = client.newCall(request).execute();
        if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
    
        Gist gist = gson.fromJson(response.body().charStream(), Gist.class);
        for (Map.Entry<String, GistFile> entry : gist.files.entrySet()) {
          System.out.println(entry.getKey());
          System.out.println(entry.getValue().content);
        }
      }
    
      static class Gist {
        Map<String, GistFile> files;
      }
    
      static class GistFile {
        String content;
      }

    下面对以上代码进行说明:

    • 访问GitHub的https://api.github.com/gists/c2a7c39532239ff261be的返回结果如下所示:
      这里写图片描述

    • Gist类对应着整个JSON的返回结果,Gist中的Map<String, GistFile> files对应着JSON中的files

    • files中的每一个元素都是一个key-value的键值对,key是String类型,value是GistFile类型,并且GistFile中必须包含一个String contentOkHttp.txt就对应着一个GistFile对象,其content值就是GistFile中的content。

    • 通过代码Gist gist = gson.fromJson(response.body().charStream(), Gist.class),将JSON字符串转换成了Java对象。将ResponseBodycharStream方法返回的Reader传给GsonfromJson方法,然后传入要转换的Java类的class。


    缓存响应结果

    如果想缓存响应结果,我们就需要为Okhttp配置缓存目录,Okhttp可以写入和读取该缓存目录,并且我们需要限制该缓存目录的大小。Okhttp的缓存目录应该是私有的,不能被其他应用访问。

    Okhttp中,多个缓存实例同时访问同一个缓存目录会出错,大部分的应用只应该调用一次new OkHttpClient(),然后为其配置缓存目录,然后在App的各个地方都使用这一个OkHttpClient实例对象,否则两个缓存实例会互相影响,导致App崩溃。

    缓存示例代码如下所示:

      private final OkHttpClient client;
    
      public CacheResponse(File cacheDirectory) throws Exception {
        int cacheSize = 10 * 1024 * 1024; // 10 MiB
        String okhttpCachePath = getCacheDir().getPath() + File.separator + "okhttp";
        File okhttpCache = new File(okhttpCachePath);
        if(!okhttpCache.exists()){
            okhttpCache.mkdirs();
        }
    
        Cache cache = new Cache(okhttpCache, cacheSize);
    
        client = new OkHttpClient.Builder()
            .cache(cache)
            .build();
      }
    
      public void run() throws Exception {
        Request request = new Request.Builder()
            .url("http://publicobject.com/helloworld.txt")
            .build();
    
        Response response1 = client.newCall(request).execute();
        if (!response1.isSuccessful()) throw new IOException("Unexpected code " + response1);
    
        String response1Body = response1.body().string();
        System.out.println("Response 1 response:          " + response1);
        System.out.println("Response 1 cache response:    " + response1.cacheResponse());
        System.out.println("Response 1 network response:  " + response1.networkResponse());
    
        Response response2 = client.newCall(request).execute();
        if (!response2.isSuccessful()) throw new IOException("Unexpected code " + response2);
    
        String response2Body = response2.body().string();
        System.out.println("Response 2 response:          " + response2);
        System.out.println("Response 2 cache response:    " + response2.cacheResponse());
        System.out.println("Response 2 network response:  " + response2.networkResponse());
    
        System.out.println("Response 2 equals Response 1? " + response1Body.equals(response2Body));
      }

    下面对以上代码进行说明:

    • 我们在App的cache目录下创建了一个子目录okhttp,将其作为Okhttp专门用于缓存的目录,并设置其上限为10M,Okhttp需要能够读写该目录。

    • 不要让多个缓存实例同时访问同一个缓存目录,因为多个缓存实例会相互影响,导致出错,甚至系统崩溃。在绝大多数的App中,我们只应该执行一次new OkHttpClient(),将其作为全局的实例进行保存,从而在App的各处都只使用这一个实例对象,这样所有的HTTP请求都可以共用Response缓存。

    • 上面代码,我们对于同一个URL,我们先后发送了两个HTTP请求。第一次请求完成后,Okhttp将请求到的结果写入到了缓存目录中,进行了缓存。response1.networkResponse()返回了实际的数据,response1.cacheResponse()返回了null,这说明第一次HTTP请求的得到的响应是通过发送实际的网络请求,而不是来自于缓存。然后对同一个URL进行了第二次HTTP请求,response2.networkResponse()返回了null,response2.cacheResponse()返回了缓存数据,这说明第二次HTTP请求得到的响应来自于缓存,而不是网络请求。

    • 如果想让某次请求禁用缓存,可以调用request.cacheControl(CacheControl.FORCE_NETWORK)方法,这样即便缓存目录有对应的缓存,也会忽略缓存,强制发送网络请求,这对于获取最新的响应结果很有用。如果想强制某次请求使用缓存的结果,可以调用request.cacheControl(CacheControl.FORCE_CACHE),这样不会发送实际的网络请求,而是读取缓存,即便缓存数据过期了,也会强制使用该缓存作为响应数据,如果缓存不存在,那么就返回504 Unsatisfiable Request错误。也可以向请求中中加入类似于Cache-Control: max-stale=3600之类的请求头,Okhttp也会使用该配置对缓存进行处理。


    取消请求

    当请求不再需要的时候,我们应该中止请求,比如退出当前的Activity了,那么在Activity中发出的请求应该被中止。可以通过调用Call的cancel方法立即中止请求,如果线程正在写入Request或读取Response,那么会抛出IOException异常。同步请求和异步请求都可以被取消。

    示例代码如下所示:

      private final ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
      private final OkHttpClient client = new OkHttpClient();
    
      public void run() throws Exception {
        Request request = new Request.Builder()
            .url("http://httpbin.org/delay/2") // This URL is served with a 2 second delay.
            .build();
    
        final long startNanos = System.nanoTime();
        final Call call = client.newCall(request);
    
        // Schedule a job to cancel the call in 1 second.
        executor.schedule(new Runnable() {
          @Override public void run() {
            System.out.printf("%.2f Canceling call.%n", (System.nanoTime() - startNanos) / 1e9f);
            call.cancel();
            System.out.printf("%.2f Canceled call.%n", (System.nanoTime() - startNanos) / 1e9f);
          }
        }, 1, TimeUnit.SECONDS);
    
        try {
          System.out.printf("%.2f Executing call.%n", (System.nanoTime() - startNanos) / 1e9f);
          Response response = call.execute();
          System.out.printf("%.2f Call was expected to fail, but completed: %s%n",
              (System.nanoTime() - startNanos) / 1e9f, response);
        } catch (IOException e) {
          System.out.printf("%.2f Call failed as expected: %s%n",
              (System.nanoTime() - startNanos) / 1e9f, e);
        }
      }

    上述请求,服务器端会有两秒的延时,在客户端发出请求1秒之后,请求还未完成,这时候通过cancel方法中止了Call,请求中断,并触发IOException异常。


    设置超时

    一次HTTP请求实际上可以分为三步:

    1. 客户端与服务器建立连接
    2. 客户端发送请求数据到服务器,即数据上传
    3. 服务器将响应数据发送给客户端,即数据下载

    由于网络、服务器等各种原因,这三步中的每一步都有可能耗费很长时间,导致整个HTTP请求花费很长时间或不能完成。

    为此,可以通过OkHttpClient.BuilderconnectTimeout()方法设置客户端和服务器建立连接的超时时间,通过writeTimeout()方法设置客户端上传数据到服务器的超时时间,通过readTimeout()方法设置客户端从服务器下载响应数据的超时时间。

    在2.5.0版本之前,Okhttp默认不设置任何的超时时间,从2.5.0版本开始,Okhttp将连接超时、写入超时(上传数据)、读取超时(下载数据)的超时时间都默认设置为10秒。如果HTTP请求需要更长时间,那么需要我们手动设置超时时间。

    示例代码如下所示:

      private final OkHttpClient client;
    
      public ConfigureTimeouts() throws Exception {
        client = new OkHttpClient.Builder()
            .connectTimeout(10, TimeUnit.SECONDS)
            .writeTimeout(10, TimeUnit.SECONDS)
            .readTimeout(30, TimeUnit.SECONDS)
            .build();
      }
    
      public void run() throws Exception {
        Request request = new Request.Builder()
            .url("http://httpbin.org/delay/2") // This URL is served with a 2 second delay.
            .build();
    
        Response response = client.newCall(request).execute();
        System.out.println("Response completed: " + response);
      }

    如果HTTP请求的某一部分超时了,那么就会触发异常。


    处理身份验证

    有些网络请求是需要用户名密码登录的,如果没提供登录需要的信息,那么会得到401 Not Authorized未授权的错误,这时候Okhttp会自动查找是否配置了Authenticator,如果配置过Authenticator,会用Authenticator中包含的登录相关的信息构建一个新的Request,尝试再次发送HTTP请求。

    示例代码如下所示:

      private final OkHttpClient client;
    
      public Authenticate() {
        client = new OkHttpClient.Builder()
            .authenticator(new Authenticator() {
              @Override public Request authenticate(Route route, Response response) throws IOException {
                System.out.println("Authenticating for response: " + response);
                System.out.println("Challenges: " + response.challenges());
                String credential = Credentials.basic("jesse", "password1");
                return response.request().newBuilder()
                    .header("Authorization", credential)
                    .build();
              }
            })
            .build();
      }
    
      public void run() throws Exception {
        Request request = new Request.Builder()
            .url("http://publicobject.com/secrets/hellosecret.txt")
            .build();
    
        Response response = client.newCall(request).execute();
        if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
    
        System.out.println(response.body().string());
      }

    上面对以上代码进行说明:

    • OkHttpClient.Builderauthenticator()方法接收一个Authenticator对象,我们需要实现Authenticator对象的authenticate()方法,该方法需要返回一个新的Request对象,该新的Request对象基于原始的Request对象进行拷贝,而且要通过header("Authorization", credential)方法对其设置登录授权相关的请求头信息。

    • 通过Response对象的challenges()方法可以得到第一次请求失败的授权相关的信息。如果响应码是401 unauthorized,那么会返回”WWW-Authenticate”相关信息,这种情况下,要执行OkHttpClient.Builderauthenticator()方法,在Authenticator对象的authenticate()中 对新的Request对象调用header("Authorization", credential)方法,设置其Authorization请求头;如果Response的响应码是407 proxy unauthorized,那么会返回”Proxy-Authenticate”相关信息,表示不是最终的服务器要求客户端登录授权信息,而是客户端和服务器之间的代理服务器要求客户端登录授权信息,这时候要执行OkHttpClient.BuilderproxyAuthenticator()方法,在Authenticator对象的authenticate()中 对新的Request对象调用header("Proxy-Authorization", credential)方法,设置其Proxy-Authorization请求头。

    • 如果用户名密码有问题,那么Okhttp会一直用这个错误的登录信息尝试登录,我们应该判断如果之前已经用该用户名密码登录失败了,就不应该再次登录,这种情况下需要让Authenticator对象的authenticate()方法返回null,这就避免了没必要的重复尝试,代码片段如下所示:

      if (credential.equals(response.request().header("Authorization"))) {
         return null; 
      }

    ResponseBody

    通过Response的body()方法可以得到响应体ResponseBody,响应体必须最终要被关闭,否则会导致资源泄露、App运行变慢甚至崩溃。

    ResponseBody和Response都实现了CloseableAutoCloseable接口,它们都有close()方法,Response的close()方法内部直接调用了ResponseBody的close()方法,无论是同步调用execute()还是异步回调onResponse(),最终都需要关闭响应体,可以通过如下方法关闭响应体:

    • Response.close()
    • Response.body().close()
    • Response.body().source().close()
    • Response.body().charStream().close()
    • Response.body().byteString().close()
    • Response.body().bytes()
    • Response.body().string()

    对于同步调用,确保响应体被关闭的最简单的方式是使用try代码块,如下所示:

     Call call = client.newCall(request);
     try (Response response = call.execute()) {
       ... // Use the response.
     }

    Response response = call.execute()放入到try()的括号之中,由于Response 实现了CloseableAutoCloseable接口,这样对于编译器来说,会隐式地插入finally代码块,编译器会在该隐式的finally代码块中执行Response的close()方法。

    也可以在异步回调方法onResponse()中,执行类似的try代码块,try()代码块括号中的ResponseBody也实现了CloseableAutoCloseable接口,这样编译器也会在隐式的finally代码块中自动关闭响应体,代码如下所示:

       Call call = client.newCall(request);
       call.enqueue(new Callback() {
         public void onResponse(Call call, Response response) throws IOException {
           try (ResponseBody responseBody = response.body()) {
             ... // Use the response.
           }
         }
    
         public void onFailure(Call call, IOException e) {
           ... // Handle the failure.
         }
       });

    响应体中的数据有可能很大,应该只读取一次响应体的数据。调用ResponseBody的bytes()string()方法会将整个响应体数据写入到内存中,可以通过source()byteStream()charStream()进行流式处理。

    参考:
    http://square.github.io/okhttp/3.x/okhttp/
    https://github.com/square/okhttp/wiki/Recipes
    https://github.com/square/okhttp/blob/master/CHANGELOG.md

    相关阅读:
    我的Android博文整理汇总
    Android中HttpURLConnection使用详解
    HTTPS理论基础及其在Android中的最佳实践

    展开全文
  • OKHTTP使用完全解析

    千次阅读 2016-09-10 18:59:34
    在Android客户端开发中,使用网络请求是非常常见的事情,一般我们使用HttpURLConnection是可以满足需求的,不过随着业务逻辑复杂,依然还是有很多不便,一个神奇的公司square开源了一个网络请求库——Okhttp。...
  • java okhttp使用详解

    千次阅读 2018-10-07 17:20:06
    本文使用eclipse编辑器,gradle依赖jar,如若未配置此环境,请转Java Eclipse配置gradle编译项目配置好环境后再查看此文 在build.gradle中添加依赖compile 'com.squareup.okhttp3:okhttp:3.8.1' 同步Get请求  /*...
  • OkHttp使用

    2018-08-02 09:30:58
    2.OkHttp的基本使用 3.Call模型 一.Http网络请求响应基本知识 简介 从客户端到服务器端的请求消息及响应,包括在消息首行中,对资源的请求方法、资源的标识符及使用的协议。 Http进行请求和响应,即一次...
  • OkHttp3的基本用法

    2018-05-01 21:35:56
    这篇文章说下OkHttp的基本用法,是最新的3哦,如果你曾经在网上搜索OkHttp怎么使用发现有些类没有了可能是因为人家说的是2。首先说下OkHttp3是Java和Android都能用,Android还有一个著名网络库叫Volley,那个只有...
  • OkHttp execute方法和enqueue方法的区别

    万次阅读 2016-03-25 23:56:17
    OkHttp的execute的方法是同步方法, OkHttp的enqueue的方法是异步方法, 具体请参考: OKHttp使用简介
  • "containedParcels"=>[ {"parcelNum": "aaa"}, {"parcelNum": "bbb"}, {"parcelNum": "ddd"} ] 像这种数据我要怎样使用post发送请求
  • 关于Eclipse使用OkHttp

    千次阅读 2016-03-30 14:15:39
    eclipse使用OKHttp
  • OkHttp和Volley对比

    万次阅读 多人点赞 2016-08-11 14:22:43
    OkHttp物理质量使用OkHttp需要 okio.jar (80k), okhttp.jar(330k)这2个jar包,总大小差不多400k,加上自己的封装,差不多得410k。功能介绍Square 公司开源的 OkHttp 是一个专注于连接效率的 HTTP 客户端。OkHttp 提供...
  • 关于OkHttp方面的Android面试题

    千次阅读 2016-11-17 16:06:57
    最近好多学生出去面试都或多或少的问到了一些三方网络框架的使用以及内部实现原理。对于三方框架的使用自然不用多说 手到擒来。但是每每问到内部实现原理时就相形见绌了。因此最近专门花了点时间准备写几篇对于网络...
  • 报错提示 E/AndroidRuntime: FATAL EXCEPTION: OkHttp Dispatcher java.lang.IllegalStateException: closed 报错原因 String data=response.body().string(); 中.string()方法使用次数超过了一次。 没错,你只能...
  • Okhttp获取中文网页乱码的解决方式

    千次阅读 2017-02-26 22:26:26
    OkHttp post请求编码问题 解决放法 在回调方法onResponse方法中 byte[] b = response.body().bytes(); //获取数据的bytes String info = new String(b, "GB2312"); //然后将其转为gb2312
  • Android Studio 之 OkHTTP使用

    千次阅读 2018-06-14 17:08:26
    详细操作参考:...amp;fps=1如果已知道版本com.squareup.okhttp3:okhttp:3.4.2,只需要输入既可以;(可能没有搜索到,并不影响添加)以上步骤做完在app--&gt;build.gradle中会添加一条compile 'co...
  • 前言 Android为我们提供了两种HTTP交互的方式:HttpURLConnection和Apache HTTP Client( Android 6.0——API 23已经不支持该方式了 ),虽然两者都支持HTTPS,流的上传和下载,配置超时...这就诞生了Okhttp及Ret...
  • 大神看一下,我这个POST请求那里错了,直接说出来。谢谢大神 ``` String username = usernameEdit.getText().toString();... String password = passwordEdit.getText().toString();... OkHttpClient client = new ...
  • Glide与OkHttp3集成

    万次阅读 2016-03-30 15:37:45
    最近做毕设,android方面采用了很多以前没有使用过的库,比较各种图片加载库根据需要采用了Glide。鉴于Google官方在Android 6.0以后开始采用okhttp作为底层网络通信库,所以项目中采用了OkHttp3来进行网络请求。 ...
  • okhttp使用https忽略证书验证

    千次阅读 2017-06-30 12:33:51
    okhttp使用https忽略证书
1 2 3 4 5 ... 20
收藏数 44,351
精华内容 17,740
热门标签
关键字:

okhttp的使用