您的位置:首页 > 理论基础 > 计算机网络

OkHttp3使用详解

2017-04-20 16:11 417 查看

引言

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

HttpClient
Apache
基金会的一个开源网络库,功能十分强大,
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
之后的
SDK
google
更是移除了对于
HttpClient
的支持,而现在流行的
Retrofit
同样是使用
OkHttp
进行再次封装而来的。

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

支持http2,使得对同一个主机发出的所有请求都可以共享相同的socket套接字连接;

使用连接池来复用连接以减少延迟、提高效率;

支持Gzip压缩响应体,降低传输内容的大小;

支持Http缓存,避免重复请求;

请求失败时会自动重试主机中的其他IP地址自动重定向;

使用Okio来简化数据的访问与存储,提高性能;

使用范围

支持
Android 2.3
及其以上版本;

支持
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
的处理方式是:

使用
header(name,value)
来设置
HTTP
头的唯一值(如果
name
已经存在,将会移除该
name
对应的
value
,然后将新
value
添加进来,即替换掉原来的
value
值);

使用
addHeader(name,value)
来补充新值(即使当前已经存在值了,也只会添加新的
value
值,并不会移除或替换原来的值);

使用
header(name)
读取唯一值或多个值的最后一个值;

使用
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();
}
}
}
}


注意事项

Android 4.0
之后要求网络请求必须在工作线程中运行,不允许在主线程中运行。因此如果使用
OkHttp3
的同步方法,需要新起工作线程进行调用。

一般情况下我们希望获得
Response
返回的字符串,可以通过
response.body().string()
获取;如果希望获得返回的二进制字节数组,则调用
response.body().bytes()
;如果想获取到返回的
InputStream
,则调用
response.body().byteStream()


异步请求
enqueue
的回调是子线程,非主线程,所以是不能直接操作
UI
界面的。

响应体的
string()
方法适用于获取小数据信息,如果返回的数据太大(超过
1MB
),建议使用
stream()
获取返回的数据,因为
string()
方法会将整个文档加载到内存中。

项目地址 ☞ 传送门
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: