OkHttp的总结分为两个部分,一个是基本用法,带你认识OkHttp,第二部分是源码分析带你对OkHttp进行深入了解。
先看看官网对OkHttp的描述
官网地址:http://square.github.io/okhttp/
HTTP is the way modern applications network. It’s how we exchange data & media. Doing HTTP efficiently makes your stuff load faster and saves bandwidth.
OkHttp is an HTTP client that’s efficient by default:
HTTP/2 support allows all requests to the same host to share a socket.
Connection pooling reduces request latency (if HTTP/2 isn’t available).
Transparent GZIP shrinks download sizes.
Response caching avoids the network completely for repeat requests.
OkHttp perseveres when the network is troublesome: it will silently recover from common connection problems. If your service has multiple IP addresses OkHttp will attempt alternate addresses if the first connect fails. This is necessary for IPv4+IPv6 and for services hosted in redundant data centers. OkHttp initiates new connections with modern TLS features (SNI, ALPN), and falls back to TLS 1.0 if the handshake fails.
Using OkHttp is easy. Its request/response API is designed with fluent builders and immutability. It supports both synchronous blocking calls and async calls with callbacks.
OkHttp supports Android 2.3 and above. For Java, the minimum requirement is 1.7.
简单翻译下,Http是应用程序进行网络请求的方式。OkHttp默认具有如下功能:1.支持Http2和SPDY,允许同一主机的所有请求共用一个socket(套接字) 2.如Http2不可用。会采用连接池复用连接 3.提供默认Gzip压缩以降低传输内容的大小 4.有Http响应缓存机制,避免重复的网络请求 5.如果发生网络错误,自动重试备用地址。
支持同步阻塞调用和带回调的异步调用。OkHttp支持Android 2.3及以上版本。对于Java,最低要求是1.7。
初探OkHttp
我用的是Android Studio直接引入
compile 'com.squareup.okhttp3:okhttp:3.2.0'
至于Eclipse下载jar包
http://repo1.maven.org/maven2/com/squareup/okhttp3/okhttp/3.2.0/okhttp-3.2.0.jar
建议使用AS,毕竟目前大多数项目都是采用AS开发
先使用同步方式做一个get请求
下载一个文件,打印他的响应头,以string形式打印响应体。
响应体的 string() 方法对于小文档来说十分方便、高效。但是如果响应体太大(超过1MB),应避免适应 string()方法 ,因为他会将把整个文档加载到内存中。
对于超过1MB的响应body,应使用流的方式来处理body。
注意两点一个是网络权限,一个是另请线程做请求因为从Honeycomb SDK(3.0)开始,google不再允许网络请求(HTTP、Socket)等相关操作直接在Main Thread类中进行,如果在主线程中直接进行网络网络请求会报android.os.NetworkOnMainThreadException异常
public class TestGetActivity extends AppCompatActivity {
private final OkHttpClient client = new OkHttpClient();
private static final String TAG = "TAG";
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test_get);
new Thread(runnable).start();
}
private Runnable runnable = new Runnable() {
@Override
public void run() {
try {
syncGet();
} catch (Exception e) {
e.printStackTrace();
}
}
};
/**
* 同步请求get
* @throws Exception
*/
public void syncGet() throws Exception {
Request request = new Request.Builder()
.url("http://device.quanjiakan.com/devices/api?handler=watch&action=devicelist&memberId=11780&platform=2&token=2156b5715e8afbd0e6b63469f292fba2")
.build();
Response response = client.newCall(request).execute();
if (!response.isSuccessful()) {
throw new IOException("Unexpected code " + response);
}
Headers responseHeaders = response.headers();
ResponseBody responseBody = response.body();
for (int i = 0; i < responseHeaders.size(); i++) {
Log.e(TAG, "响应头:---" + responseHeaders.name(i) + ": " + responseHeaders.value(i));
}
Log.e(TAG, "响应体:---" + responseBody.string());
}
}
来分析一下okhttp的请求和响应
okhttp请求
请求.png
okhttp响应
响应.png
看看异步请求GET,修改代码后
public class TestGetActivity extends AppCompatActivity {
private final OkHttpClient client = new OkHttpClient();
private static final String TAG = "TAG";
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test_get);
// new Thread(runnable).start();
// 使用enqueue方法,将call放入请求队列,然后okHttp会在线程池中进行网络访问;
try {
asynGet();
} catch (Exception e) {
e.printStackTrace();
}
}
private Runnable runnable = new Runnable() {
@Override
public void run() {
try {
syncGet();
} catch (Exception e) {
e.printStackTrace();
}
}
};
/**
* 同步请求get
* @throws Exception
*/
public void syncGet() throws Exception {
Request request = new Request.Builder()
.url("http://device.quanjiakan.com/devices/api?handler=watch&action=devicelist&memberId=11780&platform=2&token=2156b5715e8afbd0e6b63469f292fba2")
.build();
Response response = client.newCall(request).execute();
if (!response.isSuccessful()) {
throw new IOException("Unexpected code " + response);
}
Headers responseHeaders = response.headers();
ResponseBody responseBody = response.body();
for (int i = 0; i < responseHeaders.size(); i++) {
Log.e(TAG, "响应头:---" + responseHeaders.name(i) + ": " + responseHeaders.value(i));
}
Log.e(TAG, "响应体:---" + responseBody.string());
}
/**
* 异步Get请求
* @throws Exception
*/
public void asynGet() throws Exception {
Request request = new Request.Builder()
.url("http://device.quanjiakan.com/devices/api?handler=watch&action=devicelist&memberId=11780&platform=2&token=2156b5715e8afbd0e6b63469f292fba2")
.build();
client.newCall(request).enqueue(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 {
if (!response.isSuccessful()) {
throw new IOException("Unexpected code " + response);
}
Headers responseHeaders = response.headers();
for (int i = 0, size = responseHeaders.size(); i < size; i++) {
Log.e(TAG, "onResponse: --" + responseHeaders.name(i) + ": " + responseHeaders.value(i));
}
System.out.println();
Log.e(TAG, "onResponse: --" + response.body().string());
}
});
}
}
需要注意的是,无论同步还是异步都是在子线程进行的,因此他们的回调中我们不能直接更新UI,可以采用Handler(主线程的)发送消息到主线程,并在主线程更新UI
前面已经了解了请求头,我们来了解一下在okhttp中怎么设置头部和读取头部
HTTP 头的数据结构是 Map<String, List<String>>类型。也就是说,对于每个 HTTP 头,可能有多个值。但是大部分 HTTP 头都只有一个值,只有少部分 HTTP 头允许多个值。OkHttp的处理方式是:
使用header(name,value)来设置HTTP头的唯一值
使用addHeader(name,value)来补充新值
使用header(name)读取唯一值或多个值的最后一个值
使用headers(name)获取所有值
举个例子
public void syncGet() throws Exception {
Request request = new Request.Builder()
.url("https://api.github.com/repos/square/okhttp/issues")
.header("User-Agent", "OkHttp Headers.java")
.addHeader("Accept", "application/json; q=0.5")
.addHeader("Accept", "application/vnd.github.v3+json")
// .url("http://device.quanjiakan.com/devices/api?handler=watch&action=devicelist&memberId=11780&platform=2&token=2156b5715e8afbd0e6b63469f292fba2")
.build();
Headers headers = request.headers();
for (int i = 0; i < headers.size(); i++) {
Log.e(TAG, "请求头: ---"+ headers.name(i) + ": " + headers.value(i));
}
Response response = client.newCall(request).execute();
if (!response.isSuccessful()) {
throw new IOException("Unexpected code " + response);
}
Headers responseHeaders = response.headers();
ResponseBody responseBody = response.body();
for (int i = 0; i < responseHeaders.size(); i++) {
Log.e(TAG, "响应头:---" + responseHeaders.name(i) + ": " + responseHeaders.value(i));
}
Log.e(TAG, "响应体:---" + responseBody.string());
}
我们来看看打印的信息
请求
image.png
响应
image.png
Request类默认是GET请求,那么我们来看看POST请求
1.Post提交键值对 表单提交
//1.post提交键值对 form形式
//异步同步已经知道他们的区别了,这里为了简化采用同步请求
new Thread(new Runnable() {
@Override
public void run() {
//同步
try {
String url = "http://api.k780.com:88/";
OkHttpClient okHttpClient = new OkHttpClient();
FormBody formBody = new FormBody.Builder()
.add("app", "weather.future")
.add("weaid", "1")
.add("appkey", "10003")
.add("sign",
"b59bc3ef6191eb9f747dd4e83c99f2a4")
.add("format", "json")
.build();
Request request = new Request.Builder()
.url(url)
.post(formBody)
.build();
okHttpClient.newCall(request).execute();
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
2.Post提交String字符串
/**
* 2.Post提交String字符串
* 可以使用Post方法发送一串字符串,但不建议发送超过1M的文本信息,如下示例展示了,发送一个markdown文本
* @throws Exception
*/
public void doPost() throws Exception {
String postBody = ""
+ "Releases\n"
+ "--------\n"
+ "\n"
+ " * _1.0_ May 6, 2013\n"
+ " * _1.1_ June 15, 2013\n"
+ " * _1.2_ August 11, 2013\n";
Request request = new Request.Builder()
.url("https://api.github.com/markdown/raw")
.post(RequestBody.create(MEDIA_TYPE_MARKDOWN, postBody))
.build();
Response response = client.newCall(request).execute();
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
Log.e(TAG, "doPost: --" + response.body().string() );
}
还可以提交json
public static final MediaType JSON
= MediaType.parse("application/json; charset=utf-8");
OkHttpClient client = new OkHttpClient();
String post(String url, String json) throws IOException {
RequestBody body = RequestBody.create(JSON, json);
Request request = new Request.Builder()
.url(url)
.post(body)
.build();
Response response = client.newCall(request).execute();
return response.body().string();
}
3.Post提交流
/**
* post可以把流对象作为请求体,依赖okio将数据写成输出流的形式
* @throws IOException
*/
public void postStream() throws IOException {
RequestBody requestBody = new RequestBody() {
@Override
public MediaType contentType() {
return MEDIA_TYPE_MARKDOWN;
}
@Override
public void writeTo(BufferedSink sink) throws IOException {
sink.writeUtf8("Numbers\n");
sink.writeUtf8("-------\n");
for (int i = 2; i <= 997; i++) {
sink.writeUtf8(String.format(" * %s = %s\n", i, factor(i)));
}
}
private String factor(int n) {
for (int i = 2; i < n; i++) {
int x = n / i;
if (x * i == n) return factor(x) + " × " + i;
}
return Integer.toString(n);
}
};
Request request = new Request.Builder()
.url("https://api.github.com/markdown/raw")
.post(requestBody)
.build();
Response response = client.newCall(request).execute();
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
Log.e(TAG, "postStream: ---"+ response.body().string());
}
4.post提交file
/**
* post提交文件
* @throws IOException
*/
public void postFile() throws IOException {
File file = new File("README.md");
Request request = new Request.Builder()
.url("https://api.github.com/markdown/raw")
.post(RequestBody.create(MEDIA_TYPE_MARKDOWN, file))
.build();
Response response = client.newCall(request).execute();
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
Log.e(TAG, "postFile: ---"+ response.body().string());
}
5.post分块上传
/**
* post分块提交
* @throws IOException
*/
public void postMultipartBody() throws IOException {
//创建请求体
MultipartBody multipartBody = new MultipartBody.Builder("AaB03x")
.setType(MultipartBody.FORM)
.addPart(Headers.of("Content-Disposition", "form-data; name=\"title\""),
RequestBody.create(null, "Square Logo"))
.addPart(Headers.of("Content-Disposition", "form-data; name=\"image\""),
RequestBody.create(MEDIA_TYPE_PNG, new File("website/static/logo-square.png")))
.build();
//创建请求
Request request = new Request.Builder()
.header("Authorization", "Client-ID " + IMGUR_CLIENT_ID)
.url("https://api.imgur.com/3/image")
.post(multipartBody)
.build();
//用okhttpClient做请求
Call call = client.newCall(request);
Response response = call.execute();
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
Log.e(TAG, "postMultipartBody: --" + response.body().string());
}
设置响应缓存
//1.OkHttpClient设置缓存
//a.设置缓存文件路径
File cacheFile = new File(getCacheDir(), "okhttpcache");
//b.设置缓存大小
int cacheSize = 10*1024*1024; //10MB
//c.Cache类创建缓存
Cache cache = new Cache(cacheFile, cacheSize);
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.cache(cache)
.build();
原则上来讲如果服务器支持缓存(请求返回的Response会带有这样的Header:Cache-Control, max-age=xxx,这种情况下我们只需要手动给okhttp设置缓存就可以让okhttp自动帮你缓存了。这里的max-age的值代表了缓存在你本地存放的时间。),我们这样就ok了。如果服务器没有缓存,那么我们就需要设置拦截器来重写Response头部从而达到缓存的目的。
看看怎么实现
//拦截器
Interceptor interceptor = new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Response response = chain.proceed(request);
String cacheControl = request.cacheControl().toString();
//如果没有设置缓存控制
if (TextUtils.isEmpty(cacheControl)) {
cacheControl = "public, max-age=60";
}
return response.newBuilder()
.header("Cache-Control", cacheControl)
.removeHeader("Pragma")
.build();
}
};
OkHttpClient client = new OkHttpClient.Builder()
.addNetworkInterceptor(interceptor)
.cache(cache)
.build();
上面这种情况是不管有没有网络,都先读缓存,缓存时间为60s;当然我们还可以对拦截器进行设置实现我们的缓存策略:离线读取缓存,在线获取网络数据
/**
* post分块提交
*
* @throws IOException
*/
public void postMultipartBody() throws IOException {
//创建请求体
MultipartBody multipartBody = new MultipartBody.Builder("AaB03x")
.setType(MultipartBody.FORM)
.addPart(Headers.of("Content-Disposition", "form-data; name=\"title\""),
RequestBody.create(null, "Square Logo"))
.addPart(Headers.of("Content-Disposition", "form-data; name=\"image\""),
RequestBody.create(MEDIA_TYPE_PNG, new File("website/static/logo-square.png")))
.build();
//创建请求
Request request = new Request.Builder()
.header("Authorization", "Client-ID " + IMGUR_CLIENT_ID)
.url("https://api.imgur.com/3/image")
.post(multipartBody)
.build();
//用okhttpClient做请求
Call call = client.newCall(request);
Response response = call.execute();
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
Log.e(TAG, "postMultipartBody: --" + response.body().string());
}
public void setCacheResponse() throws Exception {
//1.OkHttpClient设置缓存
//a.设置缓存文件路径
File cacheFile = new File(getCacheDir(), "okhttpcache");
//b.设置缓存大小
int cacheSize = 10 * 1024 * 1024; //10MB
//c.Cache类创建缓存
Cache cache = new Cache(cacheFile, cacheSize);
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.cache(cache)
.build();
//拦截器
Interceptor cachInterceptor = new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
if (!isNetworkAvailable(TestPostActivity.this)) { //如果网络不可用
//重新Request
request = request.newBuilder()
.cacheControl(CacheControl.FORCE_CACHE) //无网络只从缓存中读取
.build();
} else {
//重新Request
request = request.newBuilder()
.cacheControl(CacheControl.FORCE_NETWORK) //有网络时只从网络获取
.build();
}
Response response = chain.proceed(request);
if (isNetworkAvailable(TestPostActivity.this)) {
int maxAge = 5 * 60; //有网络时设置超时时间5分钟
response = response.newBuilder()
.header("Cache-Control", "public, max-age=" + maxAge) //这个可以控制我们的我们频繁操作网络请求,给缓存设置一个有效时间,
// 在这段时间内会优先拿缓存,超过时间则拿网络数据并且更新缓存
.removeHeader("Pragma")// 清除头信息,因为服务器如果不支持,会返回一些干扰信息,不清除下面无法生效
.build();
} else {
int maxStale = 24 * 60 * 60; //无网络时设置超时为1天
response = response.newBuilder()
.header("Cache-Control", "public, only-if-cached, max-stale=" + maxStale)
.removeHeader("Pragma")// 清除头信息,因为服务器如果不支持,会返回一些干扰信息,不清除下面无法生效
.build();
}
return response;
}
};
OkHttpClient client = new OkHttpClient.Builder()
.addNetworkInterceptor(cachInterceptor)
.connectTimeout(10, TimeUnit.SECONDS) //连接超时10s
.readTimeout(10, TimeUnit.SECONDS) //读取超时10s
.writeTimeout(10, TimeUnit.SECONDS) //写超时10s
.cache(cache)
.build();
final Request request = new Request.Builder()
.url("http://app.quanjiakan.com/pingan/api?handler=jugui&action=getversion&alias=pingan_android_version&token=b3b837ada4b3c0d00c0957bd160764d2")
.build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.e(TAG, "onFailure: ---" + e.getMessage());
}
@Override
public void onResponse(Call call, Response response) throws IOException {
if (!response.isSuccessful()) {
throw new IOException("Unexpected code " + response);
}
Log.e(TAG, "onResponse: ---" + response.body().string());
}
});
}
/**
* 检测当的网络状态
*
* @param context Context
* @return true 表示网络可用
*/
public static boolean isNetworkAvailable(Context context) {
ConnectivityManager connectivity = (ConnectivityManager) context
.getSystemService(Context.CONNECTIVITY_SERVICE);
if (connectivity != null) {
NetworkInfo info = connectivity.getActiveNetworkInfo();
if (info != null && info.isConnected()) {
// 当前网络是连接的
if (info.getState() == NetworkInfo.State.CONNECTED) {
// 当前所连接的网络可用
return true;
}
}
}
return false;
}
拦截器
上面我们已经使用过拦截器了,让我们来详细了解一下它的作用。拦截器是一种能够监控、重写、重试调用的强大机制,我们可以使用拦截器添加我们的头信息、网络请求、网络缓存等。
拦截器是个接口类
public interface Interceptor {
Response intercept(Chain chain) throws IOException;
interface Chain {
Request request();
Response proceed(Request request) throws IOException;
Connection connection();
}
}
在注册拦截器时,可以注册成两类拦截器,分别为应用拦截器(Application Interceptors)和网络拦截器(Network Interceptors);
Application Interceptors 应用拦截器:
应用拦截器一般使用最多的是打印日志,查看请求信息及返回信息,下面简单的看下示例代码:
class LoggerInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
long t1 = System.nanoTime();
Log.d(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.d(TAG, String.format("Received response for %s in %.1fms%n%sconnection=%s",
response.request().url(), (t2 - t1) / 1e6d, response.headers(), chain.connection()));
return response;
}
}
设置Token
class TokenInterceptor implements Interceptor {
private static final String TOKEN = "Authorization";
private String token;
public TokenInterceptor(String token) {
this.token = token;
}
@Override
public Response intercept(Chain chain) throws IOException {
Request originalRequest = chain.request();
if (token == null || originalRequest.header(TOKEN) != null) {
return chain.proceed(originalRequest);
} else {
Request newRequest = originalRequest.newBuilder()
.header(TOKEN, token)
.build();
return chain.proceed(newRequest);
}
}
}
上面我们判断token不为空,有时候还没有请求到token或者登陆的时候不需要token,这个时候直接请求就好了。如果我们请求的header已经有token了,那么也不需要在添加token了,token的键一般是Authorization ,如果不是可以修改成你的值,作为验证身份用。
复用okhttpclient配置
所有Http请求的代理设置,超时,缓存等都需要在okHttpClient中设置,如果需要更改一个请求的配置,可以使用 OkHttpClient.newBuilder()获取一个builder对象,该builder对象与原来OkHttpClient共享相同的连接池,配置等。
总结:本篇文章侧重于OkHttp3的基本使用,可以看看这篇文章,作者写的很用心讲解很细致。
链接地址:https://blog.csdn.net/qq_23191031/article/details/54136755
网友评论