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"/>

1、异步get请求

    public void getAsyncRequest(){
        //1、创建OkHttpClient对象
        OkHttpClient client = new OkHttpClient();
        //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() {
                            
                        }
                    });
                }

            }
        });
    }

发送一个异步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、同步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();
    }

3、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() {

                        }
                    });
                }
            }
        });

    }

一个异步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() {

                        }
                    });
                }
            }
        });

    }

5、异步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则支持多类型的参数传递,在传输表单类型的参数的同时,还是可以传输文件。

6、异步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();
                }
        });
    }

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

    public void postAsyncRequest(){
        //创建OkHttpClient对象
        OkHttpClient client = new OkHttpClient();

        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() {

                        }
                    });
                }
            }
        });

    }

注意事项
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

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

推荐阅读更多精彩内容