android_框架_okhttp使用详解

121 篇文章 1 订阅
13 篇文章 0 订阅

转载自: https://www.jianshu.com/p/7d10f25b2492

Android OkHttp3简介和使用详解

一 、OKHttp简介

OKHttp是一个处理网络请求的开源项目,Android 当前最火热网络框架,由移动支付Square公司贡献,用于替代HttpUrlConnection和Apache HttpClient(android API23 6.0里已移除HttpClient)。
GitHub地址:https://github.com/square/okhttp

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

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

二 OkHttp3使用

首先要在AndroidManifest.xml里添加网络权限,如果需要上传或下载文件还需要添加存储卡读写权限:

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

2.1、get请求

1、异步get请求

        public void getAsyncRequest(){
            //1、创建OkHttpClient对象
            OkHttpClient client = new OkHttpClient();
            //通常使用OkHttpClient client = new OkHttpClient.Builder()//里面存在许多默认的配置
            //2、创建Request对象,设置一个URL地址,设置请求方式
            Request request = new Request.Builder()
                    .url("http://www.baidu.com")
                    .get()
                    .build();
            //3、创建一个call对象,参数为request请求对象
            Call call = client.newCall(request);
            //4、将请求加入调度,重写回调方法
            call.enqueue(new Callback() {
                @Override
                public void onFailure(Call call, IOException e) {
                    //请求失败
                }
    
                @Override
                public void onResponse(Call call, Response response) throws IOException {
                    //请求成功
                    if (response.isSuccessful()){
                        runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                
                            }
                        });
                    }
    
                }
            });
        }
2.1.1、发送一个异步GET请求的4个步骤:

(1)创建OkHttpClient对象
(2)通过Builder模式创建Request对象,参数必须有个url参数,可以通过Request.Builder设置更多的参数比如:header、method等
(3)通过request的对象去构造得到一个Call对象,Call对象有execute()和cancel()等方法。
(4)以异步的方式去执行请求,调用的是call.enqueue,将call加入调度队列,任务执行完成会在Callback中得到结果。
注意事项:
(1)Request.Builder中默认的使用Get请求,所以可以不调用get()方法
(2)无论是同步还是异步请求,接收到Response对象时均在子线程中,其中通过Response对象获取请求结果需要在子线程中完成,我们不能在子线程更新UI,需要借助于 runOnUiThread() 方法或者 Handler 来处理。
(3)onResponse回调有一个参数是response,如果我们想获得返回的是字符串,可以通过response.body().string()获取;如果希望获得返回的二进制字节数组,则调用response.body().bytes();如果你想拿到返回的inputStream,则调response.body().byteStream(),有inputStream我们就可以通过IO的方式写文件。onResponse执行的线程并不是UI线程。

2.1.2、同步GET请求

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

        public void getSyncRequest(){
            //1、创建OkHttpClient对象
            OkHttpClient client = new OkHttpClient();
            //2、创建request对象
            Request request = new Request.Builder()
                    .url("http://www.baidu.com")
                    .build();
            //3、创建call对象
            Call call = client.newCall(request);
            //4、同步调用会阻塞主线程,这边在子线程进行
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        //同步调用,返回response,会抛出IO异常
                        Response response = call.execute();
                        if (response.isSuccessful()){
                        }
    
                    } catch (IOException e){
                        e.printStackTrace();
                    }
                }
            }).start();
        }

2.2、post请求

2.2.1、POST请求提交键值对
        public void postAsyncRequest(){
            //创建OkHttpClient对象
            OkHttpClient client = new OkHttpClient();
    
            //通过new FormBody()调用build方法创建RequestBody,用add()添加键值对
            RequestBody requestBody = new FormBody.Builder()
                    .add("username","admin")
                    .add("password","123")
                    .build();
    
            //设置URL地址,将RequestBody作为post方法的参数传入
            Request request = new Request.Builder()
                    .url("http://www.baidu.com")
                    .post(requestBody)
                    .build();
            //创建call对象,参数就是Request请求对象
            Call call = client.newCall(request);
            //将请求加入调度,重写回调方法
            call.enqueue(new Callback() {
                @Override
                public void onFailure(Call call, IOException e) {
                    //请求失败
                }
    
                @Override
                public void onResponse(Call call, Response response) throws IOException {
                    //请求成功
                    if (response.isSuccessful()){
                        runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
    
                            }
                        });
                    }
                }
            });
    
        }
2.2.2、一个异步POST请求提交键值对的5个步骤:

(1)创建OkHttpClient对象。
(2)通过new FormBody()调用build方法,创建一个RequestBody,可以用add添加键值对 ,FormBody 是 RequestBody 的子类。
(3)创建Request对象,设置URL地址,将RequestBody作为post方法的参数传入。
(4)创建一个call对象,参数就是Request请求对象。
(5)请求加入调度,重写回调方法。

