首页 > 代码库 > OkHttp3使用详解

OkHttp3使用详解

引言

最初我们进行HTTP请求时使用的是HttpURLConnection或者HttpClient,那么这两者都有什么优缺点呢?

HttpClientApache基金会的一个开源网络库,功能十分强大,API数量众多,但正是由于庞大的API数量使得我们很难在不破坏兼容性的情况下对它进行升级和扩展,所以Android团队在提升和优化HttpClient方面的工作态度并不积极。官方在Android 2.3以后就不建议用了,并且在Android 5.0以后废弃了HttpClient,在Android 6.0更是删除了HttpClient

HttpURLConnection是一种多用途、轻量极的HTTP客户端,提供的API比较简单,可以容易地去使用和扩展。不过在Android 2.2版本之前,HttpURLConnection一直存在着一些令人厌烦的bug。比如说对一个可读的InputStream调用close()方法时,就有可能会导致连接池失效了。那么我们通常的解决办法就是直接禁用掉连接池的功能:

private void disableConnectionReuseIfNecessary() {
    // 这是一个2.2版本之前的bug    
    if (Integer.parseInt(Build.VERSION.SDK) < Build.VERSION_CODES.FROYO) {
        System.setProperty("http.keepAlive", "false");
    }
}

因此一般推荐是在2.2之前使用HttpClient,因为其bug较少。在2.2之后推荐使用HttpURLConnection,因为API简单、体积小、有压缩和缓存机制,并且Android团队后续会继续优化HttpURLConnection

但是上面两个类库和OkHttp比起来就显得有些不足了,因为OkHttp不仅具有高效的请求效率,并且提供了很多开箱即用的网络疑难杂症解决方案。

简介

Android 4.4开始google已经开始将源码中的HttpURLConnection替换为OkHttp,而在Android 6.0之后的SDKgoogle更是移除了对于HttpClient的支持,而现在流行的Retrofit同样是使用OkHttp进行再次封装而来的。

OkHttp是一个快速、高效的网络请求库,它的设计和实现的首要目标便是高效,有如下特性:

  1. 支持http2,使得对同一个主机发出的所有请求都可以共享相同的socket套接字连接;
  2. 使用连接池来复用连接以减少延迟、提高效率;
  3. 支持Gzip压缩响应体,降低传输内容的大小;
  4. 支持Http缓存,避免重复请求;
  5. 请求失败时会自动重试主机中的其他IP地址自动重定向;
  6. 使用Okio来简化数据的访问与存储,提高性能;

使用范围

  1. 支持Android 2.3及其以上版本;
  2. 支持Java JDK 1.7以上版本;

依赖

dependencies {
    compile ‘com.squareup.okhttp3:okhttp:3.6.0‘
    compile ‘com.squareup.okio:okio:1.11.0‘
    compile ‘com.github.franmontiel:PersistentCookieJar:v1.0.1‘
}

注:如果配置PersistentCookieJar依赖则同时也要在Project的Build.gradle中添加Maven库:

allprojects {
    repositories {
        jcenter()
        maven { url "https://jitpack.io" }
    }
}

权限

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

混淆

#okhttp
-dontwarn okhttp3.**
-keep class okhttp3.**{*;}

#okio
-dontwarn okio.**
-keep class okio.**{*;}

使用

1.Application中初始化OkHttp

/**
 * @Description 初始化OkHttp
 */
private void initOkHttp() {
    File cache = getExternalCacheDir();
    int cacheSize = 10 * 1024 * 1024;

    ClearableCookieJar cookieJar = new PersistentCookieJar(new SetCookieCache(), new SharedPrefsCookiePersistor(mContext));
    Https.SSLParams sslParams = Https.getSslSocketFactory(null, null, null);

    OkHttpClient okHttpClient = new OkHttpClient.Builder()
            .connectTimeout(15, TimeUnit.SECONDS)//连接超时(单位:秒)
            .writeTimeout(20, TimeUnit.SECONDS)//写入超时(单位:秒)
            .readTimeout(20, TimeUnit.SECONDS)//读取超时(单位:秒)
            .pingInterval(20, TimeUnit.SECONDS) //websocket轮训间隔(单位:秒)
            .cache(new Cache(cache.getAbsoluteFile(), cacheSize))//设置缓存
            .cookieJar(cookieJar)//Cookies持久化
            .hostnameVerifier(new HostnameVerifier() {
                @Override
                public boolean verify(String hostname, SSLSession session) {
                    return true;
                }
            })
            .sslSocketFactory(sslParams.sSLSocketFactory, sslParams.trustManager)//https配置
            .build();

    OkHttpUtils.initClient(okHttpClient);
}

2.同步数据请求

/**
 * @param url      请求地址
 * @param callback 请求回调
 * @Description GET请求
 */
public static void getSync(String url, HttpCallback callback) {
    Request request = OkHttpRequest.builderRequest(HttpMethodType.GET, url, null, null);
    OkHttpRequest.doExecute(request, callback);
}

/**
 * @param url      请求地址
 * @param params   请求参数
 * @param callback 请求回调
 * @Description GET请求
 */
public static void getSync(String url, Map<String, String> params, HttpCallback callback) {
    if (params != null && !params.isEmpty()) {
        url = OkHttpRequest.appendGetParams(url, params);
    }
    Request request = OkHttpRequest.builderRequest(HttpMethodType.GET, url, null, null);
    OkHttpRequest.doExecute(request, callback);
}

/**
 * @param url      请求地址
 * @param params   请求参数
 * @param callback 请求回调
 * @Description POST请求
 */
public static void postSync(String url, Map<String, String> params, HttpCallback callback) {
    Request request = OkHttpRequest.builderRequest(HttpMethodType.POST, url, params, null);
    OkHttpRequest.doExecute(request, callback);
}

3.异步数据请求

使用enqueue方法,将call放入请求队列,然后OkHttp会在线程池中进行网络访问;只需要在适当的时候(需要操作UI的时候)发送一个消息给主线程的Handler(取决于Looper,使用 Looper.getMainLooper()创建的Handler就是主线程Handler)就可以了。

/**
 * @param url      请求地址
 * @param callback 请求回调
 * @Description GET请求
 */
public static void getAsyn(String url, HttpCallback callback) {
    Request request = OkHttpRequest.builderRequest(HttpMethodType.GET, url, null, null);
    OkHttpRequest.doEnqueue(request, callback);
}

/**
 * @param url      请求地址
 * @param params   请求参数
 * @param callback 请求回调
 * @Description GET请求
 */
public static void getAsyn(String url, Map<String, String> params, HttpCallback callback) {
    if (params != null && !params.isEmpty()) {
        url = OkHttpRequest.appendGetParams(url, params);
    }
    Request request = OkHttpRequest.builderRequest(HttpMethodType.GET, url, null, null);
    OkHttpRequest.doEnqueue(request, callback);
}

/**
 * @param url      请求地址
 * @param params   请求参数
 * @param callback 请求回调
 * @Description POST请求
 */
public static void postAsyn(String url, Map<String, String> params, HttpCallback callback) {
    Request request = OkHttpRequest.builderRequest(HttpMethodType.POST, url, params, null);
    OkHttpRequest.doEnqueue(request, callback);
}

/**
 * @param url      请求地址
 * @param json     json数据格式
 * @param callback 请求回调
 * @Description POST提交JSON数据
 */
public static void postAync(String url, String json, HttpCallback callback) {
    Request request = OkHttpRequest.builderRequest(HttpMethodType.POST, url, null, json);
    OkHttpRequest.doEnqueue(request, callback);
}

4.文件上传

/**
 * @param url      请求地址
 * @param file     上传文件
 * @param callback 请求回调
 * @Description 单文件上传
 */
public static void postAsynFile(String url, File file, HttpCallback callback) {
    if (!file.exists()) {
        ToastUtil.showText(UIUtils.getString(R.string.file_does_not_exist));
        return;
    }
    Request request = OkHttpRequest.builderFileRequest(url, file, null, null, null, callback);
    OkHttpRequest.doEnqueue(request, callback);
}

/**
 * @param url      请求地址
 * @param pic_key  上传图片关键字(约定pic_key如“upload”作为后台接受多张图片的key)
 * @param files    上传文件集合
 * @param params   请求参数
 * @param callback 请求回调
 * @Description 多文件上传
 */
public static void postAsynFiles(String url, String pic_key, List<File> files, Map<String, String> params, HttpCallback callback) {
    Request request = OkHttpRequest.builderFileRequest(url, null, pic_key, files, params, callback);
    OkHttpRequest.doEnqueue(request, callback);
}

5.文件下载

/**
 * @param url          请求地址
 * @param destFileDir  目标文件存储的文件夹路径,如:Environment.getExternalStorageDirectory().getAbsolutePath()
 * @param destFileName 目标文件存储的文件名,如:gson-2.7.jar
 * @param callback     请求回调
 * @Description 文件下载
 */
public void downloadAsynFile(String url, String destFileDir, String destFileName, HttpCallback callback) {
    Request request = OkHttpRequest.builderRequest(HttpMethodType.GET, url, null, null);
    OkHttpRequest.doDownloadEnqueue(request, destFileDir, destFileName, callback);
}

/**
 * @param url          请求地址
 * @param destFileDir  目标文件存储的文件夹路径
 * @param destFileName 目标文件存储的文件名
 * @param params       请求参数
 * @param callback     请求回调
 * @Description 文件下载
 */
public void downloadAsynFile(String url, String destFileDir, String destFileName, Map<String, String> params, HttpCallback callback) {
    Request request = OkHttpRequest.builderRequest(HttpMethodType.POST, url, params, null);
    OkHttpRequest.doDownloadEnqueue(request, destFileDir, destFileName, callback);
}

6.图片显示

/**
 * @param url      请求地址
 * @param callback 请求回调
 * @Description 图片显示
 */
public static void displayAsynImage(String url, HttpCallback callback) {
    Request request = OkHttpRequest.builderRequest(HttpMethodType.GET, url, null, null);
    OkHttpRequest.doDisplayEnqueue(request, callback);
}

7.流式提交

/**
 * @param url      请求地址
 * @param content  提交内容
 * @param callback 请求回调
 * @Description 使用流的方式提交POST请求
 */
public static void postAsynStream(String url, String content, HttpCallback callback) {
    Request request = OkHttpRequest.builderStreamRequest(url, content);
    OkHttpRequest.doEnqueue(request, callback);
}

8.Websocket

/**
 * @param url 请求地址
 * @Description WebSocket协议首先会发起http请求,握手成功后,转换协议保持长连接,类似心跳
 */
public static void websocket(String url) {
    Request request = OkHttpRequest.builderRequest(HttpMethodType.GET, url, null, null);
    OkHttpRequest.doNewWebSocket(request);
}

9.HTTP头部的设置和读取

HTTP头部的数据结构是Map<String, List<String>>类型,也就是说对于每个HTTP头可能有多个值。但是大部分HTTP头都只有一个值,只有少部分HTTP头允许多个值。OkHttp的处理方式是:

  1. 使用header(name,value)来设置HTTP头的唯一值(如果name已经存在,将会移除该name对应的value,然后将新value添加进来,即替换掉原来的value值);
  2. 使用addHeader(name,value)来补充新值(即使当前已经存在值了,也只会添加新的value值,并不会移除或替换原来的值);
  3. 使用header(name)读取唯一值或多个值的最后一个值;
  4. 使用headers(name)获取所有值;
/**
 * @param builder Request.Builder
 * @param name    名称
 * @param value   值
 * @Description 添加单个头部信息
 */
public static Request.Builder appendHeader(Request.Builder builder, String name, String value) {
    builder.header(name, value);
    return builder;
}

/**
 * @param builder Request.Builder
 * @param headers 头部参数
 * @Description 添加多个头部信息
 */
public static Request.Builder appendHeaders(Request.Builder builder, Map<String, String> headers) {
    Headers.Builder headerBuilder = new Headers.Builder();
    if (headers == null || headers.isEmpty()) {
        return builder;
    }
    for (String key : headers.keySet()) {
        headerBuilder.add(key, headers.get(key));
    }
    builder.headers(headerBuilder.build());
    return builder;
}

10.缓存控制

强制不缓存

Request request = new Request.Builder()
        .cacheControl(new CacheControl.Builder()
                .noCache()
                .build())
        .url(url)
        .build();

强制缓存

Request request = new Request.Builder()
        .cacheControl(new CacheControl.Builder()
                .onlyIfCached()
                .build())
        .url(url)
        .build();
Response response = mOkHttpClient.newCall(request).execute();
if (response.code() != 504) {
    // The resource was cached! Show it.
} else {
    // The resource was not cached.
}

缓存策略由服务器指定

Request request = new Request.Builder()
        .cacheControl(new CacheControl.Builder()
                .maxAge(0, TimeUnit.SECONDS)
                .build())
        .url(url)
        .build();

允许使用旧的缓存

Request request = new Request.Builder()
        .cacheControl(new CacheControl.Builder()
                .maxStale(365, TimeUnit.DAYS)
                .build())
        .url(url)
        .build();

11.Cookies缓存

OkHttpClient mOkHttpClient = new OkHttpClient.Builder().cookieJar(new CookieJar() {

    private final HashMap<String, List<Cookie>> cookieStore = new HashMap<>();

    @Override
    public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {
        //注:key是String类型且为url的host部分
        cookieStore.put(url.host(), cookies);
    }

    @Override
    public List<Cookie> loadForRequest(HttpUrl url) {
        List<Cookie> cookies = cookieStore.get(url.host());
        return cookies != null ? cookies : new ArrayList<Cookie>();
    }
}).build();

12.关闭请求

/**
 * @param tag 请求标签
 * @Description 取消请求
 */
public static void cancelTag(Object tag) {
    if (tag == null) {
        return;
    }
    synchronized (mOkHttpClient.dispatcher().getClass()) {
        for (Call call : mOkHttpClient.dispatcher().queuedCalls()) {
            if (tag.equals(call.request().tag())) {
                call.cancel();
            }
        }
        for (Call call : mOkHttpClient.dispatcher().runningCalls()) {
            if (tag.equals(call.request().tag())) {
                call.cancel();
            }
        }
    }
}

注意事项

  1. Android 4.0之后要求网络请求必须在工作线程中运行,不允许在主线程中运行。因此如果使用OkHttp3的同步方法,需要新起工作线程进行调用。
  2. 一般情况下我们希望获得Response返回的字符串,可以通过response.body().string()获取;如果希望获得返回的二进制字节数组,则调用response.body().bytes();如果想获取到返回的InputStream,则调用response.body().byteStream()
  3. 异步请求enqueue的回调是子线程,非主线程,所以是不能直接操作UI界面的。
  4. 响应体的string()方法适用于获取小数据信息,如果返回的数据太大(超过1MB),建议使用stream()获取返回的数据,因为string()方法会将整个文档加载到内存中。

项目地址 ? 传送门

<script type="text/javascript"> $(function () { $(‘pre.prettyprint code‘).each(function () { var lines = $(this).text().split(‘\n‘).length; var $numbering = $(‘
    ‘).addClass(‘pre-numbering‘).hide(); $(this).addClass(‘has-numbering‘).parent().append($numbering); for (i = 1; i <= lines; i++) { $numbering.append($(‘
  • ‘).text(i)); }; $numbering.fadeIn(1700); }); }); </script>

    OkHttp3使用详解