OkHttp3基础使用


前言

Android中的网络请求框架,基本是okhttp和Retrofit一统天下,而Retrofit又是以okhttp为基础,所以系统学习okhttp的使用和原理就很有必要了。

okhttp是由square公司开发,Android中公认最好用的网络请求框架,在接口封装上做的简单易用,

它有以下默认特性:

支持HTTP/2,允许所有同一个主机地址的请求共享同一个socket连接
使用连接池减少请求延时
透明的GZIP压缩减少响应数据的大小
缓存响应内容,避免一些完全重复的请求
当网络出现问题的时候OkHttp 会自动恢复一般的连接问题,如果你的服务有多个IP地址,当第一个IP请求失败时,OkHttp会交替尝试你配置的其他IP。

一、依赖引入

gradle引入依赖即可。

implementation 'com.squareup.okhttp3:okhttp:3.14.7'
implementation 'com.squareup.okio:okio:1.17.5'

其中Okio库 是对Java.io和java.nio的补充,以便能够更加方便,快速的访问、存储和处理你的数据。OkHttp的底层使用该库作为支持。
另外,别忘了申请网络请求权限,如果还使用网络请求的缓存功能,那么还要申请读写外存的权限:

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

二、使用方式

基本使用步骤如下

构建客户端对象OkHttpClient
构建请求Request
生成Call对象
Call发起请求(同步/异步)

下面跟着具体使用实例,详细介绍

2.1 get请求

get方式中又可以分为两种情况,分别是同步请求异步请求
同步请求在进行请求的时候,当前线程会阻塞住,直到得到服务器的响应后,后面的代码才会执行;
而异步请求不会阻塞当前线程,它采用了回调的方式,请求是在另一个线程中执行的,不会影响当前的线程。
以百度主页为例,进行Get请求:

2.1.1 同步请求

        public void getSync() {
        //同步请求
        OkHttpClient httpClient = new OkHttpClient();
        String url = "https://www.baidu.com/";
        Request getRequest = new Request.Builder()
                .url(url)
                .get()
                .build();
        //准备好请求的Call对象
        Call call = httpClient.newCall(getRequest);
        try {
            Response response = call.execute();
            Log.i(TAG, "okHttpGet run: response:"+ response.body().string());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

首先,创建了OkHttpClient实例,接着用Request.Builder构建了Request实例并传入了百度主页的url,然后httpClient.newCall方法传入Request实例生成call,最后在子线程调用call.execute()执行请求获得结果response。

使用OkHttp进行get请求,是比较简单的,只要在构建Request实例时更换url就可以了。

2.1.2 异步请求

call.execute()是同步方法。想要在主线程直接使用可以嘛?当然可以,使用call.enqueue(callback)即可:

    public void getAsync() {
        //异步请求
        OkHttpClient httpClient = new OkHttpClient();
        String url = "https://www.baidu.com/";
        Request getRequest = new Request.Builder()
                .url(url)
                .get()
                .build();
        //准备好请求的Call对象
        Call call = httpClient.newCall(getRequest);
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                Log.i(TAG, "okHttpGet enqueue: onResponse:" + response.body().string());
                ResponseBody body = response.body();
                String string = body.string();
                byte[] bytes = body.bytes();
                InputStream inputStream = body.byteStream();
            }
        });
    }

call.enqueue会异步执行,需要注意的是,两个回调方法onFailure、onResponse是执行在子线程的,所以如果想要执行UI操作,需要使用Handler或其他方式切换到UI线程。

2.1.3 取消请求

每一个Call只能执行一次。如果想要取消正在执行的请求,可以使用call.cancel(),通常在离开页面时都要取消执行的请求的。

2.1.4 结果处理

请求回调的两个方法是指 传输层 的失败和成功。onFailure通常是connection连接失败或读写超时;onResponse是指成功得从服务器获取到了结果,但是这个结果的响应码可能是404、500等,也可能就是200(response.code()的取值)。