4、异步POST请求提交字符串(POST请求提交字符串和POST请求提交键值对非常相似,不同地方主要是RequestBody)

        public void postAsyncRequest(){
            //创建OkHttpClient对象
            OkHttpClient client = new OkHttpClient();
    
            MediaType mediaType = MediaType.parse("application/json; charset=utf-8");
            String value = "{username:admin;password:123}";
            //通过RequestBody.create创建RequestBody对象
            RequestBody requestBody = RequestBody.create(mediaType,value);
    
            //设置URL地址,将RequestBody作为post方法的参数传入
            Request request = new Request.Builder()
                    .url("http://www.baidu.com")
                    .post(requestBody)
                    .build();
            //创建call对象,参数就是Request请求对象
            Call call = client.newCall(request);
            //将请求加入调度,重写回调方法
            call.enqueue(new Callback() {
                @Override
                public void onFailure(Call call, IOException e) {
                    //请求失败
                }
    
                @Override
                public void onResponse(Call call, Response response) throws IOException {
                    //请求成功
                    if (response.isSuccessful()){
                        runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
    
                            }
                        });
                    }
                }
            });
    
        }
2.2.3、异步POST请求上传文件
        public void postAsyncRequest(){
            //创建OkHttpClient对象
            OkHttpClient client = new OkHttpClient();
    
            MediaType mediaType = MediaType.parse("application/octet-stream");
            File file = new File(Environment.getExternalStorageDirectory(),"1.png");
            RequestBody requestBody = RequestBody.create(mediaType,file);
    
            //设置URL地址,将RequestBody作为post方法的参数传入
            Request request = new Request.Builder()
                    .url("http://www.baidu.com")
                    .post(requestBody)
                    .build();
            //创建call对象,参数就是Request请求对象
            Call call = client.newCall(request);
            //将请求加入调度,重写回调方法
            call.enqueue(new Callback() {
                @Override
                public void onFailure(Call call, IOException e) {
                    //请求失败
                }
    
                @Override
                public void onResponse(Call call, Response response) throws IOException {
                    //请求成功
                    if (response.isSuccessful()){
                        runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
    
                            }
                        });
                    }
                }
            });
    
        }

RequestBody是一个抽象类,分别有FormBody和MultipartBody两个子类,上面这个例子使用的是FormBody,用于传输表单类型的参数。MultipartBody则支持多类型的参数传递,在传输表单类型的参数的同时,还是可以传输文件。

使用MultiparyBody.builder()方式构建[用来添加其他信息]

import okhttp3.*

/**
 * 
 */
class ClientUploadUtils {

    static ResponseBody upload(String url, String filePath, String fileName) throws Exception {
        OkHttpClient client = new OkHttpClient()
        RequestBody requestBody = new MultipartBody.Builder()
                .setType(MultipartBody.FORM)
                .addFormDataPart("file", fileName,
                        RequestBody.create(MediaType.parse("multipart/form-data"), new File(filePath)))
                .build()

        Request request = new Request.Builder()
                .header("Authorization", "Client-ID " + UUID.randomUUID())
                .url(url)
                .post(requestBody)
                .build()

        Response response = client.newCall(request).execute();
        if (!response.isSuccessful()) throw new IOException("Unexpected code " + response)

        return response.body()
    }


