OkHttp是由Square公司贡献的一个 处理网络请求 的开源项目,是目前Android使用最广泛的网络框架。从Android4.4开始HttpURLConnection的底层实现采用的是OkHttp
- 支持HTTP/2,并允许对同一主机的所有请求共享一个套接字
- 如果非 HTTP/2,则通过连接池减少了请求延迟
- 默认请求GZip压缩数据
- 响应缓存,避免了重复请求的网络
测试HTTP请求的服务器(大家都可以使用)URL:
https://www.httpbin.org/
一、OkHttp的基本使用
1、添加依赖,申请权限
在模块的build.gradle中添加依赖:
implementation("com.squareup.okhttp3:okhttp:4.9.0")
在AndroidManifest.xml中申请网络权限:
<uses-permission android:name="android.permission.INTERNET"/>
2、基本使用
OkHttpClient okHttpClient = new OkHttpClient();
GET同步请求:
//默认为Get请求
//Request.Builder().url(URL).get().build()中的get()可省略
//如果是Post,post()方法必须显式写出来
//创建Request请求对象
Request request = new Request.Builder()
.url("https://www.httpbin.org/get?a=1&b=2")
.build();
//获得准备执行请求的Call对象
Call call = okHttpClient.newCall(request);
try{
//执行请求得到响应
Response response = call.execute();
System.out.println("getSync:" + response.body().string());
}catch(IOException e){
e.printStackTrace();
}
结果:
getSync:{
"args": {
"a": "1",
"b": "2"
},
"headers": {
"Accept-Encoding": "gzip",
"Host": "www.httpbin.org",
"User-Agent": "okhttp/4.9.0",
"X-Amzn-Trace-Id": "Root=1-62f46765-066cbbd646cecf103cd67c0b"
},
"origin": "223.104.38.107",
"url": "https://www.httpbin.org/get?a=1&b=2"
}
GET异步请求:
OkHttpClient okHttpClient = new OkHttpClient();
Request request = new Request.Builder()
.url("https://www.httpbin.org/get")
.build();
Call call = okHttpClient.newCall(request);
call.enqueue(new Callback(){
@Override
public void onFailure(Call call, IOException e){
System.out.println( "getAsync onFailure:" + e);
}
@Override
public void onResponse(Call call, Response response) throws IOException {
System.out.println( "getAsync onResponse:" + response.body().string());
}
});
结果:
getAsync onResponse:{
"args": {},
"headers": {
"Accept-Encoding": "gzip",
"Host": "www.httpbin.org",
"User-Agent": "okhttp/4.9.0",
"X-Amzn-Trace-Id": "Root=1-62f46837-383412113417986048a23c1c"
},
"origin": "223.104.38.107",
"url": "https://www.httpbin.org/get"
}
注意:
在Android中同步请求需写在一个新的线程当中:
new Thread(){
@Override
public void run(){
//OkHttp,get同步请求代码
...
}
}.start();
开启一个新线程是因为,Call.execute()方法会发生阻塞,只有该方法执行完毕,才会执行下面的代码
Call.enqueue()方法内部会自己创建子线程
启动一个Post同步请求:
public void postSync(View view){
new Thread(){
@Override
public void run(){
FormBody formBody = new FormBody.Builder()
.add("a","1")
.add("b", "2")
.build;
Request request = new Request.Builder()
.url("https://www.httpbin.org/post")
.post(formBody)
.build();
//获得准备执行请求的Call对象
Call call = okHttpClient.newCall(request);
try{
//执行请求得到响应
Response response = call.execute();
Log.i(TAG, "postSync:" + response.body().string());
}catch(IOException e){
e.printStackTrace();
}
}
}.start();
}
同理,可写出Post异步请求
二、POST请求的数据格式
协议规定 POST 提交的数据必须放在请求体中,但协议并没有规定数据必须使用什么编码方式。而常用的数据编码方式有:
https://www.runoob.com/http/http-content-type.html
-
Content-Type:application/x-www-form-urlencoded
数据被编码为名称/值对,默认类型
-
Content-Type:multipart/form-data
数据被编码为一条消息,一般用于文件上传
-
Content-Type:application/octet-stream
提交二进制数据,如果用于文件上传,只能上传一个文件
-
Content-Type:application/json
提交json数据
代码示例:
1、上传多个文件:
public class uploadFileUnitTest(){
public void uploadFileTest(){
OkHttpClient okHttpClient = new OkHttpClient();
File file1 = new File("E:\\1.txt");
File file2 = new File("E:\\2.txt");
//addFormDataPart参数"file1","file2"这个名称在真正开发时需和服务器协商好
MultipartBody multipartBody = new MultipartBody.Builder()
.addFormDataPart("file1", file1.getName(), RequestBody.create(file1, MediaType.parse("text/plain")))
.addFormDataPart("file2", file2.getName(), RequestBody.create(file2, MediaType.parse("text/plain")))
.build();
Request request = new Request.Builder()
.url("https://www.httpbin.org/post")
.post(multipartBody)
.build();
Call call = okHttpClient.newCall(request);
Response response = call.execute();
System.out.println(response.body().string());
}
}
结果:
{
"args": {},
"data": "--0ec6eed5-b84c-480a-878b-f7477e1f5211\r\nContent-Disposition: form-data; name=\"f1\"; filename=\"1.txt\"\r\nContent-Type: text/plain\r\nContent-Length: 22\r\n\r\n1111111111111111111111\r\n--0ec6eed5-b84c-480a-878b-f7477e1f5211\r\nContent-Disposition: form-data; name=\"f2\"; filename=\"2.txt\"\r\nContent-Type: text/plain\r\nContent-Length: 16\r\n\r\n2222222222222222\r\n--0ec6eed5-b84c-480a-878b-f7477e1f5211--\r\n",
"files": {},
"form": {},
"headers": {
"Accept-Encoding": "gzip",
"Content-Length": "382",
"Content-Type": "multipart/mixed; boundary=0ec6eed5-b84c-480a-878b-f7477e1f5211",
"Host": "www.httpbin.org",
"User-Agent": "okhttp/4.9.0",
"X-Amzn-Trace-Id": "Root=1-62f46b94-286b12c151288d1a2b5b3c61"
},
"json": null,
"origin": "223.104.38.107",
"url": "https://www.httpbin.org/post"
}
2、上传 json 数据:
public void jsonTest(){
OkHttpClient okHttpClient = new OkHttpClient();
RequestBody requestBody = RequestBody.create("{\"a\":1,\"b\":2}", MediaType.parse("application/json"));
Request request = new Request.Builder()
.url("https://www.httpbin.org/post")
.post(requestBody)
.build();
Call call = okHttpClient.newCall(request);
Response response = call.execute();
System.out.println(response.body().string());
}
3、下载文件
public void download() throws IOException {
OkHttpClient okHttpClient = new OkHttpClient();
Request request = new Request.Builder()
.url("https://image.baidu.com/search/down?tn=download&word=download&ie=utf8&fr=detail&url=https%3A%2F%2Fgimg2.baidu.com%2Fimage_search%2Fsrc%3Dhttp%253A%252F%252Fc-ssl.duitang.com%252Fuploads%252Fitem%252F202105%252F29%252F20210529001057_aSeLB.thumb.1000_0.jpeg%26refer%3Dhttp%253A%252F%252Fc-ssl.duitang.com%26app%3D2002%26size%3Df9999%2C10000%26q%3Da80%26n%3D0%26g%3D0n%26fmt%3Dauto%3Fsec%3D1662861218%26t%3D08bd446ab8c926831d0f1eed9cf60c82&thumburl=https%3A%2F%2Fimg2.baidu.com%2Fit%2Fu%3D1994380678%2C3283034272%26fm%3D253%26fmt%3Dauto%26app%3D138%26f%3DJPEG%3Fw%3D500%26h%3D500")
.build();
Call call = okHttpClient.newCall(request);
Response response = call.execute();
InputStream inputStream = response.body().byteStream();
FileOutputStream fos = new FileOutputStream("E:\\666.jpg");
int len;
byte[] buff = new byte[4096];
while((len = inputStream.read(buff)) != -1){
fos.write(buff, 0, len);
}
fos.close();
inputStream.close();
}
三、OkHttp构建者的设置
提供了构建者模式来创建 OkHttpClient 对象:
OkHttpClient okHttpClient = new OkHttpClient.Builder().build();
向OkHttpClient增加自定义配置,这些自定义配置包括拦截器等
1、拦截器
添加拦截器的方式:
OkHttpClient okHttpClient = new OkHttpClient.Builder().addInterceptor(new XXX).build();
OkHttpClient okHttpClient = new OkHttpClient.Builder().addNetworkInterceptor(new XXX).build();
在同时添加 addInterception 拦截器和 addNetworkInterceptor 拦截器后,addInterception添加的拦截器中的内容会优先执行。
代码示例:
public class InterceptorUnitTest{
OkHttpClient okHttpClient = new OkHttpClient.Builder().addInterceptor(new Interceptor(){
@Override
public Response intercept(Chain chain) throws IOException{
//1. 不做任何处理
//return chain.proceed(chain.request()); //拦截器没有做任何操作,就只是过了一遍拦截器
//2. 自定义处理
//前置处理
Request request = chain.request().newBuilder().addHeader("os","android").addHeader("version", "1.0").build();
Response response = chain.proceed(request);
//后置处理
return response;
}
}).build();
Request request = new Request.Builder().url("https://www.httpbin.org/get?a=1&b=2").build();
//获得准备执行请求的Call对象
Call call = okHttpClient.newCall(request);
try{
//执行请求得到响应
Response response = call.execute();
Log.i(TAG, "getSync:" + response.body().string());
}catch(IOException e){
e.printStackTrace();
}
}
加入拦截器的作用:在请求过程中会执行一次拦截器的代码
2、缓存与Cookie
缓存:
OkHttp按照Http协议规则实现了缓存的处理,缓存是比如,当我们发起第一次请求之后,如果后续还需要进行同样的请求,此时如果符合缓存规则,则可以减少与服务器的网络通信,直接从本地文件缓存中读取响应返回给请求者。但是默认情况下,OkHttp的缓存是关闭状态,需要我们开启。
//pathname: 缓存文件地址
//maxSize:缓存最大容量字节
OkHttpClient okHttpClient = new OkHttpClient.Builder().cache(new Cache(new File(pathname), maxSize)).build();
Cookie:
Cookie是某些网站为了辨别用户身份,进行会话跟踪(比如确定登录状态、VIP身份)而存储在用户本地终端上的数据(通常经过加密)。
比如查看“收藏文章列表” 时,需要确定用户登录状态,这时用到Cookie
玩Android 里面有开放的API:
代码示例(使用Cookie):
public class CookieUnitTest{
Map<String,List<Cookie>> cookies = new HashMap<>();
public void cookieTest(){
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.cookieJar(new CookieJar(){
//saveFromResponse方法的作用是将服务器返回的数据回调给参数list
@Override
public void saveFromResponse(HttpUrl httpUrl, List<Cookie> list){
cookies.put(httpUrl.host(), list);
}
@Override
public List<Cookie> loadForRequest(HttpUrl httpUrl){
List<Cookie> cookies = CookieUnitTest.this.cookies.get(httpUrl.host());
return cookies == null ? new ArrayList<>() : cookies;
}
})
.build();
FormBody formBody = new FormBody.Builder().add("username", "lanceedu").add("password","123123").build();
Request request = new Request.Builder().url("https://www.wanandroid.com/user/login").post(formBody).build();
//获得准备执行请求的Call对象
Call call = okHttpClient.newCall(request);
try{
//执行请求得到响应
Response response = call.execute();
Log.i(TAG, "getSync:" + response.isSuccessful());
Log.i(TAG, "getSync:" + response.body().string());
}catch(IOException e){
e.printStackTrace();
}
//查看上述登录用户的文章收藏列表,如果没有设置Cookie,则查看不到
request = new Request.Builder().url("https://www.wanandroid.com/lg/collect/list/0/json").build();
//获得准备执行请求的Call对象 和上面登录使用的是同一个okHttpClient(设置了cookie)
call = okHttpClient.newCall(request);
try{
//执行请求得到响应
Response response = call.execute();
Log.i(TAG, "getSync:" + response.body().string());
}catch(IOException e){
e.printStackTrace();
}
}
}
}
不过,现在Cookie使用的也不是很多了,很多网络不是以Cookie来进行身份验证。