简介

  1. 是一个网络框架,可以用于 Android 和 Java 应用程序。
  2. 支持Android 2.3以上。对于 Java ,最低要求是1.7。当然我们主要说的是 Android 。

我们知道 Android 系统提供了两种 HTTP 通信类, HttpURLConnection 和 Apach HttpClient 。但是这个 Apach HttpClient 在 Android API 23 ,也就是Android6.0上已经废弃。而 HttpURLConnection 又实在太难用,于是 OkHttp 就应运而生。

它是一个处理 Android 网络请求的项目,是 Android 端最火热的项目之一,由 Square 公司贡献,该公司还贡献了(Picasso,以及 Retrofit 等等)。

Square 出品,必属精品。

OkHttp 处理了很多网络疑难杂症:

  • 会从很多常用的连接问题中自动恢复。
  • 如果您的服务器配置了多个 IP 地址,当第一个 IP 连接失败的时候, OkHttp 会自动尝试下一个 IP 。
  • OkHttp还处理了代理服务器问题和 SSL 握手失败问题。

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

优势

  1. 允许连接到统一主机上所有的请求,提高请求效率
  2. 共享 Socket ,减小对服务器的访问次数
  3. 通过连接池,减小了请求延迟
  4. 缓存网络数据,用来减少重复网络请求
  5. 减少了对数据流量的消耗
  6. 自动处理 Gzip 压缩
  7. 支持同步调用和带回调的异步调用。

功能

  • post, get 请求
  • 文件的上传和下载
  • 加载图片(内部会自动压缩图片大小)
  • 支持请求回调,直接返回对象,对象集合
  • 支持 Session 保持

设计思路

注意:OkHttp官方文档并不建议我们创建多个 OkHttpClient ,全局使用一个即可。即单例模式创建 OkHttpClient 对象

使用方法:

同步的 get 请求