如果response.code()是200,表示应用层请求成功了。此时我们可以获取Response的ResponseBody,这是响应体。从面看到,可以从ResponseBody获取string、byte[ ]、InputStream,这样就可以对结果进行很多操作了,比如UI上展示string(要用Handler切换到UI线程)、通过InputStream写入文件等等。

上面异步请求执行后 结果打印如下:

okHttpGet run: response:<!DOCTYPE html> <!--STATUS OK--><html> <head><meta http-equiv=content-type content=text/html;charset=utf-8><meta http-equiv=X-UA-Compatible content=IE=Edge><meta content=always name=referrer><link rel=stylesheet type=text/css 
href=https://ss1.bdstatic.com/5eN1bjq8AAUYm2zgoY3K/r/www/cache/bdorz/baidu.min.css><title>百度一下,你就知道</title></head> <body link=#0000cc> <div id=wrapper> <div id=head> <div class=head_wrapper> <div class=s_form> <div class=s_form_wrapper> <div id=lg> 
<img hidefocus=true src=//www.baidu.com/img/bd_logo1.png width=270 height=129> </div> <form id=form name=f action=//www.baidu.com/s class=fm> <input type=hidden name=bdorz_come value=1> <input type=hidden name=ie value=utf-8> <input type=hidden name=f value=8> <input type=hidden name=rsv_bp value=1> <input type=hidden name=rsv_idx value=1> <input type=hidden name=tn value=baidu><span class="bg s_ipt_wr"><input id=kw name=wd class=s_ipt value maxlength=255 autocomplete=off autofocus=autofocus></span><span class="bg s_btn_wr"><input type=submit id=su value=百度一下 class="bg s_btn" autofocus></span> </form> </div> </div> <div id=u1> <a href=http://news.baidu.com name=tj_trnews class=mnav>新闻</a> <a href=https://www.hao123.com name=tj_trhao123 class=mnav>hao123</a> <a href=http://map.baidu.com name=tj_trmap class=mnav>地图</a> <a href=http://v.baidu.com name=tj_trvideo class=mnav>视频</a> <a href=http://tieba.baidu.com name=tj_trtieba class=mnav>贴吧</a> <noscript> <a href=http://www.baidu.com/bdorz/login.gif?login&amp;tpl=mn&amp;u=http%3A%2F%2Fwww.baidu.com%2f%3fbdorz_come%3d1 name=tj_login class=lb>登录</a> </noscript> <script>document.write('<a href="http://www.baidu.com/bdorz/login.gif?login&tpl=mn&u='+ encodeURIComponent(window.location.href+ (window.location.search === "" ? "?" : "&")+ "bdorz_come=1")+ '" name="tj_login" class="lb">登录</a>');                                                                                                                   </script> <a href=//www.baidu.com/more/ name=tj_briicon class=bri style="display: block;">更多产品</a> </div> </div> </div> <div id=ftCon> <div id=ftConw> <p id=lh> <a href=http://home.baidu.com>关于百度</a> <a href=http://ir.baidu.com>About Baidu</a> </p> <p id=cp>&copy;2017&nbsp;Baidu&nbsp;<a href=http://www.baidu.com/duty/>使用百度前必读</a>&nbsp; <a href=http://jianyi.baidu.com/ class=cp-feedback>意见反馈</a>&nbsp;京ICP证030173号&nbsp; <img src=//www.baidu.com/img/gs.gif> </p> </div> </div> </div> </body> </html>

可见百度首页的get请求成功响应了。


2.2 post请求

POST请求将参数放在请求的主体中,不会直接显示在URL中。Post请求也分为同步和异步方式,和get方式用法相同,代码如下

2.2.1 同步post

