扫盲系列 - OkHttp 基本用法
简介
- 是一个网络框架,可以用于 Android 和 Java 应用程序。
- 支持Android 2.3以上。对于 Java ,最低要求是1.7。当然我们主要说的是 Android 。
我们知道 Android 系统提供了两种 HTTP 通信类, HttpURLConnection 和 Apach HttpClient 。但是这个 Apach HttpClient 在 Android API 23 ,也就是Android6.0上已经废弃。而 HttpURLConnection 又实在太难用,于是 OkHttp 就应运而生。
它是一个处理 Android 网络请求的项目,是 Android 端最火热的项目之一,由 Square 公司贡献,该公司还贡献了(Picasso,以及 Retrofit 等等)。
Square 出品,必属精品。
OkHttp 处理了很多网络疑难杂症:
- 会从很多常用的连接问题中自动恢复。
- 如果您的服务器配置了多个 IP 地址,当第一个 IP 连接失败的时候, OkHttp 会自动尝试下一个 IP 。
- OkHttp还处理了代理服务器问题和 SSL 握手失败问题。
使用 OkHttp 无需重写您程序中的网络代码。 OkHttp 实现了几乎和java.net.HttpURLConnection一样的 API 。如果你用了 Apache HttpClient ,则 OkHttp 也提供了一个对应的okhttp-apache 模块。
优势
- 允许连接到统一主机上所有的请求,提高请求效率
- 共享 Socket ,减小对服务器的访问次数
- 通过连接池,减小了请求延迟
- 缓存网络数据,用来减少重复网络请求
- 减少了对数据流量的消耗
- 自动处理 Gzip 压缩
- 支持同步调用和带回调的异步调用。
功能
- post, get 请求
- 文件的上传和下载
- 加载图片(内部会自动压缩图片大小)
- 支持请求回调,直接返回对象,对象集合
- 支持 Session 保持
设计思路
注意:OkHttp官方文档并不建议我们创建多个 OkHttpClient ,全局使用一个即可。即单例模式创建 OkHttpClient 对象
使用方法:
同步的 get 请求
private void getDataSycn() {
new Thread() {
@Override
public void run() {
try {
OkHttpClient okHttpClient = new OkHttpClient();//创建 OkHttpClient 对象
Request request = new Request.Builder()//Builder是辅助类
.url("http://www.baidu.com")//请求接口,如果需要传递参数,拼接到后面
.build();//创建 Request 对象
Response response = null;//Response即 OkHttp 中的响应
response = okHttpClient.newCall(request).execute();
if(response.isSuccessful()){//请求成功
Log.e("hoyouly", "currentThread: "+Thread.currentThread()
+" \n code: "+response.code()+" \n body: "+response.body().string()
+" \n message: "+response.message());
}
} catch (IOException e) {
e.printStackTrace();
}
}
}.start();
}
结果:
message: OK
currentThread: Thread[Thread-914, 5 ,main]
code: 200
body: <!DOCTYPE html> 后面是百度的网页文本
message: OK
注意:
- response.body().string()本质是输入流的读操作,所以它还是网络请求的一部分,所以这行代码必须放在子线程
- Response.code是 http 响应行中的 code ,如果访问成功则返回200.这个不是服务器设置的,而是 http 协议中自带的。response.body() 中的 code 才是服务器设置的。(但是我却没打印出来)注意二者的区别。
- response.body().string()只能调用一次,在第一次时有返回值,第二次再调用时将会返回 null 。原因是:response.body().string()的本质是输入流的读操作,必须有服务器的输出流的写操作时客户端的读操作才能得到数据。而服务器的写操作只执行一次,所以客户端的读操作也只能执行一次,第二次将返回 null 。 (没搞明白)
- 请求后的结果是在一个新线程里面,如果想要更新 UI ,需要把结果传递到 UI 线程里面,可以使用Handler.
当然最方便的办法使用 异步 get 请求
异步 get 请求
private void getDataAsync() {
OkHttpClient okHttpClient = new OkHttpClient();//创建 OkHttpClient 对象
Request request = new Request.Builder()//
.url("http://www.baidu.com")//请求接口,如果需要传递参数,拼接到后面
.build();//创建 Request 对象
okHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
if (response.isSuccessful()) {//请求成功
Log.e("hoyouly", "onResponse currentThread: " + Thread.currentThread() //
+ " \n code: " + response.code() //
+ " \n body: " + response.body().string()//
+ " \n message: " + response.message());
}
}
});
}
得到的结果:
onCreate: current thread :Thread[main, 5 ,main]
onResponse currentThread: Thread[OkHttp http://www.baidu.com/..., 5 ,main]
code: 200
body: <!DOCTYPE html>
message: OK
注意: 通过 log 我们可以看出来,回调接口的onResponse(其实也包括 onFailure 方法)执行在子线程。而不是在 main 线程中,(onCreat() 执行在 main() 方法中,)
异步请求的打印结果与同步请求时相同。最大的不同点就是异步请求不需要开启子线程,另外使用方式上也有所不同:
- 同步请求需要创建一个 Response 对象,用来保存请求后的结果,并且最后执行的是 execute() 方法,如果想取消,可以使用 cancle() ,但是已完成的请求是不可以取消的。
- 异步请求不需要创建 Response 对象,只需要传递一个 Callback ,用来处理回调结果就可以了。最后使用的是 enqueue 方法会自动将网络请求部分放入子线程中执行。
异步 post 请求
post 也分同步和异步,同步也 get 方法类似,所以只讲异步 post 请求
private void postDataAsync() {
OkHttpClient okHttpClient=new OkHttpClient();
FormBody.Builder formBody=new FormBody.Builder();//创建表单请求体
formBody.add("username","张三");//设置键值对传递参数
Request request=new Request.Builder()//创建 Request 对象
.url(BAIDU_URL)//设置URL
.post(formBody.build())//传递请求体
.build();
okHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
}
});
}
post 请求需要设置请求方法,并且需要添加请求体,如 .post(formBody.build()),
在 OkHttp 中,默认的是 get 方式,所以 get 方式不需要设置。
post 请求传递参数
查看源码可知,只要是 RequestBody 类以及子类都可以作为 post 方法的参数
public Builder post(RequestBody body) {
return method("POST", body);
}
FormBody:传递键值对参数
这种方式用来上传 String 类型的键值对,例如
FormBody.Builder formBody = new FormBody.Builder();//创建表单请求体
formBody.add("username","zhangsan");//传递键值对参数
RequestBody:传递 Json 或 File 对象
RequestBody 是一个抽象类,不能直接 new ,但是它有静态方法 creat() ,使用这种方式可以得到 RequestBody 对象,这种方式可以上传 JSON 对象或者 File 对象
上传 JSON 对象
OkHttpClient okHttpClient=new OkHttpClient();
MediaType mediaType=new MediaType("application/json; charset=utf-8");//数据格式为json
String jsonStr = "{\"username\":\"lisi\",\"nickname\":\"李四\"}";//json数据.
RequestBody requestBody=RequestBody.create(mediaType,jsonStr);
Request request=new Request.Builder()//创建 Request 对象
.url(BAIDU_URL)//设置URL
.post(requestBody)//传递请求体
.build();
okHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
}
});
MediaType 用于描述 HTTP 请求和响应体的内容类型,也就是Content-Type。
RequestBody 的数据格式都要指定Content-Type,常见的有三种:
- application/x-www-form-urlencoded 数据是个普通表单
- multipart/form-data 数据里有文件
- application/json 数据是个json
上边的普通表单并没有指定Content-Type,这是因为 FormBody 继承了 RequestBody ,它已经指定了数据类型为application/x-www-form-urlencoded。
上传 File 对象
MediaType mediaType1=MediaType.parse("File/*");//数据类型为文件类型
File file=new File("文件路径");
RequestBody requestBody1=RequestBody.create(mediaType1,file);
MultipartBody: 同时上传键值对和 File 对象
Multipart 意思就是多重的,如果既想传递键值对,又想传递 File 对象,那么就使用 MultipartBody
MultipartBody body=new MultipartBody.Builder()//
.setType(MultipartBody.FORM)//
.addFormDataPart("groupId",""+groudId)////添加键值对参数
.addFormDataPart("title","title")//
.addFormDataPart("file", file.getName(),RequestBody.create(MediaType.parse("file/*"),file))//添加文件
.build();
final Request request = new Request.Builder()//
.url(BAIDU_URL)//
.post(body)//
.build();
okHttpClient.newCall(request).enqueue(new Callback() {...});
自定义RequestBody: 流的上传
自定义流上传的RequestBody
RequestBody uploadBody=new RequestBody() {
@Override
public MediaType contentType() {
return null;
}
@Override
public void writeTo(BufferedSink sink) throws IOException {
FileInputStream fio=new FileInputStream(new File("文件名字"));
byte[] buffer=new byte[];
if(fio.read(buffer)!=-1){
sink.write(buffer);
}
}
};
这个 RequestBody 对象重写了 write 方法,里面有个 sink 对象。这个是 OKio 包中的输出流,有 write 方法。使用这个方法我们可以实现上传流的功能。
- 如果想要实现断点续传的功能,可以结合 RandomAccessFile 类实现断点续传的功能。
- 如果想得到 OutputStream 对象 ,可以使用 BufferedSink 的 outputStream() 方法
然后使用
OkHttpClient client = new OkHttpClient();//创建 OkHttpClient 对象。
Request request = new Request.Builder()
.url("http://www.baidu.com")
.post(uploadBody)
.build();
client.newCall(request).enqueue(new Callback() {...});
设置请求头
设置请求头很简单,只需要在创建 Request 对象的时候,调用相应的方法即可
Request request = new Request.Builder()
.url("http://www.baidu.com")//
.header("User-Agent", "OkHttp Headers.java")//
.addHeader("token", "myToken")//
.build();
OkHttp 的处理方式是:
- 使用header(name,value)来设置 HTTP 头的唯一值,如果请求中已经存在响应的信息那么直接替换掉。
- 使用addHeader(name,value)来补充新值,如果请求头中已经存在 name 的name-value,那么还会继续添加,请求头中便会存在多个 name 相同而 value 不同的“键值对”。
- 使用header(name)读取唯一值或多个值的最后一个值
- 使用headers(name)获取所有值
下载文件
OkHttp 中并没有提供下载文件的功能,但是在 Response 中,可以获得流对象,有了流对象,我们就可以通过 IO 的方式自己实现下载文件的功能: 下面这段代码就是在 CallBack 中的 onResponse() 中的:
InputStream inputStream=response.body().byteStream();//从服务器得到输入流对象
long sum=0;
File dir=new File("文件夹路径");
if(!dir.exists()){
dir.mkdirs();
}
File file=new File(dir,"文件名");//根据目录和文件名得到 file 对象
FileOutputStream fos=new FileOutputStream(file);
byte[] bytes=new byte[1024*8];
int len=0;
while ((len=inputStream.read(bytes))!=-1){
fos.write(bytes);
}
fos.flush();
return file;
缓存控制
缓存控制主要是在创建 Request 对象后,调用 cacheContorl() 方法来实现的
强制不缓存,
- 关键字:noCache()
Request request = new Request.Builder()
.cacheControl(new CacheControl.Builder().noCache().build())
.url("http://publicobject.com/helloworld.txt")
.build();
缓存策略由服务器指定
- 关键字:maxAge(0, TimeUnit.SECONDS)
Request request = new Request.Builder()//
.cacheControl(new CacheControl.Builder().maxAge(0,TimeUnit.SECONDS).build())//
.url("http://publicobject.com/helloworld.txt")//
.build();
强制缓存
- 关键字:onlyIfCached()
Request request = new Request.Builder()//
.cacheControl(new CacheControl.Builder().onlyIfCached().build())//
.url("http://publicobject.com/helloworld.txt")//
.build();
Response forceCacheResponse = client.newCall(request).execute();
if (forceCacheResponse.code() != 504) {
// The resource was cached! Show it.
} else {
// The resource was not cached.
}
允许使用旧的缓存
- 关键字:maxStale(365, TimeUnit.DAYS)
Request request = new Request.Builder()//
.cacheControl(new CacheControl.Builder().maxStale(365, TimeUnit.DAYS).build())//
.url("http://publicobject.com/helloworld.txt")//
.build();
暂时完结。。。
搬运地址:
既已览卷至此,何不品评一二: