概述
用途
OKhttp是一个网络请求开源项目,Android网络请求轻量级框架,支持文件上传与下载,支持https。
由来
OKhttp由移动支付Square公司贡献,作为一个建立在开源基础上的公司,在开源方面非常强劲。
使用
引入
在AndroidStudio的gradle中引入
compile 'com.squareup.okhttp3:okhttp:3.9.0'
GET请求
同步get请求
/**
* 同步get请求
*/
public void syncGet() {
OkHttpClient okHttpClient = new OkHttpClient();
Request requset = new Request.Builder()
.url("url?key=valueg&key=value")
.build();
final Call call = okHttpClient.newCall(requset);
//同步get请求
new Thread(new Runnable() {
@Override
public void run() {
try {
Response response = call.execute();
Log.d("test1:", "当前线程名称:" + Thread.currentThread().getName());
Log.d("test1:", "是否响应:" + response.isSuccessful() + ",响应码:" + response.code());
Log.d("test1:", "返回内容:" + response.body().string());
int size = response.headers().size();
for (int i = 0; i < size; i++) {
Log.d("test1", response.headers().name(i) + ":" + response.headers().value(i));
}
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
以上是OKhttp的get同步请求方式,通过call.execute()方法返回response,这是个阻塞过程,不能直接在主线程直接操作网络等耗时操作,所以开启子线程访问网络。
异步get请求
/**
* 异步get请求
*/
public void asyncGet() {
OkHttpClient okHttpClient = new OkHttpClient();
Request requset = new Request.Builder().url("url?key=value&key=value").build();
final Call call = okHttpClient.newCall(requset);
//异步get请求
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
Log.d("test:", "当前线程名称:" + Thread.currentThread().getName());
Log.d("test:", "是否响应:" + response.isSuccessful() + ",响应码:" + response.code());
Log.d("test:", "返回内容:" + response.body().string());
int size = response.headers().size();
for (int i = 0; i < size; i++) {
Log.d("test", response.headers().name(i) + ":" + response.headers().value(i));
}
}
}
);
}
以上是OKhttp的get异步请求方式,通过call.enqueue方法后通过Callback的回调返回response,这个非阻塞线程,所以不用我们开启线程,由Okhttp自动开启。
POST请求
同步post表单请求
/**
* 同步post请求
*/
public void syncPost() {
OkHttpClient okHttpClient = new OkHttpClient();
FormBody.Builder builder = new FormBody.Builder();
builder.add("key", "value");
builder.add("key", "value");
Request requset = new Request.Builder()
.url("url")
.post(builder.build())
.build();
final Call call = okHttpClient.newCall(requset);
//同步get请求
new Thread(new Runnable() {
@Override
public void run() {
try {
Response response = call.execute();
Log.d("test1:", "当前线程名称:" + Thread.currentThread().getName());
Log.d("test1:", "是否响应:" + response.isSuccessful() + ",响应码:" + response.code());
Log.d("test1:", "返回内容:" + response.body().string());
int size = response.headers().size();
for (int i = 0; i < size; i++) {
Log.d("test1", response.headers().name(i) + ":" + response.headers().value(i));
}
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
通过 FormBody.Builder 健值对表单的形式添加参数,在 Request 的post方法将参数添加到请求体中。然后同步方式获取结果 。
异步post表单请求
/**
* 异步post请求
*/
public void asyncPost() {
OkHttpClient okHttpClient = new OkHttpClient();
FormBody.Builder builder = new FormBody.Builder();
builder.add("key", "value");
builder.add("key", "value");
Request requset = new Request.Builder()
.url("url")
.post(builder.build())
.build();
final Call call = okHttpClient.newCall(requset);
//异步get请求
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
Log.d("test4:", "当前线程名称:" + Thread.currentThread().getName());
Log.d("test4:", "是否响应:" + response.isSuccessful() + ",响应码:" + response.code());
Log.d("test4:", "返回内容:" + response.body().string());
int size = response.headers().size();
for (int i = 0; i < size; i++) {
Log.d("test4", response.headers().name(i) + ":" + response.headers().value(i));
}
}
}
);
}
通过 FormBody.Builder 健值对的形式添加参数,在 Request 的post方法将参数添加到请求体中。然后异步方式获取结果。
上传字符串
以提交json举例
RequestBody jsonBody= RequestBody.create(MediaType.parse("application/json"), "json字符串");
Request requset = new Request.Builder()
.url("url")
.post(jsonBody)
.build();
...
通过 RequestBody.create指定MediaType类型和json字符串。
返回结果分析:
10-09 16:09:24.716 22899-22918/? D/test:: 当前线程名称:OkHttp https://way.jd.com/...
10-09 16:09:24.716 22899-22918/? D/test:: 是否响应:true,200
10-09 16:09:24.718 22899-22918/? D/test:: 返回内容:{"code":"10000","charge":false,"msg":"查询成功","result":{"HeWeather5":[{"now":{"hum":"86","vis":"10","pres":"1025","pcpn":"0","fl":"11","tmp":"11","cond":{"txt":"阴","code":"104"},"wind":{"sc":"3-4","spd":"13","deg":"15","dir":"东北风"}},"suggestion":{"uv":{"txt":"属弱紫外线辐射天气,无需特别防护。若长期在户外,建议涂擦SPF在8-12之间的防晒护肤品。","brf":"最弱"},"cw":{"txt":"不宜洗车,未来24小时内有雨,如果在此期间洗车,雨水和路上的泥水可能会再次弄脏您的爱车。","brf":"不宜"},"trav":{"txt":"温度适宜,但风稍大,且较强降雨的天气将给您的出行带来很多的不便,若坚持旅行建议带上雨具。","brf":"一般"},"air":{"txt":"气象条件有利于空气污染物稀释、扩散和清除,可在室外正常活动。","brf":"良"},"comf":{"txt":"白天会有降雨,人们会感到有些凉意,但大部分人完全可以接受。","brf":"较舒适"},"drsg":{"txt":"建议着厚外套加毛衣等服装。年老体弱者宜着大衣、呢外套加羊毛衫。","brf":"较冷"},"sport":{"txt":"有较强降水,建议您选择在室内进行健身休闲运动。","brf":"较不宜"},"flu":{"txt":"天冷空气湿度大,易发生感冒,请注意适当增加衣服,加强自我防护避免感冒。","brf":"易发"}},"aqi":{"city":{"no2":"18","o3":"40","pm25":"5","qlty":"优","so2":"2","aqi":"12","pm10":"5","co":"0"}},"basic":{"city":"北京","update":{"loc":"2017-10-09 17:46","utc":"2017-10-09 09:46"},"lon":"116.40528870","id":"CN101010100","cnty":"中国","lat":"39.90498734"},"daily_forecast":[{"date":"2017-10-09","pop":"96","hum":"73","uv":"1","vis":"12","astro":{"ss":"17:45","mr":"20:27","ms":"09:51","sr":"06:18"},"pres":"1025","pcpn":"36.9","tmp":{"min":"9","max":"15"},"cond":{"txt_n":"中雨","code_n":"306","code_d":"306","txt_d":"中雨"},"wind":{"sc":"微风","spd":"14","deg":"3","dir":"北风"}},{"date":"2017-10-10","pop":"90","hum":"57","uv":"1","vis":"15","astro":{"ss":"17:43","mr":"21:16","ms":"10:59","sr":"06:19"},"pres":"1028","pcpn":"0.0","tmp":{"min":"7","max":"11"},"cond":{"txt_n":"阴","code_n":"104","code_d":"305","txt_d":"小雨"},"wind":{"sc":"微风","spd":"5","deg":"9","dir":"北风"}},{"date":"2017-10-11","pop":"0","hum":"52","uv":"4","vis":"20","astro":{"ss":"17:42","mr":"22:11","ms":"12:04","sr":"06:20"},"pres":"1025","pcpn":"0.0","tmp":{"min":"8","max":"17"},"cond":{"txt_n":"多云","code_n":"101","code_d":"101","txt_d":"多云"},"wind":{"sc":"微风","spd":"7","deg":"321","dir":"西北风"}},{"date":"2017-10-12","pop":"2","hum":"55","uv":"4","vis":"20","astro":{"ss":"17:40","mr":"23:12","ms":"13:03","sr":"06:21"},"pres":"1020","pcpn":"0.0","tmp":{"min":"7","max":"15"},"cond":{"txt_n":"多云","code_n":"101","code_d":"101","txt_d":"多云"},"wind":{"sc":"微风","spd":"6","deg":"210","dir":"西南风"}},{"date":"2017-10-13","pop":"0","hum":"29","uv":"4","vis":"20","astro":{"ss":"17:39","mr":"09:45","ms":"13:56","sr":"06:22"},"pres":"1022","pcpn":"0.0","tmp":{"min":"10","max":"17"},"cond":{"txt_n":"多云","code_n":"101","code_d":"101","txt_d":"多云"},"wind":{"sc":"微风","spd":"4","deg":"175","dir":"南风"}},{"date":"2017-10-14","pop":"13","hum":"49","uv":"3","vis":"19","astro":{"ss":"17:37","mr":"00:15","ms":"14:41","sr":"06:23"},"pres":"1030","pcpn":"13.5","tmp":{"min":"8","max":"14"},"cond":{"txt_n":"阴","code_n":"104","code_d":"104","txt_d":"阴"},"wind":{"sc":"微风","spd":"7","deg":"10","dir":"北风"}},{"date":"2017-10-15","pop":"13","hum":"50","uv":"4","vis":"20","astro":{"ss":"17:36","mr":"01:20","ms":"15:22","sr":"06:24"},"pres":"1034","pcpn":"1.2","tmp":{"min":"9","max":"13"},"cond":{"txt_n":"多云","code_n":"101","code_d":"104","txt_d":"阴"},"wind":{"sc":"微风","spd":"4","deg":"128","dir":"东南风"}}],"hourly_forecast":[{"date":"2017-10-09 19:00","pop":"56","hum":"85","pres":"1028","tmp":"11","cond":{"txt":"中雨","code":"306"},"wind":{"sc":"微风","spd":"12","deg":"12","dir":"东北风"}},{"date":"2017-10-09 22:00","p
10-09 16:09:24.718 22899-22918/? D/test: Server:JDWS/1.0.0
10-09 16:09:24.718 22899-22918/? D/test: Date:Mon, 09 Oct 2017 09:48:55 GMT
10-09 16:09:24.718 22899-22918/? D/test: Content-Type:application/json;charset=utf-8
10-09 16:09:24.718 22899-22918/? D/test: Content-Length:4215
10-09 16:09:24.718 22899-22918/? D/test: Connection:close
10-09 16:09:24.718 22899-22918/? D/test: Expires:Mon, 09 Oct 2017 09:48:55 GMT
10-09 16:09:24.719 22899-22918/? D/test: Cache-Control:max-age=0
10-09 16:09:24.835 22899-22917/? D/test1:: 当前线程名称:Thread-223
10-09 16:09:24.835 22899-22917/? D/test1:: 是否响应:true,200
10-09 16:09:24.836 22899-22917/? D/test1:: 返回内容:{"code":"10000","charge":false,"msg":"查询成功","result":{"HeWeather5":[{"now":{"hum":"86","vis":"10","pres":"1025","pcpn":"0","fl":"11","tmp":"11","cond":{"txt":"阴","code":"104"},"wind":{"sc":"3-4","spd":"13","deg":"15","dir":"东北风"}},"suggestion":{"uv":{"txt":"属弱紫外线辐射天气,无需特别防护。若长期在户外,建议涂擦SPF在8-12之间的防晒护肤品。","brf":"最弱"},"cw":{"txt":"不宜洗车,未来24小时内有雨,如果在此期间洗车,雨水和路上的泥水可能会再次弄脏您的爱车。","brf":"不宜"},"trav":{"txt":"温度适宜,但风稍大,且较强降雨的天气将给您的出行带来很多的不便,若坚持旅行建议带上雨具。","brf":"一般"},"air":{"txt":"气象条件有利于空气污染物稀释、扩散和清除,可在室外正常活动。","brf":"良"},"comf":{"txt":"白天会有降雨,人们会感到有些凉意,但大部分人完全可以接受。","brf":"较舒适"},"drsg":{"txt":"建议着厚外套加毛衣等服装。年老体弱者宜着大衣、呢外套加羊毛衫。","brf":"较冷"},"sport":{"txt":"有较强降水,建议您选择在室内进行健身休闲运动。","brf":"较不宜"},"flu":{"txt":"天冷空气湿度大,易发生感冒,请注意适当增加衣服,加强自我防护避免感冒。","brf":"易发"}},"aqi":{"city":{"no2":"18","o3":"40","pm25":"5","qlty":"优","so2":"2","aqi":"12","pm10":"5","co":"0"}},"basic":{"city":"北京","update":{"loc":"2017-10-09 17:46","utc":"2017-10-09 09:46"},"lon":"116.40528870","id":"CN101010100","cnty":"中国","lat":"39.90498734"},"daily_forecast":[{"date":"2017-10-09","pop":"96","hum":"73","uv":"1","vis":"12","astro":{"ss":"17:45","mr":"20:27","ms":"09:51","sr":"06:18"},"pres":"1025","pcpn":"36.9","tmp":{"min":"9","max":"15"},"cond":{"txt_n":"中雨","code_n":"306","code_d":"306","txt_d":"中雨"},"wind":{"sc":"微风","spd":"14","deg":"3","dir":"北风"}},{"date":"2017-10-10","pop":"90","hum":"57","uv":"1","vis":"15","astro":{"ss":"17:43","mr":"21:16","ms":"10:59","sr":"06:19"},"pres":"1028","pcpn":"0.0","tmp":{"min":"7","max":"11"},"cond":{"txt_n":"阴","code_n":"104","code_d":"305","txt_d":"小雨"},"wind":{"sc":"微风","spd":"5","deg":"9","dir":"北风"}},{"date":"2017-10-11","pop":"0","hum":"52","uv":"4","vis":"20","astro":{"ss":"17:42","mr":"22:11","ms":"12:04","sr":"06:20"},"pres":"1025","pcpn":"0.0","tmp":{"min":"8","max":"17"},"cond":{"txt_n":"多云","code_n":"101","code_d":"101","txt_d":"多云"},"wind":{"sc":"微风","spd":"7","deg":"321","dir":"西北风"}},{"date":"2017-10-12","pop":"2","hum":"55","uv":"4","vis":"20","astro":{"ss":"17:40","mr":"23:12","ms":"13:03","sr":"06:21"},"pres":"1020","pcpn":"0.0","tmp":{"min":"7","max":"15"},"cond":{"txt_n":"多云","code_n":"101","code_d":"101","txt_d":"多云"},"wind":{"sc":"微风","spd":"6","deg":"210","dir":"西南风"}},{"date":"2017-10-13","pop":"0","hum":"29","uv":"4","vis":"20","astro":{"ss":"17:39","mr":"09:45","ms":"13:56","sr":"06:22"},"pres":"1022","pcpn":"0.0","tmp":{"min":"10","max":"17"},"cond":{"txt_n":"多云","code_n":"101","code_d":"101","txt_d":"多云"},"wind":{"sc":"微风","spd":"4","deg":"175","dir":"南风"}},{"date":"2017-10-14","pop":"13","hum":"49","uv":"3","vis":"19","astro":{"ss":"17:37","mr":"00:15","ms":"14:41","sr":"06:23"},"pres":"1030","pcpn":"13.5","tmp":{"min":"8","max":"14"},"cond":{"txt_n":"阴","code_n":"104","code_d":"104","txt_d":"阴"},"wind":{"sc":"微风","spd":"7","deg":"10","dir":"北风"}},{"date":"2017-10-15","pop":"13","hum":"50","uv":"4","vis":"20","astro":{"ss":"17:36","mr":"01:20","ms":"15:22","sr":"06:24"},"pres":"1034","pcpn":"1.2","tmp":{"min":"9","max":"13"},"cond":{"txt_n":"多云","code_n":"101","code_d":"104","txt_d":"阴"},"wind":{"sc":"微风","spd":"4","deg":"128","dir":"东南风"}}],"hourly_forecast":[{"date":"2017-10-09 19:00","pop":"56","hum":"85","pres":"1028","tmp":"11","cond":{"txt":"中雨","code":"306"},"wind":{"sc":"微风","spd":"12","deg":"12","dir":"东北风"}},{"date":"2017-10-09 22:00","
10-09 16:09:24.837 22899-22917/? D/test1: Server:JDWS/1.0.0
10-09 16:09:24.837 22899-22917/? D/test1: Date:Mon, 09 Oct 2017 09:48:55 GMT
10-09 16:09:24.837 22899-22917/? D/test1: Content-Type:application/json;charset=utf-8
10-09 16:09:24.837 22899-22917/? D/test1: Content-Length:4215
10-09 16:09:24.837 22899-22917/? D/test1: Connection:close
10-09 16:09:24.837 22899-22917/? D/test1: Expires:Mon, 09 Oct 2017 09:48:55 GMT
10-09 16:09:24.837 22899-22917/? D/test1: Cache-Control:max-age=0
同步调用execute()方法,异步调用enqueue(),
get请求直接URL拼接参数,post需要另外构建参数实体添加到request对象中,
当结果返回时,都不是在主线程,所以不能直接操作UI。这个需要注意!
header中添加头信息
如果你需要在request的的header添加参数。例如Cookie、Token等
Request requset = new Request.Builder()
.url("https://way.jd.com/he/freeweather?city=beijing&appkey=7b84a35601b8715fa49c3714f76844ba")
.header("key", "value")
.header("key", "value")
.build();
Response
- response.isSuccessful() 返回请求结果
- response.code() 返回结果码
- response.headers() 返回请求结果的头部信息
- response.body() 返回结果内容
- response.body().string()返回字符串
- response.body().bytes() 返回字节数组
- response.body().byteStream() 返回字节流
- response.body().charStream() 返回字符流
- response.body().contentLength() 长度
- response.body().contentType() 内容类型
- …
能拿到字节流当然我们就能下载文件。
文件操作
上传文件
private void uploadAsyncMultiFile() {
OkHttpClient okHttpClient = new OkHttpClient();
final String url = "服务器接口地址";
File file = new File("path", "abc.jpg");
RequestBody requestBody = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("key1", "fileName", RequestBody.create(MediaType.parse("application/octet-stream"), file))
.addFormDataPart("key2", "value")
.build();
Request request = new Request.Builder()
.url(url)
.post(requestBody)
.build();
okHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.e("test5", "uploadAsyncMultiFile() e=" + e);
}
@Override
public void onResponse(Call call, Response response) throws IOException {
Log.i("test5", "uploadAsyncMultiFile() response=" + response.body().string());
}
});
}
通过MultipartBody.Builder()上传参数以及文件,
addFormDataPart()随意添加参数和文件对象。
和html提交表单和文件一致效果。
下载文件
private void downloadFile() {
OkHttpClient okHttpClient = new OkHttpClient();
Request request = new Request.Builder()
.url("https://www.baidu.com/")
.build();
okHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.e("test6", "downloadFile() e=" + e);
}
@Override
public void onResponse(Call call, Response response) throws IOException {
InputStream inputStream = response.body().byteStream();
byte[] b = new byte[1024];
File outFile = new File("/sdcard/baidu.html");
if (!outFile.exists()){
outFile.createNewFile();
}else {
outFile.delete();
}
FileOutputStream fileOutputStream = new FileOutputStream(outFile);
BufferedOutputStream bos = new BufferedOutputStream(fileOutputStream);
int len;
while ((len=inputStream.read(b))!=-1){
bos.write(b,0,len);
}
bos.flush();
bos.close();
inputStream.close();
Log.i("test6", "uploadAsyncMultiFile() 下载完成");
}
});
}
既然能拿到输入流response.body().byteStream() 那么写文件到本地也就是很简单的事情了。
拦截器
拦截器是一种强大的机制,可以监视、重写和重试调用.下面是一个简单例子,拦截发出的请求和传入的响应的日志。
如上图所示,有两种拦截器,应用拦截器和网络拦截器。
那么有什么区别呢?
/**
* 应用拦截器
*/
public static class MyInterceptor implements okhttp3.Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Request request2 = chain.request();
Log.d(TAG, String.format("request: URL: %s%nMETHOD: %s%n%s",
request2.url(),
request2.method(),
request2.headers()));
Response proceed = chain.proceed(request2);
Log.d(TAG, String.format("response: URL: %s%n%s",
proceed.request().url(),
proceed.headers()
)
);
return proceed;
}
}
调用 chain.proceed(request) 是每个拦截器的关键部分的实现.这个简单的方法存在所有HTTP工作发生的地方,生产满足请求的响应。
可以添加多个拦截器,假设您有一个压缩拦截器和校验拦截器:你需要决定数据是先压缩然后校验,还是先校验后压缩.OkHttp使用列表追踪拦截器,拦截器按顺序被调用。
应用拦截器
添加应用拦截器
OkHttpClient.Builder builder = new OkHttpClient.Builder()
.connectTimeout(5000, TimeUnit.SECONDS)
.readTimeout(5000, TimeUnit.SECONDS)
.writeTimeout(5000, TimeUnit.SECONDS)
.retryOnConnectionFailure(true)
.addInterceptor(new MyInterceptor());
返回
10-16 22:39:09.003 14566-14595/okhttpdemo.huyunqiang.com.okhttpdemo D/OkhttpService: request: URL: https://way.jd.com/he/freeweather
METHOD: POST
10-16 22:39:09.266 14566-14595/okhttpdemo.huyunqiang.com.okhttpdemo D/OkhttpService: response: URL: https://way.jd.com/he/freeweather
Server: JDWS/1.0.0
Date: Tue, 17 Oct 2017 07:20:38 GMT
Content-Type: application/json;charset=utf-8
Content-Length: 4427
Connection: close
Expires: Tue, 17 Oct 2017 07:20:38 GMT
Cache-Control: max-age=0
很奇怪,为什么没有打印request的header信息!因为还没有到网络请求,这个地方只能打印我们自己定义的header等信息。
网络拦截器
添加网络拦截器,和添加应用拦截器一样,只是把addInterceptor()换成了addNetworkInterceptor();
OkHttpClient.Builder builder = new OkHttpClient.Builder()
.connectTimeout(5000, TimeUnit.SECONDS)
.readTimeout(5000, TimeUnit.SECONDS)
.writeTimeout(5000, TimeUnit.SECONDS)
.retryOnConnectionFailure(true)
.addNetworkInterceptor(new MyInterceptor());
返回
10-16 22:46:15.454 19788-19804/? D/OkhttpService: request: URL: https://way.jd.com/he/freeweather
METHOD: POST
Content-Type: application/x-www-form-urlencoded
Content-Length: 52
Host: way.jd.com
Connection: Keep-Alive
Accept-Encoding: gzip
User-Agent: okhttp/3.9.0
10-16 22:46:15.617 19788-19804/? D/OkhttpService: response: URL: https://way.jd.com/he/freeweather
Server: JDWS/1.0.0
Date: Tue, 17 Oct 2017 07:27:44 GMT
Content-Type: application/json;charset=utf-8
Content-Length: 4428
Connection: close
Expires: Tue, 17 Oct 2017 07:27:44 GMT
Cache-Control: max-age=0
这里打印了我想要的包含header信息。包含okhttp封装的请求头,是实际发送得数据。
差异
应用拦截器实际上还没有到网络交互这一块,很多数据(包含请求头)并没有体现。
网络拦截器其实已经到了实际发送请求步骤,okhttp封装请求体得体现。
其实还有更多,具体看官方文档说明
应用拦截器
- 不需要担心中间过程的响应,如重定向和重试.
- 总是只调用一次,即使HTTP响应是从缓存中获取.
- 观察应用程序的初衷. 不关心OkHttp注入的头信息如: If-None-Match.
- 允许短路而不调用 - Chain.proceed(),即中止调用.
- 允许重试,使 Chain.proceed()调用多次.
网络拦截器
- 能够操作中间过程的响应,如重定向和重试.
- 当网络短路而返回缓存响应时不被调用.
- 只观察在网络上传输的数据.
- 携带请求来访问连接.