public void postSync(){//同步请求
        new Thread(new Runnable() {
            @Override
            public void run() {
                OkHttpClient okHttpClient=new OkHttpClient();
                FormBody formBody=new FormBody.Builder()
                        .add("a","1")
                        .add("b","2")
                        .build();
                Request request=new Request.Builder()
                        .post(formBody)
                        .url("https://www.httpbin.org/post")
                        .build();
                //准备好请求的Call对象
                Call call = okHttpClient.newCall(request);
                try {
                    Response response = call.execute();
                    Log.i("postSync",response.body().string());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }

2.2.2 异步post

 public void postAsync(){//异步请求
        OkHttpClient okHttpClient=new OkHttpClient();
        FormBody formBody=new FormBody.Builder()
                .add("a","1")
                .add("b","2")
                .build();
        Request request=new Request.Builder()
                .post(formBody)
                .url("https://www.httpbin.org/post")
                .build();
        //准备好请求的Call对象
        Call call = okHttpClient.newCall(request);
        call.enqueue(new Callback() {
            @Override
            public void onFailure(@NonNull Call call, @NonNull IOException e) {

            }

            @Override
            public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
                if(response.isSuccessful()){
                    Log.i("postAsync",response.body().string());
                }
            }
        });
    }

2.2.3 post请求提交多种格式

2.2.3.1 post请求提交String、文件

post请求与get请求的区别 是在构造Request对象时,需要多构造一个RequestBody对象,用它来携带我们要提交的数据,其他都是一样的。示例如下:

        OkHttpClient httpClient = new OkHttpClient();

        MediaType contentType = MediaType.parse("text/x-markdown; charset=utf-8");
        String content = "hello!";
        RequestBody body = RequestBody.create(contentType, content);

        Request getRequest = new Request.Builder()
                .url("https://api.github.com/markdown/raw")
                .post(body)
                .build();

        Call call = httpClient.newCall(getRequest);

        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                Log.i(TAG, "okHttpPost enqueue: \n onResponse:"+ response.toString() +"\n body:" +response.body().string());
            }
        });

对比get请求,把构建Request时的get()改成post(body),并传入RequestBody实例。RequestBody实例是通过create方法创建,需要指定请求体内容类型、请求体内容。这里是传入了一个指定为markdown格式的文本。

传入RequestBody的 MediaType 还可以是其他类型,如客户端要给后台发送json字符串、发送一张图片,那么可以定义为:

// RequestBody:jsonBody,json字符串
String json = "jsonString";
RequestBody jsonBody = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), json);

//RequestBody:fileBody, 上传文件
File file = new File(Environment.getExternalStorageDirectory(), "1.png");
RequestBody fileBody = RequestBody.create(MediaType.parse("image/png"), file);

MediaType更多类型信息可以查看 RFC 2045:https://tools.ietf.org/html/rfc2045。
查看各个文件类型所对应的Content-type字符串,可以访问以下这个网址:https://www.runoob.com/http/http-content-type.html

2.2.3.2 post请求提交表单

构建RequestBody除了上面的方式,还有它的子类FormBody,FormBody用于提交表单键值对,这种能满足平常开发大部分的需求。

//RequestBody:FormBody,表单键值对
RequestBody formBody = new FormBody.Builder()
        .add("username", "henry")
        .add("password", "666")
        .build();

FormBody是通过FormBody.Builder用构建者模式创建,add键值对即可。它的contentType在内部已经指定了。

2.2.3.3 post请求提交复杂请求体

RequestBody另一个子类MultipartBody,用于post请求提交复杂类型的请求体。复杂请求体可以同时包含多种类型的的请求体数据。

上面介绍的 post请求 string、文件、表单,只有单一类型。考虑一种场景–注册场景,用户填写完姓名、电话,同时要上传头像图片,这时注册接口的请求体就需要 接受 表单键值对 以及文件了,那么前面讲的的post就无法满足了。那么就要用到MultipartBody了。 完整代码如下:

        OkHttpClient httpClient = new OkHttpClient();

