Android网络请求之OkHttp应用分析

一、引言

OkHttp这个开源项目,可以说是如今最为流行的网络请求框架之一,由移动支付Square公司贡献(真的很NB,还贡献了Picasso和LeakCanary),用于替代HttpUrlConnection和Apache HttpClient (android API23 6.0里已移除HttpClient)。本文整理了 OkHttp 的使用方法,包括Get请求、Post请求、上传下载等功能。

二、OkHttp简介

OkHttp 是一款用于 Android 和 Java 的网络请求库,也是目前 Android 中最火的一个网络库。
OkHttp有很多的优点:

  • 支持HTTP2/SPDY(SPDY是Google开发的基于TCP的传输层协议,用以最小化网络延迟,提升网络速度,优化用户的网络使用体验。)
  • socket自动选择最好路线,并支持自动重连,拥有自动维护的socket连接池,减少握手次数,减少了请求延迟,共享Socket,减少对服务器的请求次数
  • 基于Headers的缓存策略减少重复的网络请求
  • 拥有Interceptors轻松处理请求与响应(自动处理GZip压缩)

OkHttp的作用:

  • PUT,DELETE,POST,GET等请求
  • 文件的上传下载
  • 加载图片(内部会图片大小自动压缩)
  • 支持请求回调,直接返回对象、对象集合
  • 支持session的保持

HttpURLConnection和OkHttp的比较:

  • HttpURLConnection有的API,OkHttp基本上都有
  • HttpURLConnection和OkHttp都支持Https,流的上传和下载,超时,IP6、连接池等等
  • OkHttp比HttpURLConnection具有更好的同步异步请求、缓存机制,支持HttpDNS、重定向、Gzip压缩,平台适应性、很好的服务器IP的转换、直接Socket通信,支持拦截器等等

和其它网络框架类似,OKhttp也主要由以下6个概念一起运作:

  • OkHttpClient:客户端
  • Dispatcher:线程池
  • Interceptor:拦截器(OKhttp的特色)
  • Request:请求
  • Response:响应
  • CallBack:回调

GitHub:OkHttp
GitHub:Okio

感觉真的很强大!OkHttp对于网络请求这块使应用更加省流量、请求的更快。OkHttp对于Https和HttpDNS的支持,使得应用的网络通信安全性更高。当然了,万事都有不足的地方,例如:

  • OkHttp不支持优先级请求
  • OkHttp不支持自签名证书
  • OkHttp header中不能传中文

三、OkHttp的常见用法

3.1 加载

在使用前需要先在项目中添加OkHttp的依赖库,在对应的Module的gradle中添加如下语句

implementation 'com.squareup.okhttp3:okhttp:3.11.0'

此外,OkHttp内部依赖另一个开源库OkIo,所以也要将它导入进来

implementation 'com.squareup.okio:okio:1.15.0'

3.2 Get请求流程

最常见的网络请求方式可以说就是Get请求了,这里来获取我的个人主页的网页内容:

  • 创建OkHttpClient对象
  • 通过Builder模式创建Request对象,参数必须有个url参数,可以通过Request.Builder设置更多的参数比如:header、method等
  • 通过request的对象去构造得到一个Call对象,Call对象有execute()和cancel()等方法。
  • 以异步的方式去执行请求,调用的是call.enqueue,将call加入调度队列,任务执行完成会在Callback中得到结果。
        OkHttpClient client = new OkHttpClient();
        //构造Request对象
        //采用建造者模式,链式调用指明进行Get请求,传入Get的请求地址
        Request request = new Request.Builder().get().url("http://www.baidu.co").build();
        Call call = client.newCall(request);
        //异步调用并设置回调函数
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                //toast
            }

            @Override
            public void onResponse(Call call, final Response response) throws IOException {
                final String responseStr = response.body().string();
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        //UI updata
                    }
                });
            }
        });

注意事项:

  • 异步调用的回调函数是在子线程,我们不能在子线程更新UI,需要借助于 runOnUiThread() 方法或者 Handler 来处理
  • onResponse回调有一个参数是response,如果我们想获得返回的是字符串,可以通过response.body().string()获取;如果希望获得返回的二进制字节数组,则调用response.body().bytes();如果你想拿到返回的inputStream,则调response.body().byteStream(),有inputStream我们就可以通过IO的方式写文件

Get请求也有同步调用,但一般使用的比较少:

    OkHttpClient client = new OkHttpClient();
    Request request = new Request.Builder()
        .url(url)
        .build();

    Response response = client.newCall(request).execute();
    String string = response.body().string();
    System.out.println(string);

    //创建OkHttpClient对象
    OkHttpClient okHttpClient = new OkHttpClient();
    //创建Request对象,设置一个url地址(个人站点),设置请求方式。
    Request request = new Request.Builder().url("http://www.xmamiga.com").method("GET",null).build();
    //创建一个call对象,参数就是Request请求对象
    Call call = okHttpClient.newCall(request);
    //同步调用会阻塞主线程,这边在子线程进行
    new Thread(new Runnable() {
        @Override
            public void run() {
            try {
                //同步调用,返回Response,会抛出IO异常
                Response response = call.execute();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }).start();

同步GET请求和异步GET请求基本一样,不同地方是同步请求调用Call的execute()方法,而异步请求调用call.enqueue()方法。

下载文件也是我们经常用到的功能,以下载图片为例:


OkHttpClient okHttpClient = new OkHttpClient(); Request request = new Request.Builder().url("https://www.xmamiga.com/img/tuoxiang.png").get().build(); Call call = okHttpClient.newCall(request); //请求加入调度,重写回调方法 call.enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { Log.e(TAG, "onFailure: "+call.toString() ); } @Override public void onResponse(Call call, Response response) throws IOException { //拿到字节流 InputStream is = response.body().byteStream(); int len = 0; //设置下载图片存储路径和名称 File file = new File(Environment.getExternalStorageDirectory(),"tuoxiang.png"); FileOutputStream fos = new FileOutputStream(file); byte[] buf = new byte[128]; while((len = is.read(buf))!= -1){ fos.write(buf,0,len); Log.e(TAG, "onResponse: "+len ); } fos.flush(); fos.close(); is.close(); } });

3.3 POST请求流程

流程和GET请求类似,不同的地方在于:

  • 需要RequestBody请求体封装各种类型的请求参数
  • 调用Request.Builder的post()方法,传入RequestBody,设置为POST请求

在OkHttp中用Post方法向服务器发送一个请求体时,请求体需要是一个RequestBody。这个请求体可以是:

  • key-value:键值对类型
  • String:字符串类型
  • Form:类似于Html的表单数据提交
  • Stream:流类型
  • File:文件类型
3.3.1 Post请求提交键值对
  • 创建OkHttpClient对象
  • 通过new FormBody()调用build方法,创建一个RequestBody,可以用add添加键值对 ,FormBody 是 RequestBody 的子类
  • 创建Request对象,设置URL地址,将RequestBody作为post方法的参数传入
  • 创建一个call对象,参数就是Request请求对象
  • 请求加入调度,重写回调方法
    OkHttpClient client = new OkHttpClient();
    //构建FormBody,传入要提交的参数
    FormBody formBody = new FormBody
            .Builder()
            .add("username", "initObject")
            .add("password", "initObject")
            .build();
    final Request request = new Request.Builder()
            .url("http://www.xmamiga.com/")
            .post(formBody)
            .build();
    Call call = client.newCall(request);
    call.enqueue(new Callback() {
        @Override
        public void onFailure(Call call, IOException e) {
            //toast
        }

        @Override
        public void onResponse(Call call, Response response) throws IOException {
            final String responseStr = response.body().string();
            //String.valueOf(response.code());
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    // UI updata
                }
            });
        }
    });
3.3.2 Post请求字符串

有时候我们会有要传送字符串的需要,比如向服务器发送一个JSON字符串。那么就可以用如下方法:

        OkHttpClient client = new OkHttpClient();
        //RequestBody中的MediaType指定为纯文本,编码方式是utf-8
        RequestBody requestBody = RequestBody.create(MediaType.parse("text/plain;charset=utf-8"),
                "{username:admin;password:admin}");
        final Request request = new Request.Builder()
                .url("http://www.xmamiga.com/")
                .post(requestBody)
                .build();
        Call call = client.newCall(request);
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                //toast
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                final String responseStr = response.body().string();
                // String.valueOf(response.code());
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        // UI updata
                    }
                });
            }
        });

