OkHttp使用小记——8分钟进阶

转载注明出处:简书-十个雨点

上一篇中,我们学习了如何使用OkHttp进行Get和Post操作,本篇将教你如何对网络请求和应答进行更多定制。

Call call = new OkHttpClient().newCall(new Request.Builder().build());
Response response=call.execute();

上面一行代码中展示了使用OkHttp发送一次请求涉及的类,我们来一一解析。

OkHttpClient

OkHttpClient是OkHttp的核心功能的执行者,可以通过

OkHttpClient client=new OkHttpClient();

来创建默认的OkHttpClient对象,也可以使用:

OkHttpClient client=new OkHttpClient.Builder()
        .connectTimeout(60, TimeUnit.SECONDS)       //设置连接超时
        .readTimeout(60, TimeUnit.SECONDS)          //设置读超时
        .writeTimeout(60,TimeUnit.SECONDS)          //设置写超时
        .retryOnConnectionFailure(true)             //是否自动重连
        .build();                                   //构建OkHttpClient对象

来构建自定义的OkHttpClient 对象,上面代码只给出了常用的设置项,其他设置项比较复杂,我们下篇再说。

使用OkHttpClient 的时候需要注意以下几点:

  1. 最好只使用一个共享的OkHttpClient 实例,将所有的网络请求都通过这个实例处理。因为每个OkHttpClient 实例都有自己的连接池和线程池,重用这个实例能降低延时,减少内存消耗,而重复创建新实例则会浪费资源。
  2. OkHttpClient的线程池和连接池在空闲的时候会自动释放,所以一般情况下不需要手动关闭,但是如果出现极端内存不足的情况,可以使用以下代码释放内存:
client.dispatcher().executorService().shutdown();   //清除并关闭线程池
client.connectionPool().evictAll();                 //清除并关闭连接池
client.cache().close();                             //清除cache
  1. 如果对一些请求需要特殊定制,可以使用
OkHttpClient eagerClient = client.newBuilder() 
       .readTimeout(500, TimeUnit.MILLISECONDS)
       .build();

这样创建的实例与原实例共享线程池、连接池和其他设置项,只需进行少量配置就可以实现特殊需求。

Request

Request是网络请求的对象,其本身的构造函数是private的,只能通过Request.Builder来构建。下面代码展示了常用的设置项。

Request request = new Request.Builder()
        .url("https://api.github.com/repos/square/okhttp/issues")                          //设置访问url
        .get()                                                                             //类似的有post、delete、patch、head、put等方法,对应不同的网络请求方法
        .header("User-Agent", "OkHttp Headers.java")                                       //设置header
        .addHeader("Accept", "application/json; q=0.5")                                    //添加header
        .removeHeader("User-Agent")                                                        //移除header
        .headers(new Headers.Builder().add("User-Agent", "OkHttp Headers.java").build())   //移除原有所有header,并设置新header
        .addHeader("Accept", "application/vnd.github.v3+json")
        .build();                                                                          //构建request

RequestBody

在Request中使用post、patch等方法时,需要传入一个RequestBody参数,除了上一节讲到的构造方法外,RequestBody还有两个子类:FormBody和MultipartBody。

FromBody用于提交表单键值对,其作用类似于HTML中的<form>标记。

RequestBody formBody = new FormBody.Builder()                             //提交表单键值对
        .add("platform", "android")                                       //添加键值对
        .add("name", "XXX")
        .add("subject", "Hello")
        .addEncoded(URLEncoder.encode("详细","GBK"),                       //添加已编码的键值对
                    URLEncoder.encode("无","GBK"))
        .add("描述","你好")                                                //其实会自动编码,但是无法控制编码格式
        .build();

使用MultipartBody.Builder可以构建与HTML文件上传格式兼容的复杂请求体。多块请求体中每块请求都是一个独立的请求体,都可以定义自己的请求头。这些请求头应该用于描述对应的请求体,例如Content-Disposition,Content-Length,和Content-Type会自动被添加到请求头中。

RequestBody requestBody = new MultipartBody.Builder()
        .setType(MultipartBody.FORM)
        .addPart(
                Headers.of("Content-Disposition", "form-data; name=\"title\""),
                RequestBody.create(null, "Logo"))
        .addPart(
                Headers.of("Content-Disposition", "form-data; name=\"image\""),
                RequestBody.create(MediaType.parse("image/png"), new File("website/static/logo.png")))
        .addFormDataPart("discription","beautiful")
        .build();

了上面这些现成的类和方法以外,还可以用继承RequestBody的方式自定义实现,比如下例所示是以流的方式POST提交RequestBody,其中BufferedSink是Okio的API,可以使用BufferedSink.outputStream()来得到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());
}

Call

Call对象表示一个已经准备好可以执行的请求,用这个对象可以查询请求的执行状态,或者取消当前请求。它具有以下方法:

Call call=client.newCall(request);              //获取Call对象
Response response=call.execute();               //同步执行网络请求,不要在主线程执行
call.enqueue(new Callback());                   //异步执行网络请求
call.cancel();                                  //取消请求
call.isCanceled();                              //查询是否取消
call.isExecuted();                              //查询是否被执行过

要注意的是,每个Call对象只能执行一次请求。如果想重复执行相同的请求,可以:

Call reCall=client.newCall(call.request());     //获取另一个相同配置的Call对象

Response

Response是网络请求的结果下面是一些常用方法:

Response response=call.execute();               //获取Response对象
response.code();                                //请求的状态码
response.isSuccessful();                        //如果状态码为[200..300),则表明请求成功
Headers headers=response.headers();              //获取响应头
List<String> names=response.headers("name");     //获取响应头中的某个字段
ResponseBody body=response.body();             //获取响应体

其中ResponseBody代表响应体,用于操作网络请求返回的内容。常用方法如下:

body.contentLength();                           //body的长度
String content=body.string();                   //以字符串形式解码body
byte[] byteContent=body.bytes();                //以字节数组形式解码body
InputStreamReader reader=body.charStream();     //将body以字符流的形式解码
InputStream inputStream=body.byteStream();      //将body以字节流的形式解码

ResponseBody还有一些注意事项

  1. ResponseBody必须关闭,不然可能造成资源泄漏,你可以通过以下方法关闭ResponseBody,对同一个ResponseBody只要关闭一次就可以了。
Response.close();
Response.body().close();
Response.body().source().close();
Response.body().charStream().close();
Response.body().byteString().close();
Response.body().bytes();
Response.body().string();
  1. ResponseBody只能被消费一次,也就是string(),bytes(),byteStream()或 charStream()方法只能调用其中一个。

  2. 如果ResponseBody中的数据很大,则不应该使用bytes() 或 string()方法,它们会将结果一次性读入内存,而应该使用byteStream()或 charStream(),以流的方式读取数据。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容