//        MediaType contentType = MediaType.parse("text/x-markdown; charset=utf-8");
//        String content = "hello!";
//        RequestBody body = RequestBody.create(contentType, content);

        //RequestBody:fileBody,上传文件
        File file = drawableToFile(this, R.mipmap.bigpic, new File("00.jpg"));
        RequestBody fileBody = RequestBody.create(MediaType.parse("image/jpg"), file);


        //RequestBody:multipartBody, 多类型 (用户名、密码、头像)
        MultipartBody multipartBody = new MultipartBody.Builder()
                .setType(MultipartBody.FORM)
                .addFormDataPart("username", "hufeiyang")
                .addFormDataPart("phone", "123456")
                .addFormDataPart("touxiang", "00.png", fileBody)
                .build();


        Request getRequest = new Request.Builder()
                .url("http://yun918.cn/study/public/file_upload.php")
                .post(multipartBody)
                .build();

        Call call = httpClient.newCall(getRequest);

        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {

                Log.i(TAG, "okHttpPost enqueue: \n onFailure:"+ call.request().toString() +"\n body:" +call.request().body().contentType()
                +"\n IOException:"+e.getMessage());
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                Log.i(TAG, "okHttpPost enqueue: \n onResponse:"+ response.toString() +"\n body:" +response.body().string());
            }
        });

在构建RequestBody时是使用MultipartBody.Builder构建了MultipartBody实例,通过addFormDataPart方法传入了姓名、电话的键值对,也通过addFormDataPart(“touxiang”, “00.png”, fileBody)传入了头像图片,其中"touxiang"是key值, "00.png"是文件名,fileBody是要以上传的图片创建的RequestBody。
因为所有数据都是以键值对的表单形式提交,所以要设置setType(MultipartBody.FORM)。

其他请求方式像put、header、delete,主要在构建Request时把get()或post()换成put()、header()、delete()就可以了,但一般在Android端很少用到。

2.3 请求配置项

先看几个问题:

如何全局设置超时时长?
缓存位置、最大缓存大小 呢?
考虑有这样一个需求,我要监控App通过 OkHttp 发出的 所有 原始请求,以及整个请求所耗费的时间,如何做?

这些问题,在OkHttp这里很简单。把OkHttpClient实例的创建,换成以下方式即可:

        OkHttpClient client = new OkHttpClient.Builder()
                .connectTimeout(15, TimeUnit.SECONDS)
                .readTimeout(10, TimeUnit.SECONDS)
                .writeTimeout(10, TimeUnit.SECONDS)
                .cache(new Cache(getExternalCacheDir(),500 * 1024 * 1024))
                .addInterceptor(new Interceptor() {
                    @Override
                    public Response intercept(Chain chain) throws IOException {
                        Request request = chain.request();
                        String url = request.url().toString();
                        Log.i(TAG, "intercept: proceed start: url"+ url+ ", at "+System.currentTimeMillis());
                        Response response = chain.proceed(request);
                        ResponseBody body = response.body();
                        Log.i(TAG, "intercept: proceed end: url"+ url+ ", at "+System.currentTimeMillis());
                        return response;
                    }
                })
                .build();

这里通过OkHttpClient.Builder通过构建者模式设置了连接、读取、写入的超时时长,用cache()方法传入了由缓存目录、缓存大小构成的Cache实例,这样就解决了前两个问题。

使用addInterceptor()方法添加了Interceptor实例,且重写了intercept方法。Interceptor意为拦截器,intercept()方法会在开始执行请求时调用。其中chain.proceed(request)内部是真正请求的过程,是阻塞操作,执行完后会就会得到请求结果ResponseBody,所以chain.proceed(request)的前后取当前时间,那么就知道整个请求所耗费的时间。上面chain.proceed(request)的前后分别打印的日志和时间,这样第三个问题也解决了。

具体Interceptor是如何工作,后面介绍。
另外,通常OkHttpClient实例是全局唯一的,这样这些基本配置就是统一,且内部维护的连接池也可以有效复用(后面介绍)。

全局配置的有了,单个请求的也可以有一些单独的配置。

        Request getRequest = new Request.Builder()
                .url("http://yun918.cn/study/public/file_upload.php")
                .post(multipartBody)
                .addHeader("key","value")
                .cacheControl(CacheControl.FORCE_NETWORK)
                .build();
  • 使用addHeader()方法添加了请求头。
  • 使用cacheControl(CacheControl.FORCE_NETWORK)设置此次请求是能使用网络,不用缓存。(还可以设置只用缓存FORCE_CACHE。)

2.4 拦截器的使用

OkHttp的拦截器(Interceptors)提供了强大的自定义和修改HTTP请求和响应的能力。拦截器允许在发送请求前、收到响应后以及其他阶段对HTTP流量进行拦截和处理。例如:拦截器可以修改请求的URL、请求方法、请求头部、请求体等。这对于添加身份验证头、设置缓存控制头等场景很有用。用法如下:

public void interceptor(){
        OkHttpClient okHttpClient=new OkHttpClient.Builder()//添加拦截器的使用OkHttpClient的内部类Builder
                .addInterceptor(new Interceptor() {//使用拦截器可以对所有的请求进行统一处理,而不必每个request单独去处理
                    @NonNull
                    @Override
                    public Response intercept(@NonNull Chain chain) throws IOException {
                        //前置处理,以proceed方法为分割线:提交请求前
                        Request request = chain.request().newBuilder()
                                .addHeader("id", "first request")
                                .build();
                        Response response = chain.proceed(request);
                        //后置处理:收到响应后
                        return response;
                    }
                })
                .addNetworkInterceptor(new Interceptor() {//这个在Interceptor的后面执行,无论添加顺序如何
                    @NonNull
                    @Override
                    public Response intercept(@NonNull Chain chain) throws IOException {
                        Log.i("id",chain.request().header("id"));
                        return chain.proceed(chain.request());
                    }
                })
                .cache(new Cache(new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).getAbsolutePath()+"/cache"),1024*1024))//添加缓存
                .build();
        Request request=new Request.Builder()
                .url("https://www.httpbin.org/get?a=1&b=2")
                .build();
        //准备好请求的Call对象
        Call call = okHttpClient.newCall(request);
        //异步请求
        call.enqueue(new Callback() {
            @Override
            public void onFailure(@NonNull Call call, @NonNull IOException e) {

            }

            @Override
            public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
                if(response.isSuccessful()){
                    Log.i("interceptor",response.body().string());
                }
            }
        });
    }

2.5 Cookie的使用

大家应该有这样的经历,就是有些网站的好多功能都需要用户登录之后才能访问,而这个功能可以用cookie实现,在客户端登录之后,服务器给客户端发送一个cookie,由客户端保存;然后客服端在访问需要登录之后才能访问的功能时,只要携带这个cookie,服务器就可以识别该用户是否登录。用法如下:

public void cookie(){
        Map<String,List<Cookie>> cookies=new HashMap<>();
        new Thread(new Runnable() {
            @Override
            public void run() {
                OkHttpClient okHttpClient=new OkHttpClient.Builder()
                        .cookieJar(new CookieJar() {
                            @Override
                            public void saveFromResponse(@NonNull HttpUrl httpUrl, @NonNull List<Cookie> list) {//保存服务器发送过来的cookie
                                cookies.put("cookies",list);
                            }

                            @NonNull
                            @Override
                            public List<Cookie> loadForRequest(@NonNull HttpUrl httpUrl) {//请求的时候携带cookie
                                if(httpUrl.equals("www.wanandroid.com")){
                                    return cookies.get("cookies");
                                }
                                return new ArrayList<>();
                            }
                        })
                        .build();
                FormBody formBody=new FormBody.Builder()
                        .add("username","ibiubiubiu")
                        .add("password","Lhh823924.")
                        .build();
                Request request=new Request.Builder()  //模拟登录
                        .url("https://wanandroid.com/user/lg")
                        .post(formBody)
                        .build();
                //准备好请求的Call对象
                Call call = okHttpClient.newCall(request);
                try {
                    Response response = call.execute();
                    Log.i("login",response.body().string());
                } catch (IOException e) {
                    e.printStackTrace();
                }
                //请求收藏页面,必须登录之后才能访问到
                request=new Request.Builder()
                        .url("https://wanandroid.com/lg/collect")
                        .build();
                //准备好请求的Call对象
                call = okHttpClient.newCall(request);
                try {
                    Response response = call.execute();
                    Log.i("collect",response.body().string());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }

2.6 封装OkHttp单例模式

public class OkHttpUtils {
    /**
     * 单例模式
     */
    private static OkHttpUtils okHttpUtils = null;
 
    private OkHttpUtils() {
    }
 
    public static OkHttpUtils getInstance() {
        //双层判断,同步锁
        if (okHttpUtils == null) {
            synchronized (OkHttpUtils.class) {
                if(okHttpUtils == null){
                    okHttpUtils = new OkHttpUtils();
                }
            }
        }
        return okHttpUtils;
    }
 
    /**
     * 单例模式
     * 封装OkhHttp
     * synchronized同步方法
     */
    private static OkHttpClient okHttpClient = null;
 
    private static synchronized OkHttpClient getOkHttpClient() {
        if (okHttpClient == null) {
            //拦截器
            HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() {
                @Override
                public void log(String message) {
                    //拦截日志消息
                    Log.i("henry", "log: " + message);
                }
            });
            //设置日志拦截器模式
            interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
 
            okHttpClient = new OkHttpClient.Builder()
                //日志拦截器
                .addInterceptor(interceptor)
                //应用拦截器
                .addInterceptor(new Interceptor() {
                    @Override
                    public Response intercept(Chain chain) throws IOException {
                        Request request = chain.request()
                                .newBuilder()
                                .addHeader("source", "android")
                                .build();
 
                        return chain.proceed(request);
                    }
                })
                .build();
        }
        return okHttpClient;
    }
 
    /**
     * doGet
     */
    public void doGet(String url, Callback callback) {
        //创建okhttp
        OkHttpClient okHttpClient = getOkHttpClient();
        Request request = new Request.Builder()
                .url(url)
                .build();
        okHttpClient.newCall(request).enqueue(callback);
    }
 
    /**
     * doPost
     */
    public void doPost(String url, Map<String, String> params, Callback callback) {
        OkHttpClient okHttpClient = getOkHttpClient();
        //请求体
        FormBody.Builder formBody = new FormBody.Builder();
        for (String key : params.keySet()) {
            //遍历map集合
            formBody.add(key, params.get(key));
        }
        Request request = new Request.Builder()
                .url(url)
                .post(formBody.build())
                .build();
        okHttpClient.newCall(request).enqueue(callback);
    }
}

实现了单例模式来确保 OkHttpUtils 和 OkHttpClient 实例的唯一性


三、配置https的自签证书和信任所有证书

HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,要比http协议安全。
一般支持https的网站,都是CA(Certificate Authority)机构颁发的证书,但是一般该机构颁发的证书需要提供费用且有使用时间的限制,到期需要续费。否则默认该链接是不信任的,通过okHttp无法直接访问。
在这里插入图片描述

但是我们可以使用自签的方式,通过JDK自带的keytool.exe 生成一个自己的证书,然后使用该证书内容。虽然也是会出现提示“不安全”,但是我们可以通过okhttp访问链接。

3.1 使用自签证书

将证书文件放置在assets目录(也可以放置在其他目录下,只要能正确读取到该文件),在创建OkhttpClient对象时sslSocketFactory()将该证书信息添加。

private SSLContext getSLLContext() {
        SSLContext sslContext = null;
        try {
            CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
            InputStream certificate = mContext.getAssets().open("gdroot-g2.crt");
            KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
            keyStore.load(null);
            String certificateAlias = Integer.toString(0);
            keyStore.setCertificateEntry(certificateAlias, certificateFactory.generateCertificate(certificate));
            sslContext = SSLContext.getInstance("TLS");
            final TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
            trustManagerFactory.init(keyStore);
            sslContext.init(null, trustManagerFactory.getTrustManagers(), new SecureRandom());
        } catch (CertificateException e) {
            e.printStackTrace();
        } catch (KeyStoreException e) {
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (KeyManagementException e) {
            e.printStackTrace();
        }
        return sslContext;
    }

通过以上代码可以看出,通过InputStream方式读取证书信息,因此为了避免将证书文件打包到APK中,我们可以直接将证书文件内容放置在String中,将该字符串转为流的形式。

在使用okhttp时,将其设置到sslSocketFactory中。

OkHttpClient httpClient = new OkHttpClient().newBuilder()
                .sslSocketFactory(getSLLContext().getSocketFactory())
                .build();

3.2 信任所有证书

通过添加证书的形式,可以实现客户端访问Https服务端的功能,但是如果服务端更换证书内容,那么客户端需要相应的更换https证书,否则无法正常交互获取不到数据,我们可以通过自定义X509TrustManager的形式实现来规避所有的证书检测,实现信任所有证书的目的。

 private OkHttpClient getHttpsClient() {
        OkHttpClient.Builder okhttpClient = new OkHttpClient().newBuilder();
        //信任所有服务器地址
        okhttpClient.hostnameVerifier(new HostnameVerifier() {
            @Override
            public boolean verify(String s, SSLSession sslSession) {
                //设置为true
                return true;
            }
        });
        //创建管理器
        TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
            @Override
            public void checkClientTrusted(
                    java.security.cert.X509Certificate[] x509Certificates,
                    String s) throws java.security.cert.CertificateException {
            }

            @Override
            public void checkServerTrusted(
                    java.security.cert.X509Certificate[] x509Certificates,
                    String s) throws java.security.cert.CertificateException {
            }

            @Override
            public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                return new java.security.cert.X509Certificate[] {};
            }
        } };
        try {
            SSLContext sslContext = SSLContext.getInstance("TLS");
            sslContext.init(null, trustAllCerts, new java.security.SecureRandom());

            //为OkHttpClient设置sslSocketFactory
            okhttpClient.sslSocketFactory(sslContext.getSocketFactory());

        } catch (Exception e) {
            e.printStackTrace();
        }

        return okhttpClient.build();
    }

创建X509TrustManager对象,并实现其中的方法,由于X509TrustManager是通用证书格式,只需要拿到该格式就行。最后init该安全协议,将其放入okhttp的sslSocketFactory中。
由于Retrofit只是对Okhttp网络接口的封装,因此实际使用中,该方法同样适用于Retrofit中。


参考链接:
以下是一些OkHttp相关的优秀文章:
网络请求-Android篇(Okhttp和Retrofit)
OkHttp使用踩坑记录总结(一):OkHttpClient单例和长连接Connection Keep-Alive
OkHttp使用踩坑记录总结(二):OkHttp同步异步请求和连接池线程池
Android网络编程(一)HTTP协议原理
Android okhttp3.0配置https的自签证书和信任所有证书
OkHttp的工作流程分析

### 回答1: OKHttp3 是一个开源的用于HTTP请求的工具类库,是Square公司开发的。它构建在OkIO库的基础上,并且提供了强大且灵活的功能,用于进行网络请求和数据交互。 OKHttp3 支持GET、POST、PUT、DELETE等常用的HTTP请求方法,可以方便地与Web服务器进行数据交互。它提供了丰富的功能,如连接池管理、请求重试、请求拦截器、响应拦截器等,能够满足各种复杂的网络请求需求。 使用OKHttp3工具类进行网络请求也十分简单。首先,我们需要创建一个OKHttpClient实例,可以设置一些配置,如连接超时时间、读取超时时间等。然后,我们可以通过OKHttpClient生成一个Request对象,设置请求的URL、请求方法以及请求体等。接下来,使用这个Request对象来创建一个Call对象,再调用execute或enqueue方法发送请求。最后,我们可以通过Response对象获取到服务器返回的响应数据。 除了基本的网络请求功能,OKHttp3还支持多线程并发请求、文件上传和下载、WebSocket等高级特性。它的设计易于扩展和定制,可以与其他框架(如RxJava、Retrofit)配合使用,进一步简化网络请求的操作。 总之,OKHttp3是一个功能强大、灵活易用的工具类库,能够帮助我们轻松进行网络请求和数据交互。无论是简单的GET请求还是复杂的多线程请求,OKHttp3都能够胜任,并且具备良好的性能和稳定性。 ### 回答2: OkHttp3 是一个开源的基于 Java 的 HTTP 客户端工具类库,它提供了简洁的 API 接口和高效的网络通信能力。 首先,OkHttp3 内部实现了连接池和请求复用功能,可以重用之前的连接和请求,从而减少网络连接和请求的开销,提高网络性能。它还支持 HTTP/2 和 SPDY,能够多路复用多个请求,提升并发访问效率。 OkHttp3 的 API 设计简洁易用,提供了丰富的功能,如异步请求、同步请求、文件上传、文件下载、Cookie 管理等。我们可以通过建立 OkHttpClient 对象,设置一些配置信息,如超时时间、拦截器等,然后通过创建 Request 对象来发起请求,并通过回调方式获取服务器返回的响应。 使用 OkHttp3,我们可以轻松地处理各种类型的网络请求,包括 GET、POST、PUT、DELETE 等,并可以通过设置 Header、Body 参数来自定义请求内容。OkHttp3 还提供了缓存机制,我们可以通过配置缓存策略,减少对服务器的频繁请求,同时也可以配置自定义的拦截器,对请求和响应进行处理和修改。 另外,OkHttp3 还支持自动设置代理、支持网络请求的重试和重定向,以及支持自定义的证书校验等安全性功能。 总之,OkHttp3 是一个功能强大、易用且高效的网络请求工具类库,广泛应用于 AndroidJava 开发中。它提供了丰富的功能和高性能的网络通信能力,帮助我们方便快捷地发起各种类型的网络请求,并处理返回的响应数据。 ### 回答3: okhttp3 是一个流行的开源网络请求框架,主要用于在 Android 平台上发送和接收 HTTP 请求。 okhttp3 工具类可以帮助我们更方便地使用和管理 okhttp3,提供了一系列简化了的方法和功能,使我们能够更高效地进行网络请求的处理。 首先,okhttp3 工具类可以帮助我们创建 OkHttpClient 实例,这是 okhttp3 中的核心对象,用于发送和接收请求。我们可以通过设置不同的配置参数,如超时时间、连接数限制等,来满足不同的需求。 接下来,okhttp3 工具类提供了简化了的方法,如 GET、POST 等,用于发送不同类型的请求。我们只需提供请求的地址和参数,工具类就会自动构建请求对象,并将响应结果以回调方式返回。 此外,okhttp3 工具类还支持多线程并发请求的功能。我们可以通过设置线程池来同时发送多个请求,从而提高并发处理能力。 okhttp3 工具类还提供了拦截器的功能,可用于在发送和接收请求的过程中进行一些自定义的操作,如参数加密、日志记录等。我们可以通过自定义拦截器来实现这些功能,并将其添加到 OkHttpClient 实例中。 总的来说,okhttp3 工具类提供了一系列简化了的方法和功能,使我们能够更方便地使用 okhttp3 进行网络请求。它大大简化了我们的开发流程,减少了代码量,并且具有高效和可靠的性能。无论是在 Android 开发中,还是在其他需要进行网络请求的场景中,okhttp3 工具类都是一个值得推荐的选择。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值