private void getDataSycn() {
    new Thread() {
        @Override
        public void run() {
            try {
                OkHttpClient okHttpClient = new OkHttpClient();//创建 OkHttpClient 对象
                Request request = new Request.Builder()//Builder是辅助类
                        .url("http://www.baidu.com")//请求接口,如果需要传递参数,拼接到后面
                        .build();//创建 Request 对象
                Response response = null;//Response即 OkHttp 中的响应
                response = okHttpClient.newCall(request).execute();
                if(response.isSuccessful()){//请求成功
                    Log.e("hoyouly", "currentThread: "+Thread.currentThread()
                          +" \n code: "+response.code()+" \n body: "+response.body().string()
                          +" \n message: "+response.message());
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }.start();
}

结果:

message: OK
currentThread: Thread[Thread-914, 5 ,main]
code: 200
body: <!DOCTYPE html>  后面是百度的网页文本
message: OK

注意:

  1. response.body().string()本质是输入流的读操作,所以它还是网络请求的一部分,所以这行代码必须放在子线程
  2. Response.code是 http 响应行中的 code ,如果访问成功则返回200.这个不是服务器设置的,而是 http 协议中自带的。response.body() 中的 code 才是服务器设置的。(但是我却没打印出来)注意二者的区别。
  3. response.body().string()只能调用一次,在第一次时有返回值,第二次再调用时将会返回 null 。原因是:response.body().string()的本质是输入流的读操作,必须有服务器的输出流的写操作时客户端的读操作才能得到数据。而服务器的写操作只执行一次,所以客户端的读操作也只能执行一次,第二次将返回 null 。 (没搞明白)
  4. 请求后的结果是在一个新线程里面,如果想要更新 UI ,需要把结果传递到 UI 线程里面,可以使用Handler.

当然最方便的办法使用 异步 get 请求

异步 get 请求

private void getDataAsync() {
    OkHttpClient okHttpClient = new OkHttpClient();//创建 OkHttpClient 对象
    Request request = new Request.Builder()//
           .url("http://www.baidu.com")//请求接口,如果需要传递参数,拼接到后面
           .build();//创建 Request 对象
    okHttpClient.newCall(request).enqueue(new Callback() {
       @Override
       public void onFailure(Call call, IOException e) {

       }
       @Override
       public void onResponse(Call call, Response response) throws IOException {
           if (response.isSuccessful()) {//请求成功
               Log.e("hoyouly", "onResponse currentThread: " + Thread.currentThread() //
                       + " \n code: " + response.code() //
                       + " \n body: " + response.body().string()//
                       + " \n message: " + response.message());
           }
       }
    });
}

得到的结果:

onCreate: current thread :Thread[main, 5 ,main]
onResponse currentThread: Thread[OkHttp http://www.baidu.com/..., 5 ,main]
code: 200
body: <!DOCTYPE html>
message: OK

注意: 通过 log 我们可以看出来,回调接口的onResponse(其实也包括 onFailure 方法)执行在子线程。而不是在 main 线程中,(onCreat() 执行在 main() 方法中,)

异步请求的打印结果与同步请求时相同。最大的不同点就是异步请求不需要开启子线程,另外使用方式上也有所不同:

  • 同步请求需要创建一个 Response 对象,用来保存请求后的结果,并且最后执行的是 execute() 方法,如果想取消,可以使用 cancle() ,但是已完成的请求是不可以取消的。
  • 异步请求不需要创建 Response 对象,只需要传递一个 Callback ,用来处理回调结果就可以了。最后使用的是 enqueue 方法会自动将网络请求部分放入子线程中执行。

异步 post 请求

post 也分同步和异步,同步也 get 方法类似,所以只讲异步 post 请求

private void postDataAsync() {
    OkHttpClient okHttpClient=new OkHttpClient();
    FormBody.Builder formBody=new FormBody.Builder();//创建表单请求体
    formBody.add("username","张三");//设置键值对传递参数
    Request request=new Request.Builder()//创建 Request 对象
           .url(BAIDU_URL)//设置URL
           .post(formBody.build())//传递请求体
           .build();
    okHttpClient.newCall(request).enqueue(new Callback() {
       @Override
       public void onFailure(Call call, IOException e) {
       }
       @Override
       public void onResponse(Call call, Response response) throws IOException {
       }
    });
}

post 请求需要设置请求方法,并且需要添加请求体,如 .post(formBody.build())

在 OkHttp 中,默认的是 get 方式,所以 get 方式不需要设置。

post 请求传递参数

查看源码可知,只要是 RequestBody 类以及子类都可以作为 post 方法的参数

public Builder post(RequestBody body) {
  return method("POST", body);
}

FormBody:传递键值对参数

这种方式用来上传 String 类型的键值对,例如

FormBody.Builder formBody = new FormBody.Builder();//创建表单请求体
formBody.add("username","zhangsan");//传递键值对参数

RequestBody:传递 Json 或 File 对象

RequestBody 是一个抽象类,不能直接 new ,但是它有静态方法 creat() ,使用这种方式可以得到 RequestBody 对象,这种方式可以上传 JSON 对象或者 File 对象

上传 JSON 对象

OkHttpClient okHttpClient=new OkHttpClient();
MediaType mediaType=new MediaType("application/json; charset=utf-8");//数据格式为json
String jsonStr = "{\"username\":\"lisi\",\"nickname\":\"李四\"}";//json数据.
RequestBody requestBody=RequestBody.create(mediaType,jsonStr);
Request request=new Request.Builder()//创建 Request 对象
       .url(BAIDU_URL)//设置URL
       .post(requestBody)//传递请求体
       .build();
okHttpClient.newCall(request).enqueue(new Callback() {
   @Override
   public void onFailure(Call call, IOException e) {
   }

   @Override
   public void onResponse(Call call, Response response) throws IOException {
   }
});

MediaType 用于描述 HTTP 请求和响应体的内容类型,也就是Content-Type。

RequestBody 的数据格式都要指定Content-Type,常见的有三种:

  • application/x-www-form-urlencoded 数据是个普通表单
  • multipart/form-data 数据里有文件
  • application/json 数据是个json

上边的普通表单并没有指定Content-Type,这是因为 FormBody 继承了 RequestBody ,它已经指定了数据类型为application/x-www-form-urlencoded。

上传 File 对象

MediaType mediaType1=MediaType.parse("File/*");//数据类型为文件类型
File file=new File("文件路径");
RequestBody requestBody1=RequestBody.create(mediaType1,file);

MultipartBody: 同时上传键值对和 File 对象

Multipart 意思就是多重的,如果既想传递键值对,又想传递 File 对象,那么就使用 MultipartBody

MultipartBody body=new MultipartBody.Builder()//
             .setType(MultipartBody.FORM)//
             .addFormDataPart("groupId",""+groudId)////添加键值对参数
             .addFormDataPart("title","title")//
             .addFormDataPart("file", file.getName(),RequestBody.create(MediaType.parse("file/*"),file))//添加文件
             .build();
final Request request = new Request.Builder()//
             .url(BAIDU_URL)//
             .post(body)//
             .build();
okHttpClient.newCall(request).enqueue(new Callback() {...});

自定义RequestBody: 流的上传

自定义流上传的RequestBody

RequestBody uploadBody=new RequestBody() {
    @Override
    public MediaType contentType() {
        return null;
    }

    @Override
    public void writeTo(BufferedSink sink) throws IOException {
        FileInputStream fio=new FileInputStream(new File("文件名字"));
        byte[] buffer=new byte[];
        if(fio.read(buffer)!=-1){
            sink.write(buffer);
        }
    }
};

这个 RequestBody 对象重写了 write 方法,里面有个 sink 对象。这个是 OKio 包中的输出流,有 write 方法。使用这个方法我们可以实现上传流的功能。

  • 如果想要实现断点续传的功能,可以结合 RandomAccessFile 类实现断点续传的功能。
  • 如果想得到 OutputStream 对象 ,可以使用 BufferedSink 的 outputStream() 方法

然后使用

OkHttpClient client = new OkHttpClient();//创建 OkHttpClient 对象。
Request request = new Request.Builder()
       .url("http://www.baidu.com")
       .post(uploadBody)
       .build();
client.newCall(request).enqueue(new Callback() {...});

设置请求头

设置请求头很简单,只需要在创建 Request 对象的时候,调用相应的方法即可

Request request = new Request.Builder()
                .url("http://www.baidu.com")//
                .header("User-Agent", "OkHttp Headers.java")//
                .addHeader("token", "myToken")//
                .build();

OkHttp 的处理方式是:

  • 使用header(name,value)来设置 HTTP 头的唯一值,如果请求中已经存在响应的信息那么直接替换掉。
  • 使用addHeader(name,value)来补充新值,如果请求头中已经存在 name 的name-value,那么还会继续添加,请求头中便会存在多个 name 相同而 value 不同的“键值对”。
  • 使用header(name)读取唯一值或多个值的最后一个值
  • 使用headers(name)获取所有值

下载文件

OkHttp 中并没有提供下载文件的功能,但是在 Response 中,可以获得流对象,有了流对象,我们就可以通过 IO 的方式自己实现下载文件的功能: 下面这段代码就是在 CallBack 中的 onResponse() 中的:

InputStream inputStream=response.body().byteStream();//从服务器得到输入流对象
long sum=0;
File dir=new File("文件夹路径");
if(!dir.exists()){
    dir.mkdirs();
  }
  File file=new File(dir,"文件名");//根据目录和文件名得到 file 对象
  FileOutputStream fos=new FileOutputStream(file);
  byte[] bytes=new byte[1024*8];
  int len=0;
  while ((len=inputStream.read(bytes))!=-1){
    fos.write(bytes);
  }
  fos.flush();
return file;

缓存控制

缓存控制主要是在创建 Request 对象后,调用 cacheContorl() 方法来实现的

强制不缓存,

  • 关键字:noCache()
Request request = new Request.Builder()
    .cacheControl(new CacheControl.Builder().noCache().build())
    .url("http://publicobject.com/helloworld.txt")
    .build();

缓存策略由服务器指定

  • 关键字:maxAge(0, TimeUnit.SECONDS)
Request request = new Request.Builder()//
    .cacheControl(new CacheControl.Builder().maxAge(0,TimeUnit.SECONDS).build())//
    .url("http://publicobject.com/helloworld.txt")//
    .build();

强制缓存

  • 关键字:onlyIfCached()
Request request = new Request.Builder()//
    .cacheControl(new CacheControl.Builder().onlyIfCached().build())//
    .url("http://publicobject.com/helloworld.txt")//
    .build();
Response forceCacheResponse = client.newCall(request).execute();
if (forceCacheResponse.code() != 504) {
  // The resource was cached! Show it.
} else {
  // The resource was not cached.
}

允许使用旧的缓存

  • 关键字:maxStale(365, TimeUnit.DAYS)
Request request = new Request.Builder()//
    .cacheControl(new CacheControl.Builder().maxStale(365, TimeUnit.DAYS).build())//
    .url("http://publicobject.com/helloworld.txt")//
    .build();

暂时完结。。。


搬运地址:

Retrofit2 完全解析 探索与 okhttp 之间的关系

百度百科 OkHttp

OKHttp使用详解

OkHttp使用教程

#Android#OkHttp3使用指南