3.3.3 Post 上传文件

文件的类型很多,有图片,TXT文档等,不同地方主要是RequestBody,以上传图片为例,代码如下:

    OkHttpClient okHttpClient = new OkHttpClient();
    //上传的图片
    File file = new File(Environment.getExternalStorageDirectory(), "tuoxiang.png");
    //通过RequestBody.create 创建requestBody对象,application/octet-stream 表示文件是任意二进制数据流
    RequestBody requestBody = RequestBody.create(MediaType.parse("application/octet-stream"), file);
    //创建Request对象,设置URL地址,将RequestBody作为post方法的参数传入
    Request request = new Request.Builder().url("url").post(requestBody).build();
    //创建一个call对象,参数就是Request请求对象
    Call call = okHttpClient.newCall(request);
    //请求加入调度,重写回调方法
    call.enqueue(new Callback() {
        @Override
        public void onFailure (Call call, IOException e){
            //toast
        }

        @Override
        public void onResponse (Call call, Response response) throws IOException {
            //TODO
        }
    });
3.3.4 Post 表单

有些情况下既要上传文件还要上传其他类型字段。比如在个人中心我们可以修改名字,年龄,修改图像,这其实就是一个表单。这里我们用到MuiltipartBody ,它 是RequestBody 的一个子类,我们提交表单就是利用这个类来构建一个 RequestBody,代码如下:


OkHttpClient okHttpClient = new OkHttpClient(); //上传的图片 File file = new File(Environment.getExternalStorageDirectory(), "tuoxiang.png"); //通过new MultipartBody build() 创建requestBody对象, RequestBody requestBody = new MultipartBody.Builder() //设置类型是表单 .setType(MultipartBody.FORM) //添加数据 .addFormDataPart("username","xmamiga") .addFormDataPart("age","25") .addFormDataPart("image","tuoxiang.png", RequestBody.create(MediaType.parse("image/png"),file)) .build(); //创建Request对象,设置URL地址,将RequestBody作为post方法的参数传入 Request request = new Request.Builder().url("url").post(requestBody).build(); //创建一个call对象,参数就是Request请求对象 Call call = okHttpClient.newCall(request); //请求加入调度,重写回调方法 call.enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { } @Override public void onResponse(Call call, Response response) throws IOException { } });

3.4 取消请求

开发中,有时需要取消请求,则OkHttp也提供了取接口:

call.cancel();//取消请求,不能取消已经准备完成的请求
okHttpClient.dispatcher().cancelAll();//取消所有请求

3.5 附加参数使用

为了多样化调用,OkHttp提供了附加参数的设置。

3.5.1 设置请求头
Request request = new Request.Builder()
                .url(url)
                .header("headerKey1", "headerValue1")// 设置请求头
                .headers(Headers headers)// 批量设置请求头
                .addHeader("headerKey2", "headerValue2")// 追加请求头
                .removeHeader("headerKey2")//去除请求头
                .build();
3.5.2 获取请求头
Response response=okHttpClient.newCall(request).execute();
response.header("responseHeader");
response.header("responseHeader", "defaultValue");
//
Headers headers = response.headers();

3.5.3 设置请求超时
okHttpClient = new OkHttpClient.Builder()
        .connectTimeout(10, TimeUnit.SECONDS)
        .writeTimeout(10, TimeUnit.SECONDS)
        .readTimeout(30, TimeUnit.SECONDS)
        .build();
3.5.4 设置缓存
File sdcache = getExternalCacheDir();
int cacheSize = 10 * 1024 * 1024;
okHttpClient = new OkHttpClient.Builder()
        .cache(new Cache(sdcache.getAbsoluteFile(), cacheSize));
        .build();

四、总结

由于OkHttp是偏底层的网络请求类库,返回结果的回调方法仍然执行在子线程中,需要自己跳转到UI线程,使用麻烦。为了使用方便需要对OKHttp进行再次封装,这就有了Retrofit(热豆腐)。