为了账号安全,请及时绑定邮箱和手机立即绑定

Android OkHttp经验小结

标签:
Android

OkHttp应该是目前最完善,也是相当流行的一个底层网络请求库。Google都在用,所以有必要深入了解一下,刚好最近在重构公司项目的网络层,就顺便梳理一下。
———–12.29————
最近暂时没有时间详细整理了。就简单过了一下官方文档。
以下取自官方文档。

网络请求

同步Get方法

以下样例代码下载一个文件,打印headers,打印字符串形式的 response body

1
   2
   3
   4
   5
   6
   7
   8
   9
   10
   11
   12
   13
   14
   15
   16
   17
private final OkHttpClient client = new OkHttpClient();
   
     public void () 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());
     }


注意: response body中的string()方法对小文件是很方便高效的,但是当response body 大于1M时,避免用string(),因为它会加载整个文件到内存,此时应该把body用stream的形式来处理.


异步Get方法

在工作线程下载一个文件,并且当response准备好了的时候回调Callback。 response headers 准备好了的时候,就走回调。读response body 也会阻塞线程,OkHttp暂时没有提供额外的异步
APIs来获得response body。

1
  2
  3
  4
  5
  6
  7
  8
  9
  10
  11
  12
  13
  14
  15
  16
  17
  18
  19
  20
  21
  22
  23
  24
private final OkHttpClient client = new OkHttpClient();
   
     public void () throws Exception {
       Request request = new Request.Builder()
           .url("http://publicobject.com/helloworld.txt")
           .build();
   
       client.newCall(request).enqueue(new Callback() {
         @Override public void (Request request, IOException throwable) {
           throwable.printStackTrace();
         }
   
         @Override public void (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());
         }
       });
     }

Accessing Headers

典型的HTTP请求头,像Map<String, String>那样工作,每个field有一个值或者没有,但是有的headers允许有多个值,就像Guava’s Multimap.比如,一个HTTP response 提供多个多样的headers是合法且普遍的。 OkHttp’s APIs 兼容这两种情况。
注意:当写request headers时,header(name, value) 设置唯一的键值对,这会覆盖已有的值。而用方法 addHeader(name, value) 来添加header时,不会移除已有的header。
相应的header(name)来获取最后的这个name的相应value,通常这也是唯一的,如果没有就返回null 。headers(name)用来read所有的field的值,已list的形式。
想要看所有的headers,使用支持通过索引访问的类Headers

1
  2
  3
  4
  5
  6
  7
  8
  9
  10
  11
  12
  13
  14
  15
  16
  17
private final OkHttpClient client = new OkHttpClient();
   
   public void () 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

由于这个request body同时要完全加载到内存,所以避免用个API来posting大于1M的文件。

1
  2
  3
  4
  5
  6
  7
  8
  9
  10
  11
  12
  13
  14
  15
  16
  17
  18
  19
  20
  21
  22
  23
  24
public static final MediaType MEDIA_TYPE_MARKDOWN
       = MediaType.parse("text/x-markdown; charset=utf-8");
   
   private final OkHttpClient client = new OkHttpClient();
   
   public void () 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

一下示例代码使用到了Okio的buffered sink,你可能更喜欢使用OutputStream,你可以通过BufferedSink.outputStream()来获得。

1
  2
  3
  4
  5
  6
  7
  8
  9
  10
  11
  12
  13
  14
  15
  16
  17
  18
  19
  20
  21
  22
  23
  24
  25
  26
  27
  28
  29
  30
  31
  32
  33
  34
  35
  36
  37
  38
public static final MediaType MEDIA_TYPE_MARKDOWN
       = MediaType.parse("text/x-markdown; charset=utf-8");
   
   private final OkHttpClient client = new OkHttpClient();
   
   public void () throws Exception {
     RequestBody requestBody = new RequestBody() {
       @Override public MediaType () {
         return MEDIA_TYPE_MARKDOWN;
       }
   
       @Override public void (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 (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文件

简单

1
  2
  3
  4
  5
  6
  7
  8
  9
  10
  11
  12
  13
  14
  15
  16
  17
  18
public static final MediaType MEDIA_TYPE_MARKDOWN
       = MediaType.parse("text/x-markdown; charset=utf-8");
   
   private final OkHttpClient client = new OkHttpClient();
   
   public void () 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(格式化参数)

用FormEncodingBuilder来构建一个request body,像HTML的

标签一样工作,键值对将以兼容HTML的URL编码方式被组织。

1
   2
   3
   4
   5
   6
   7
   8
   9
   10
   11
   12
   13
   14
   15
   16
private final OkHttpClient client = new OkHttpClient();
   
   public void () throws Exception {
     RequestBody formBody = new FormEncodingBuilder()
         .add("search", "Jurassic Park")
         .build();
     Request request = new Request.Builder()
         .url("https://en.wikipedia.org/w/index.php")
         .post(formBody)
         .build();
   
     Response response = client.newCall(request).execute();
     if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
   
     System.out.println(response.body().string());
   }

Posting一个multipart request(复合请求)

MultipartBuilder可以构建复杂的request bodies,兼容html文件上传forms。复合请求的每个request body分别可以定义自己的headers,如果存在,这些headers分别描述相应的body,例如Content-Disposition。The Content-Length and Content-Type headers 会自动添加。

1
  2
  3
  4
  5
  6
  7
  8
  9
  10
  11
  12
  13
  14
  15
  16
  17
  18
  19
  20
  21
  22
  23
  24
  25
  26
  27
  28
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 () throws Exception {
     
     RequestBody requestBody = new MultipartBuilder()
         .type(MultipartBuilder.FORM)
         .addPart(
             Headers.of("Content-Disposition", "form-data; name=\"title\""),
             RequestBody.create(null, "Square Logo"))
         .addPart(
             Headers.of("Content-Disposition", "form-data; name=\"image\""),
             RequestBody.create(MEDIA_TYPE_PNG, new File("website/static/logo-square.png")))
         .build();
   
     Request request = new Request.Builder()
         .header("Authorization", "Client-ID " + IMGUR_CLIENT_ID)
         .url("https://api.imgur.com/3/image")
         .post(requestBody)
         .build();
   
     Response response = client.newCall(request).execute();
     if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
   
     System.out.println(response.body().string());
   }

用Gson解析JSON返回结果

Gson是一个便利的API用来转换JSON和java对象。
Note that ResponseBody.charStream() uses the Content-Type response header to select which charset to use when decoding the response body. It defaults to UTF-8 if no charset is specified.
注意ResponseBody.charStream()根据返回头的Content-Type来选择相应的编码方式,来解码response body。如果没有定义charset,默认是用UTF-8。

1
  2
  3
  4
  5
  6
  7
  8
  9
  10
  11
  12
  13
  14
  15
  16
  17
  18
  19
  20
  21
  22
  23
  24
private final OkHttpClient client = new OkHttpClient();
   private final Gson gson = new Gson();
   
   public void () 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  {
     Map<String, GistFile> files;
   }
   
   static class  {
     String content;
   }

Response Caching

缓存请求结果,你需要一个你可以读写的限制大小的缓存目录,缓存目录应该是私有的,禁止未信任的程序随便读取。
Response caching uses HTTP headers for all configuration. You can add request headers like Cache-Control: max-stale=3600 and OkHttp’s cache will honor them. Your webserver configures how long responses are cached with its own response headers, like Cache-Control: max-age=9600. There are cache headers to force a cached response, force a network response, or force the network response to be validated with a conditional GET.
Response caching完全用HTTP headers来配置,你可以添加像这样的请求头:Cache-Control: max-stale=3600,你的web服务器可以这样的请求头Cache-Control: max-age=9600来配置请求结果缓存时间。可以设置相应的headers来强制缓存response,force a network response, or force the network response to be validated(经过验证的) with a conditional GET.

1
  2
  3
  4
  5
  6
  7
  8
  9
  10
  11
  12
  13
  14
  15
  16
  17
  18
  19
  20
  21
  22
  23
  24
  25
  26
  27
  28
  29
  30
  31
  32
  33
private final OkHttpClient client;
   
   public (File cacheDirectory) throws Exception {
     int cacheSize = 10 * 1024 * 1024; 
     Cache cache = new Cache(cacheDirectory, cacheSize);
   
     client = new OkHttpClient();
     client.setCache(cache);
   }
   
   public void () 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。阻止使用网络,使用 CacheControl.FORCE_CACHE。注意,如果你使用FORCE_CACHE而response需要网络支持的话,OkHttp 将返回一个 504 Unsatisfiable Request response.

取消一个call

用Call.cancel()会立刻停止一个进行中的请求,如果一个线程正在writing一个request,或者在reading一个response,将会抛IOException。
如果你的用户操作离开了app,所有的同步异步请求都应该取消。
你可以用tags来同时取消多个请求,用RequestBuilder.tag(tag)在创建请求的时候指定一个tag,用OkHttpClient.cancel(tag)取消所有有这个tag的请求。

1
  2
  3
  4
  5
  6
  7
  8
  9
  10
  11
  12
  13
  14
  15
  16
  17
  18
  19
  20
  21
  22
  23
  24
  25
  26
  27
  28
  29
  30
private final ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
   private final OkHttpClient client = new OkHttpClient();
   
   public void () throws Exception {
     Request request = new Request.Builder()
         .url("http://httpbin.org/delay/2") 
         .build();
   
     final long startNanos = System.nanoTime();
     final Call call = client.newCall(request);
   
     
     executor.schedule(new Runnable() {
       @Override public void () {
         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

1
  2
  3
  4
  5
  6
  7
  8
  9
  10
  11
  12
  13
  14
  15
  16
  17
  private final OkHttpClient client;
   
   public () throws Exception {
     client = new OkHttpClient();
     client.setConnectTimeout(10, TimeUnit.SECONDS);
     client.setWriteTimeout(10, TimeUnit.SECONDS);
     client.setReadTimeout(30, TimeUnit.SECONDS);
   }
   
   public void () throws Exception {
     Request request = new Request.Builder()
         .url("http://httpbin.org/delay/2") 
         .build();
   
     Response response = client.newCall(request).execute();
     System.out.println("Response completed: " + response);
   }

Per-call Configuration 某个请求特殊配置

所有的HTTP client配置都在OkHttpClient,包括代理,超时,缓存,你想为单个请求改变配置的话,clone the OkHttpClient,这个会返回一个浅(shallow)copy让你来单独定制,如下,你make了一个500ms超时的和3000ms超时的请求。

1
  2
  3
  4
  5
  6
  7
  8
  9
  10
  11
  12
  13
  14
  15
  16
  17
  18
  19
  20
  21
  22
  23
  24
  25
  26
  27
private final OkHttpClient client = new OkHttpClient();
   
   public void () throws Exception {
     Request request = new Request.Builder()
         .url("http://httpbin.org/delay/1") 
         .build();
   
     try {
       OkHttpClient cloned = client.clone(); 
       cloned.setReadTimeout(500, TimeUnit.MILLISECONDS);
   
       Response response = cloned.newCall(request).execute();
       System.out.println("Response 1 succeeded: " + response);
     } catch (IOException e) {
       System.out.println("Response 1 failed: " + e);
     }
   
     try {
       OkHttpClient cloned = client.clone(); 
       cloned.setReadTimeout(3000, TimeUnit.MILLISECONDS);
   
       Response response = cloned.newCall(request).execute();
       System.out.println("Response 2 succeeded: " + response);
     } catch (IOException e) {
       System.out.println("Response 2 failed: " + e);
     }
   }

新版3.0修改为如下:

1
  2
  3
  4
  5
  6
  7
  8
  9
  10
  11
  12
  13
  14
  15
  16
  17
  18
  19
  20
  21
  22
  23
  24
  25
  26
  27
  28
  29
  30
  31
private final OkHttpClient client = new OkHttpClient();
   
   public void () throws Exception {
     Request request = new Request.Builder()
         .url("http://httpbin.org/delay/1") 
         .build();
   
     try {
       
       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 {
       
       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可以自动重操作没有验证通过的请求,当一个response是401 Not Authorized(未授权,未认可),表示认证者需要你提供相应证书,实现是应该新建一个带有相应缺失证书的请求,如果获取不到证书,返回null跳过retry。
Use Response.challenges() to get the schemes and realms of any authentication challenges.
When fulfilling a Basic challenge, use Credentials.basic(username, password) to encode the request header.

1
  2
  3
  4
  5
  6
  7
  8
  9
  10
  11
  12
  13
  14
  15
  16
  17
  18
  19
  20
  21
  22
  23
  24
  25
  26
  27
private final OkHttpClient client = new OkHttpClient();
   
   public void () throws Exception {
     client.setAuthenticator(new Authenticator() {
       @Override public Request (Proxy proxy, Response response) {
         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();
       }
   
       @Override public Request (Proxy proxy, Response response) {
         return null; 
       }
     });
   
     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());
   }

身份验证失效的时候就别多次retry了,return null放弃就好。

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

You may also skip the retry when you’ve hit an application-defined attempt limit:

1
  2
  3
  4
  5
  6
  7
  8
  9
  10
  11
if (responseCount(response) >= 3) {
       return null; 
     }
   
    private int (Response response) {
       int result = 1;
       while ((response = response.priorResponse()) != null) {
         result++;
       }
       return result;
     }

原文链接:http://www.apkbus.com/blog-705730-61402.html

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
微信客服

购课补贴
联系客服咨询优惠详情

帮助反馈 APP下载

慕课网APP
您的移动学习伙伴

公众号

扫描二维码
关注慕课网微信公众号

举报

0/150
提交
取消