    static void main(String[] args) throws IOException {
        try {
            String fileName = "com.jdsoft.biz.test.zip"
            String filePath = "D:\\ExtJsTools\\Sencha\\Cmd\\repo\\pkgs\\test.zip"
            String url = "http://localhost:9990/upload_app_package"
            System.out.println(upload(url, filePath, fileName).string())
        } catch (Exception e) {
            e.printStackTrace()
        }
    }

}
2.2.4、异步GET请求下载文件
        //get请求异步下载图片
        public void getPhotoAsyncRequest(){
            //1、创建OkHttpClient对象
            OkHttpClient client = new OkHttpClient();
            //2、创建Request对象,设置一个URL地址,设置请求方式
            Request request = new Request.Builder()
                    .url("https://www.baidu.com/img/bd_logo1.png")
                    .get()
                    .build();
            //3、创建一个call对象,参数为request请求对象
            Call call = client.newCall(request);
            //4、将请求加入调度,重写回调方法
            call.enqueue(new Callback() {
                @Override
                public void onFailure(Call call, IOException e) {
                    //请求失败
                }
    
                @Override
                public void onResponse(Call call, Response response) throws IOException {
                    //请求成功
                    if (response.isSuccessful()){
                        //请求成功
                        //拿到字节流
                        InputStream is = response.body().byteStream();
                        int len = 0;
                        //设置下载图片存储路径和名称
                        File file = new File(Environment.getExternalStorageDirectory(),"baidu.png");
                        FileOutputStream fos = new FileOutputStream(file);
                        byte[] buf = new byte[128];
                        while ((len = is.read(buf))!=-1){
                            fos.write(buf,0,len);
                        }
                        fos.flush();
                        fos.close();
                        is.close();
    
    //                //下载图片并直接设置到ImageVeiw中
    //                InputStream is = response.body().byteStream();
    //                //使用BitmapFactory的decodeStream将图片的输入流直接转换为Bitmap
    //                final Bitmap bitmap = BitmapFactory.decodeStream(is);
    //                //在主线程中操作UI
    //                runOnUiThread(new Runnable() {
    //                    @Override
    //                    public void run() {
    //                        //将Bitmap设置到ImageView中
    //                        imageView.setImageBitmap(bitmap);
    //                    }
    //                });
    //                is.close();
                    }
            });
        }
2.2.5、异步POST请求上传Multipart文件(在有些情况下既要上传文件还要上传其他类型字段。比如在个人中心我们可以修改名字,年龄,修改图像,这其实就是一个表单。这里我们用到MuiltipartBody ,它 是RequestBody 的一个子类,我们提交表单就是利用这个类来构建一个 RequestBody)
        public void postAsyncRequest(){
            //创建OkHttpClient对象
            OkHttpClient client = new OkHttpClient();

//---------------
//        构建RequestBody
        /*val requestBody: RequestBody = MultipartBody.Builder()
            .setType(MultipartBody.FORM)
            .addFormDataPart("location", locationRequestParamValue)
            .addFormDataPart("file", fileName, RequestBody.create("image/jpg; charset=utf-8".toMediaTypeOrNull(), file))
            .build()*/
//--------------
    
            MediaType mediaType = MediaType.parse("image/png");
            File file = new File(Environment.getExternalStorageDirectory(),"1.png");
            RequestBody fileRequestBody = RequestBody.create(mediaType,file);
            RequestBody requestBody = new MultipartBody.Builder().setType(MultipartBody.FORM)
                    .addFormDataPart("username","admin")
                    .addFormDataPart("age","25")
                    .addFormDataPart("image","1.png",fileRequestBody)
                    .build();
    
            //设置URL地址,将RequestBody作为post方法的参数传入
            Request request = new Request.Builder()
                    .url("http://www.baidu.com")
                    .post(requestBody)
                    .build();
            //创建call对象,参数就是Request请求对象
            Call call = client.newCall(request);
            //将请求加入调度,重写回调方法
            call.enqueue(new Callback() {
                @Override
                public void onFailure(Call call, IOException e) {
                    //请求失败
                }
    
                @Override
                public void onResponse(Call call, Response response) throws IOException {
                    //请求成功
                    if (response.isSuccessful()){
                        runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
    
                            }
                        });
                    }
                }
            });
    
        }
2.2.6、注意事项

MultipartBody.Builder().addFormDataPart() 方法的第一个参数就是类似于键值对的键,是供服务端使用的,第二个参数是文件的本地的名字,第三个参数是 RequestBody,里面包含了我们要上传的文件的路径以及 MediaType。MultipartBody中可以添加一个或多个RequestBody对象及其他表单类型参数。
总结:
(1)当请求的RequestBody中只有表单类型参数时,使用FormBody.Builder()方法创建RequestBody对象
(2)当请求的RequestBody中是字符串或者文件时,使用RequestBody.create()方法创建RequestBody对象
(3)当请求的RequestBody中既有文件类型又有表单类型参数时,使用RequestBody.create()创建一个文件RequestBody对象,然后使用MultipartBody.Bulider()的addForDataPart()把文件RequestBody对象和其他表单类型参数添加进去,生成一个最终的RequestBody对象。
(4)无论post什么类型的数据,区别都在于创建RequestBody对象的方法

三、设置超时时间

OkHttp可以设置调用、连接和读写的超时时间,都是通过OkHttpClient.Builder设置的。如果不主动设置,OkHttp将使用默认的超时设置

    OkHttpClient mClient = new OkHttpClient.Builder()
            .callTimeout(6_000, TimeUnit.MILLISECONDS)
            .connectTimeout(6_000, TimeUnit.MILLISECONDS)
            .readTimeout(20_000, TimeUnit.MILLISECONDS)
            .writeTimeout(20_000, TimeUnit.MILLISECONDS)
            .build();

四、设置请求Header

请求的Header是通过Request.Builder对象的相关方法来维护的,如下:

headers(Headers headers)  
header(String name, String value)  
addHeader(String name, String value)  
removeHeader(String name)  

