OkHttp
支持Android 2.3
及其以上版本。
对于Java
,JDK1.7
以上。
dependencies {
compile 'com.squareup.okhttp3:okhttp:3.7.0'
}
具体的版本号,请查看OKHttp在GitHub上的网址
OkHttpClient
有一个无参数的构造方法,使用该构造方法new
出来的实例使用默认的参数。
OkHttpClient okHttpClient = new OkHttpClient();
我们通常不使用默认参数的那个无参数的构造方法。而使用OkHttpClient.Builder
这个构造器。
示例:
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)//设置连接超时时间
.readTimeout(10, TimeUnit.SECONDS) //设置读取超时时间
.writeTimeout(10, TimeUnit.SECONDS) //设置写入超时时间
.retryOnConnectionFailure(true) //设置失败后重试
...
.build();
我们通常要设置3种超时时间、失败后的重试、打印日志、Cookie
的处理等。
表示一个请求。可以获取到请求方法、请求的URL
、请求头、请求体等信息。
Request
的构造器。可以设置请求方法、请求的URL
、请求头、请求体等信息。
示例:
Request request = new Request.Builder()
.url("http://www.fpliu.com")
.get()
.tag(TAG)
.addHeader("Accept", "*/*")
.addHeader("Connection", "Keep-Alive")
.addHeader("User-Agent", "Android")
.addHeader("Referer", "http://www.fpliu.com")
.addHeader("Authorization", "xxxxx")
.build();
Call
代表一次请求过程。
Call call = okHttpClient.newCall(request);
异步请求:
call.enqueue(callback);
异步请求回掉在工作线程中执行。如果需要更新UI的话,可以通过Handler
或者是使用现在最流行的RxJava
。
同步请求:
Response response = call.execute();
如果在Android
中使用同步请求,请确保这句代码在工作线程中,否则会报错。
表示一个响应。可以获取到状态行、响应头、响应体等信息。
Interceptor
接口用于拦截请求和响应。
Interceptor
接口的定义如下:
package okhttp3;
public interface Interceptor {
Response intercept(Chain chain) throws IOException;
interface Chain {
Request request();
Response proceed(Request request) throws IOException;
Connection connection();
}
}
通常,我们拦截了请求之后,可以添加一些头或者获取信息。比如打印日志。示例:
OkHttpClient okHttpClient = new OkHttpClient.Builder()
...
.addInterceptor(new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Log.i(TAG, request.method() + " " + request.url().url());
Log.i(TAG, request.headers().toString());
Response response = chain.proceed(request);
Log.i(TAG, response.headers().toString());
return response;
}
})
.build();
我们也可以使用Square的一个日志实现库logging-interceptor
,添加如下依赖:
compile 'com.squareup.okhttp3:logging-interceptor:3.4.1'
实现如下:
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
Authenticator
接口用于Authorization
认证。
Authenticator
接口的定义如下:
package okhttp3;
public interface Authenticator {
Authenticator NONE = new Authenticator() {
@Override public Request authenticate(Route route, Response response) {
return null;
}
};
Request authenticate(Route route, Response response) throws IOException;
}
当服务需要Authorization
认证的时候,你的请求头里没有设置Authorization
请求头, 那么就是以401
状态码响应,当得到401
状态码当响应,OKHttp
就会调用此接口, 并尝试重新发送一次请求。
示例:
OkHttpClient okHttpClient = new OkHttpClient.Builder()
...
.authenticator(new Authenticator() {
@Override
public Request authenticate(Route route, Response response) throws IOException {
String username = "";
String password = "";
String credential = Credentials.basic(username, password);
return response.request().newBuilder().header("Authorization", credential).build();
}
})
.build();
通常,在移动端开发中,Authorization
认证不会这么简单,我们一般是在登陆的时候从服务器上下发一个令牌, 客户端以后每隔几分钟拿着上次发送的令牌问服务器要新的令牌,这样的安全性更高。Authorization
认证中的basic
认证方式是将用户名和密码用base64
编码的, 虽然不是明文了,但是很容易解码还原出来,安全性不高!
当你有代理服务器的时候,代理服务器需要Authorization
认证的时候,你的请求头里没有设置Proxy-Authorization
请求头, 那么就是以407
状态码响应,当得到407
状态码当响应,OKHttp
就会调用此接口, 并尝试重新发送一次请求。
示例:
OkHttpClient okHttpClient = new OkHttpClient.Builder()
...
.authenticator(new Authenticator() {
@Override
public Request authenticate(Route route, Response response) throws IOException {
String username = "";
String password = "";
String credential = Credentials.basic(username, password);
return response.request().newBuilder().header("Proxy-Authorization", credential).build();
}
})
.build();
CookieJar
接口用于从响应中提取Cookie
并保存、读取保存的Cookie
并设置到下次请求的请求头里。
CookieJar
接口的定义如下:
package okhttp3;
import java.util.Collections;
import java.util.List;
public interface CookieJar {
CookieJar NO_COOKIES = new CookieJar() {
@Override public void saveFromResponse(HttpUrl url, List cookies) {
}
@Override public List loadForRequest(HttpUrl url) {
return Collections.emptyList();
}
};
void saveFromResponse(HttpUrl url, List cookies);
List loadForRequest(HttpUrl url);
}
假设,下面是某次响应中的Set-Cookie
头的设置:
Set-Cookie: BAIDUID=C2933D4BD21F629E1B5D0050BC924E4C:FG=1; max-age=31536000; expires=Sat, 12-May-18 01:56:15 GMT; domain=.baidu.com; path=/; version=1
Set-Cookie: H_WISE_SIDS=102090_114745; path=/; domain=.baidu.com
Set-Cookie: BDSVRTM=8; path=/
以后,每次请求都会在请求头添加Cookie
头,其值是key=value
的形式,可以有多对,每对之间用;
隔开。示例:
Cookie: BAIDUID=C2933D4BD21F629E1B5D0050BC924E4C:FG=1;H_WISE_SIDS=102090_114745;BDSVRTM=8
CookieJar
实现示例:
OkHttpClient okHttpClient = new OkHttpClient.Builder()
...
.cookieJar(new CookieJar() {
private final HashMap> cookieStore = new HashMap<>();
@Override
public void saveFromResponse(HttpUrl httpUrl, List list) {
cookieStore.put(httpUrl, list);
}
@Override
public List loadForRequest(HttpUrl httpUrl) {
List cookies = cookieStore.get(httpUrl);
return cookies != null ? cookies : new ArrayList();
}
})
.build();
这个实现是保存在内存中的,一旦杀死APP
,再启动APP
,保存的Cookie
就没有了,所以通常保存在文件中, 在Android
中就是保存在SharedPreference
中。
CacheControl
类用于设置Cache-Control
请求头,此请求头用于设置一次请求的缓存策略。
指令 | 说明 |
---|---|
Public | 指示响应可被任何缓存区缓存 |
Private | 指示对于单个用户的整个或部分响应消息,不能被共享缓存处理。 这允许服务器仅仅描述当用户的部分响应消息,此响应消息对于其他用户的请求无效。 |
no-cache | 指示请求或响应消息不能缓存 |
no-store | 用于防止重要的信息被无意的发布。在请求消息中发送将使得请求和响应消息都不使用缓存。 |
max-age | 指示客户机可以接收生存期不大于指定时间(以秒为单位)的响应。 |
min-fresh | 指示客户机可以接收响应时间小于当前时间加上指定时间的响应。 |
max-stale | 指示客户机可以接收超出超时期间的响应消息。如果指定max-stale消息的值,那么客户机可以接收超出超时期指定值之内的响应消息。 |
CacheControl
对象的构造:
CacheControl.Builder builder = new CacheControl.Builder();
builder.noCache();//不使用缓存,全部走网络
builder.noStore();//不使用缓存,也不存储缓存
builder.onlyIfCached();//只使用缓存
builder.noTransform();//禁止转码
builder.maxAge(10, TimeUnit.MILLISECONDS);//指示客户机可以接收生存期不大于指定时间的响应。
builder.maxStale(10, TimeUnit.SECONDS);//指示客户机可以接收超出超时期间的响应消息
builder.minFresh(10, TimeUnit.SECONDS);//指示客户机可以接收响应时间小于当前时间加上指定时间的响应。
CacheControl cacheControl = builder.build();
我们也可以直接使用下面两个常量:
CacheControl.FORCE_CACHE; //仅仅使用缓存
CacheControl.FORCE_NETWORK; //仅仅使用网络
设置:
Request request = new Request.Builder().cacheControl(cacheControl).url(url).build();
需要注意的是,要使用缓存,就必须设置一个全局的缓存目录和缓存大小,如下:
OkHttpClient okHttpClient = new OkHttpClient.Builder().cache(new Cache(context.getCacheDir(), 10 * 1024 * 1024)).build();
示例:
/**
* 异步GET请求
*
* @param url 请求资源的路径
* @param authorization 验证用户,不需要验证的,传入空,即可
* @param params URL的参数
* @param callback 请求的回调
*/
public static void asyncGet(String url, String authorization, Callback callback, String... params) {
Request.Builder requestBuilder = new Request.Builder().url(makeUrl(url, params)).get().tag(TAG);
requestBuilder.addHeader("Accept", "*/*");
requestBuilder.addHeader("Connection", "Keep-Alive");
requestBuilder.addHeader("User-Agent", "Android");
requestBuilder.addHeader("Referer", "http://www.fpliu.com");
if (!TextUtils.isEmpty(authorization)) {
requestBuilder.addHeader("Authorization", authorization);
}
Request request = requestBuilder.build();
Call call = okHttpClient.newCall(request);
call.enqueue(callback);
}
POST
用于提交数据,POST
一般必然包含请求体,而请求体可以有多种形式:FORM
、JSON
、XML
等格式化形式的字符串,或者其他自定义格式的数据形式。
示例一:
/**
* 异步POST请求,请求体是表单
*
* @param url 请求资源的路径
* @param authorization 验证用户,不需要验证的,传入空,即可
* @param params 请求参数
* @param callback 请求的回调
*/
public static void asyncPostForm(String url, String authorization, List<KV> params, Callback callback) {
FormBody.Builder formBodyBuilder = new FormBody.Builder();
if (params != null) {
for (KV kv : params) {
if (kv == null) {
continue;
}
String key = kv.getKey();
String value = kv.getValue();
if (TextUtils.isEmpty(key)) {
continue;
}
formBodyBuilder.add(key, value);
}
}
Request.Builder requestBuilder = new Request.Builder().url(makeUrl(url, params)).post(formBodyBuilder.build()).tag(TAG);
requestBuilder.addHeader("Accept", "*/*");
requestBuilder.addHeader("Connection", "Keep-Alive");
requestBuilder.addHeader("User-Agent", "Android");
requestBuilder.addHeader("Referer", "http://www.fpliu.com");
if (!TextUtils.isEmpty(authorization)) {
requestBuilder.addHeader("Authorization", authorization);
}
Request request = requestBuilder.build();
Call call = okHttpClient.newCall(request);
call.enqueue(callback);
}
示例二:
/**
* 异步POST请求,请求体是表单
*
* @param url 请求资源的路径
* @param authorization 验证用户,不需要验证的,传入空,即可
* @param params 请求参数
* @param callback 请求的回调
*/
public static void asyncPostForm(String url, String authorization, Callback callback, String... params) {
FormBody.Builder formBodyBuilder = new FormBody.Builder();
if (params != null) {
int length = params.length / 2;
for (int i = 0; i < length; i++) {
String key = params[i];
String value = params[i + 1];
if (TextUtils.isEmpty(key)) {
continue;
}
formBodyBuilder.add(key, value);
}
}
Request.Builder requestBuilder = new Request.Builder().url(makeUrl(url, params)).post(formBodyBuilder.build()).tag(TAG);
requestBuilder.addHeader("Accept", "*/*");
requestBuilder.addHeader("Connection", "Keep-Alive");
requestBuilder.addHeader("User-Agent", "Android");
requestBuilder.addHeader("Referer", "http://www.fpliu.com");
if (!TextUtils.isEmpty(authorization)) {
requestBuilder.addHeader("Authorization", authorization);
}
Request request = requestBuilder.build();
Call call = okHttpClient.newCall(request);
call.enqueue(callback);
}
示例:
/**
* 异步POST请求,请求体是JSON
*
* @param url 请求资源的路径
* @param authorization 验证用户,不需要验证的,传入空,即可
* @param json JSON字符串,使用String类型可以支持GSON、fastJson、json-lib等库的转化,而不局限于一种
* @param callback 请求的回调
*/
public static void asyncPostJson(String url, String authorization, String json, Callback callback) {
RequestBody requestBody = RequestBody.create(MediaType.parse("application/json;charset=UTF-8"), json);
Request.Builder requestBuilder = new Request.Builder().url(url).post(requestBody).tag(TAG);
requestBuilder.addHeader("Accept", "*/*");
requestBuilder.addHeader("Connection", "Keep-Alive");
requestBuilder.addHeader("User-Agent", "Android");
requestBuilder.addHeader("Referer", "http://www.fpliu.com");
if (!TextUtils.isEmpty(authorization)) {
requestBuilder.addHeader("Authorization", authorization);
}
Request request = requestBuilder.build();
Call call = okHttpClient.newCall(request);
call.enqueue(callback);
}
实际上,XML
提交只需要将JSON
提交的头中的Content-Type
修改为application/xml
即可。
示例:
/**
* 异步POST请求,请求体是XML
*
* @param url 请求资源的路径
* @param authorization 验证用户,不需要验证的,传入空,即可
* @param xml XML字符串
* @param callback 请求的回调
*/
public static void asyncPostXml(String url, String authorization, String xml, Callback callback) {
RequestBody requestBody = RequestBody.create(MediaType.parse("application/xml;charset=UTF-8"), xml);
Request.Builder requestBuilder = new Request.Builder().url(url).post(requestBody).tag(TAG);
requestBuilder.addHeader("Accept", "*/*");
requestBuilder.addHeader("Connection", "Keep-Alive");
requestBuilder.addHeader("User-Agent", "Android");
requestBuilder.addHeader("Referer", "http://www.fpliu.com");
if (!TextUtils.isEmpty(authorization)) {
requestBuilder.addHeader("Authorization", authorization);
}
Request request = requestBuilder.build();
Call call = okHttpClient.newCall(request);
call.enqueue(callback);
}
示例:
/**
* 异步POST请求,请求体是文件(二进制数据流)
*
* @param url 请求资源的路径
* @param authorization 验证用户,不需要验证的,传入空,即可
* @param filePath JSON字符串,使用String类型可以支持GSON、fastJson、json-lib等库的转化,而不局限于一种
* @param callback 请求的回调
*/
public static void asyncPostFile(String url, String authorization, String filePath, Callback callback) {
if (TextUtils.isEmpty(filePath) && requestCallBack != null) {
requestCallBack.onFailure(null, new FileNotFoundException("文件路径不能为空"));
return;
}
File file = new File(filePath);
RequestBody requestBody = RequestBody.create(MediaType.parse("application/octet-stream"), file);
Request.Builder requestBuilder = new Request.Builder().url(url).post(requestBody).tag(TAG);
requestBuilder.addHeader("Accept", "*/*");
requestBuilder.addHeader("Connection", "Keep-Alive");
requestBuilder.addHeader("User-Agent", "Android");
requestBuilder.addHeader("Referer", "http://www.fpliu.com");
if (!TextUtils.isEmpty(authorization)) {
requestBuilder.addHeader("Authorization", authorization);
}
Request request = requestBuilder.build();
Call call = okHttpClient.newCall(request);
call.enqueue(callback);
}
这里要特别注意,如果想要读取SD
卡里的文件,需要申请android.permission.READ_EXTERNAL_STORAGE
这个权限, 如果您的targetSdkVersion
被设置成了大于等于23
,您还必须要在代码中动态申请这个权限。为了方便, 可以使用RxPermissions这个框架,示例:
RxPermissions rxPermissions = new RxPermissions(this);
rxPermissions.requestEach(Manifest.permission.READ_EXTERNAL_STORAGE).subscribe(new Observer() {
@Override
public void onSubscribe(@NonNull Disposable d) {
Log.d(TAG, "onSubscribe()");
}
@Override
public void onNext(@NonNull Permission permission) {
Log.d(TAG, "onNext()" + permission);
if (permission.granted) {
//客户授权了
OKHttpRequest.init(MainActivity.this);
OKHttpRequest.asyncPostFile("http://192.168.1.103/upload", "", "/sdcard/xx.txt", new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.e(TAG, "onFailure()", e);
}
@Override
public void onResponse(Call call, Response response) throws IOException {
Log.d(TAG, response.body().string());
}
});
} else if (permission.shouldShowRequestPermissionRationale){
//客户没有授权,并且选择了不再提示,需要提示用户
Toast.makeText(MainActivity.this, "您曾经选择过不授权,您想再授权,请到设置里进行授权", Toast.LENGTH_SHORT).show();
}
}
@Override
public void onError(@NonNull Throwable e) {
Log.e(TAG, "onError()", e);
}
@Override
public void onComplete() {
Log.d(TAG, "onComplete()");
}
});
示例:
/**
* 异步POST请求,请求体是Multipart
*
* @param url 请求资源的路径
* @param authorization 验证用户,不需要验证的,传入空,即可
* @param parts 每一部分的列表
* @param callback 请求的回调
*/
public static void asyncPostMultipart(String url, String authorization, List<MultipartBody.Part> parts, Callback callback) {
MultipartBody.Builder multipartBodyBuilder = new MultipartBody.Builder().setType(MultipartBody.FORM);
if (parts != null) {
for (MultipartBody.Part part : parts) {
multipartBodyBuilder.addPart(part);
}
}
Request.Builder requestBuilder = new Request.Builder().url(url).post(multipartBodyBuilder.build()).tag(TAG);
requestBuilder.addHeader("Accept", "*/*");
requestBuilder.addHeader("Connection", "Keep-Alive");
requestBuilder.addHeader("User-Agent", "Android");
requestBuilder.addHeader("Referer", "http://www.fpliu.com");
if (!TextUtils.isEmpty(authorization)) {
requestBuilder.addHeader("Authorization", authorization);
}
Request request = requestBuilder.build();
Call call = okHttpClient.newCall(request);
call.enqueue(callback);
}
PUT
请求与POST
请求在协议本身,除了请求方法本身的不同,其他方面没有任何差别。 从Restful
风格上讲:只是语义上不同,POST
用于创建资源,而PUT
用于更新资源。
所以将
.post(requestBody)
修改为:
.put(requestBody)
即可。
Delete
请求用于删除资源,不应该携带大量信息,所以,一般Delete
请求没有实体。
示例:
/**
* 异步下载
*
* @param url 资源路径
* @param desFilePath 本地路径
* @param needContinue 是否断点续传
* @param progressCallback 带有进度的回调
*/
public static void asyncDownload(String url, String authorization, String desFilePath, boolean needContinue, final ProgressCallback progressCallback) {
if (TextUtils.isEmpty(desFilePath)) {
FileNotFoundException exception = new FileNotFoundException("文件路径不能为空");
if (progressCallback == null) {
Log.e(TAG, "asyncDownload()", exception);
} else {
progressCallback.onFailure(null, exception);
}
return;
}
Request.Builder requestBuilder = getRequestBuilder(authorization).url(url).get().tag(TAG);
final File file = new File(desFilePath);
if (file.exists()) {
if (needContinue) {
final long startByte = file.length();
//断点续传要用到的,指示下载的区间
requestBuilder.header("RANGE", "bytes=" + startByte + "-");
Call call = okHttpClient.newCall(requestBuilder.build());
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
if (progressCallback == null) {
Log.e(TAG, "asyncDownload()", e);
} else {
progressCallback.onFailure(null, e);
}
}
@Override
public void onResponse(Call call, Response response) throws IOException {
saveFile(response, file, startByte, progressCallback);
}
});
} else {
file.delete();
}
}
Call call = okHttpClient.newCall(requestBuilder.build());
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
if (progressCallback == null) {
Log.e(TAG, "asyncDownload()", e);
} else {
progressCallback.onFailure(null, e);
}
}
@Override
public void onResponse(Call call, Response response) throws IOException {
saveFile(response, file, 0, progressCallback);
}
});
}
private static void saveFile(Response response, File destFile, long startByte, ProgressCallback progressCallback) {
ResponseBody body = response.body();
final long contentLength = body.contentLength();
InputStream inputStream = body.byteStream();
FileChannel channelOut = null;
// 随机访问文件,可以指定断点续传的起始位置
RandomAccessFile randomAccessFile = null;
try {
randomAccessFile = new RandomAccessFile(destFile, "rwd");
channelOut = randomAccessFile.getChannel();
// 内存映射,直接使用RandomAccessFile,是用其seek方法指定下载的起始位置,使用缓存下载,在这里指定下载位置。
MappedByteBuffer mappedBuffer = channelOut.map(FileChannel.MapMode.READ_WRITE, startByte, body.contentLength());
byte[] buffer = new byte[1024];
long currentLength = 0;
int length;
while ((length = inputStream.read(buffer)) != -1) {
currentLength += length;
mappedBuffer.put(buffer, 0, length);
if (progressCallback == null) {
Log.e(TAG, "asyncDownload() currentLength = " + currentLength + ", contentLength = " + contentLength);
} else {
progressCallback.onProgress(currentLength, contentLength);
}
}
} catch (IOException e) {
Log.e(TAG, "saveFile()", e);
} finally {
try {
inputStream.close();
if (channelOut != null) {
channelOut.close();
}
if (randomAccessFile != null) {
randomAccessFile.close();
}
} catch (IOException e) {
Log.e(TAG, "saveFile()", e);
}
}
}
public interface ProgressCallback extends Callback {
/**
* 进度回掉
*
* @param currentByte 当前字节
* @param total 总字节
*/
void onProgress(long currentByte, long total);
}
这里要特别注意,如果想要将文件保存在SD
卡里,需要申请android.permission.WRITE_EXTERNAL_STORAGE
这个权限, 如果您的targetSdkVersion
被设置成了大于等于23
,您还必须要在代码中动态申请这个权限。为了方便, 可以使用RxPermissions这个框架,示例:
RxPermissions rxPermissions = new RxPermissions(this);
rxPermissions.requestEach(Manifest.permission.WRITE_EXTERNAL_STORAGE).subscribe(new Observer() {
@Override
public void onSubscribe(@NonNull Disposable d) {
Log.d(TAG, "onSubscribe()");
}
@Override
public void onNext(@NonNull Permission permission) {
Log.d(TAG, "onNext()" + permission);
if (permission.granted) {
//客户授权了
OKHttpRequest.init(MainActivity.this);
OKHttpRequest.asyncDownload("http://192.168.1.103/", "", "/sdcard/xx.txt", true, new OKHttpRequest.ProgressCallback() {
@Override
public void onProgress(long currentByte, long total) {
Log.d(TAG, "total = " + total + ", currentByte = " + currentByte);
}
@Override
public void onFailure(Call call, IOException e) {
Log.e(TAG, "onFailure()", e);
}
@Override
public void onResponse(Call call, Response response) throws IOException {
Log.d(TAG, "onResponse()");
}
});
} else if (permission.shouldShowRequestPermissionRationale){
//客户没有授权,需要提示用户
Toast.makeText(MainActivity.this, "您曾经选择过不授权,您想再授权,请到设置里进行授权", Toast.LENGTH_SHORT).show();
}
}
@Override
public void onError(@NonNull Throwable e) {
Log.e(TAG, "onError()", e);
}
@Override
public void onComplete() {
Log.d(TAG, "onComplete()");
}
});
OKHttp
没有提供数据解析接口,我们只能自己在回掉里面使用Google-Gson
、fastjson
等库进行JSON
数据解析。
从上面的使用来看,直接使用OKHttp
的话,代码量也是很大的,我们必须要进行封装, 有人已经封装好了OKHttpUtils, 另外,Square公司也封装了一个Retrofit。