addHeader和removeHeader方法比较好理解,分别是添加和移除header信息。header(String name, String value)这是会重新设置指定name的header信息。
headers(Headers headers)则是会移除掉原有的所有header信息,将参数headers的header信息添加到请求中。这是这几个方法的一些差别。
Cookie也是header信息中的一个字段,通过Header相关方法添加就好了。
使用的话都是Builder模式的链式调用,举个栗子

    Request request = new Request.Builder()
            .header("Accept","image/webp")
            .addHeader("Charset","UTF-8")
            .url(url)
            .build();

五、Interceptors(拦截器)

拦截器是OkHttp当中一个比较强大的机制,可以监视、重写和重试调用请求。
这是一个比较简单的Interceptor的实现,对请求的发送和响应进行了一些信息输出。

    class LoggingInterceptor implements Interceptor {
        public static final String TAG = "Http_log";
    
        @Override
        public Response intercept(Interceptor.Chain chain) throws IOException {
            Request request = chain.request();
    
            long t1 = System.nanoTime();
            Log.i(TAG, String.format("Sending request %s on %s%n%s",
                    request.url(), chain.connection(), request.headers()));
    
            Response response = chain.proceed(request);
    
            long t2 = System.nanoTime();
            Log.i(TAG, String.format("Received response for %s in %.1fms%n%s",
                    response.request().url(), (t2 - t1) / 1e6d, response.headers()));
    
            return response;
        }
    }

需要实现其中intercept(Interceptor.Chain chain)方法,同时必须调用chain.proceed(request)代码,也就是网络请求真正发生的地方。
拦截器可以设置多个,并且拦截器的调用是有顺序的。官网举的例子是,同时添加一个压缩拦截器和一个校验拦截器,需要决定数据是先被压缩在校验,还是先校验在压缩。
拦截器还分为应用拦截器(Application Interceptors)和网络拦截器(Network Interceptors)

在这里插入图片描述

image

Application Interceptors
先看看应用拦截器,通过OkHttpClient.Builder的addInterceptor方法添加拦截器

    OkHttpClient client = new OkHttpClient.Builder()
        .addInterceptor(new LoggingInterceptor())
        .build();
    
    Request request = new Request.Builder()
        .url("http://www.publicobject.com/helloworld.txt")
        .header("User-Agent", "OkHttp Example")
        .build();
    
    Response response = client.newCall(request).execute();
    response.body().close();

看请求和响应的两个链接是不同的,URL http://www.publicobject.com/helloworld.txt会重定向到 https://publicobject.com/helloworld.txt,OkHttp会自动跟随重定向,而应用拦截器只被调用一次,并且chain.proceed()返回的Response对象是具有重定向响应对象。

INFO: Sending request http://www.publicobject.com/helloworld.txt on null
User-Agent: OkHttp Example

INFO: Received response for https://publicobject.com/helloworld.txt in 1179.7ms
Server: nginx/1.4.6 (Ubuntu)
Content-Type: text/plain
Content-Length: 1759
Connection: keep-alive

Network Interceptors
再来看看网络拦截器,通过OkHttpClient.Builder的addNetworkInterceptor方法添加拦截器

    OkHttpClient client = new OkHttpClient.Builder()
        .addNetworkInterceptor(new LoggingInterceptor())
        .build();
    
    Request request = new Request.Builder()
        .url("http://www.publicobject.com/helloworld.txt")
        .header("User-Agent", "OkHttp Example")
        .build();
    
    Response response = client.newCall(request).execute();
    response.body().close();

结果日志:

INFO: Sending request http://www.publicobject.com/helloworld.txt on Connection{www.publicobject.com:80, proxy=DIRECT hostAddress=54.187.32.157 cipherSuite=none protocol=http/1.1}
User-Agent: OkHttp Example
Host: www.publicobject.com
Connection: Keep-Alive
Accept-Encoding: gzip

INFO: Received response for http://www.publicobject.com/helloworld.txt in 115.6ms
Server: nginx/1.4.6 (Ubuntu)
Content-Type: text/html
Content-Length: 193
Connection: keep-alive
Location: https://publicobject.com/helloworld.txt

INFO: Sending request https://publicobject.com/helloworld.txt on Connection{publicobject.com:443, proxy=DIRECT hostAddress=54.187.32.157 cipherSuite=TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA protocol=http/1.1}
User-Agent: OkHttp Example
Host: publicobject.com
Connection: Keep-Alive
Accept-Encoding: gzip

INFO: Received response for https://publicobject.com/helloworld.txt in 80.9ms
Server: nginx/1.4.6 (Ubuntu)
Content-Type: text/plain
Content-Length: 1759
Connection: keep-alive

从日志来看,拦截器运行了两次,第一次请求了http://www.publicobject.com/helloworld.txt,第二次则是重定向到https://publicobject.com/helloworld.txt。同时通过网络拦截能获得更多的header信息

转自:https://blog.csdn.net/zhangqiluGrubby/article/details/71480546
https://www.jianshu.com/p/aaa87e8ad9eb

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值