okHttp 基础及封装
@(Android 学习)[okhttp, 网络]
[TOC]
Google 在 6.0 版本里面删除了 HttpClient 相关 API,OkHttp 处理了很多网络疑难杂症:会从很多常用的连接问题中自动恢复。
OkHttp 官网
OkHttp 官方 API 文档
OkHttp GitHub 地址
1. 添加依赖
compile 'com.squareup.okhttp:okhttp:2.4.0'
因为 okhttp 内部依赖 okio,因此还需要导入 okio:
compile 'com.squareup.okio:okio:1.5.0'
2. 使用教程
2.1 Http Get
在网络请求中,最常见的就是 http get 请求,具体用法如下:
1. 首先构造一个 Request 对象,参数最起码要有个 url,也可以通过 Request.Builder 设置更多的参数,比如:header
、method
等;
2. 通过 request 的对象构造一个 Call 对象,类似于将请求封装成任务;
3. 最后,以异步的方式去执行请求,调用call.enqueue,将 call 加入调度队列,然后等待任务执行完成,在 Callback 中即可得到结果。
// 创建 OKHttpClient 对象
OkHttpClient mOkHttpClient = new OkHttpClient();
// 创建一个 Request
final Request request = new Request.Builder().url("https://github.com/hongyangAndroid").build();
// new call
Call mCall = mOkHttpClient.newCall(request);
// 请求加入调度
mCall.enqueue(new Callback() {
@Override
public void onFailure(Request request, IOException e) {
}
@Override
public void onResponse(Response response) throws IOException {
// String htmlStr = response.body().string();
}
});
onResponse 回调的参数是 response,一般情况下,如果希望获得返回的字符串,可以通过 response.body().string() 获取;如果希望获得返回的二进制字节数组,则调用 response.body().bytes();如果希望获得返回的 inputStream,则调用 response.body().byteStream()。
可以看到能够拿到返回的 inputStream,表明支持大文件下载,有 inputStream 就可以通过 IO 的方式写文件。这也说明一个问题,onResponse 执行的线程并不是 UI 线程,如果希望操作控件,就需要使用 handler,如:
Override
public void onResponse(Response response) throws IOException {
final String res = response.body().string();
runOnUiThread(new Runnable() {
@Override
public void run() {
mTv.setText(res);
}
});
}
上面的是用异步的方式去执行,也支持阻塞的方式,就是调用 Call.execute() 方法,也同样返回一个 Response。
2.2 Http Post 携带参数
Post 方法中,整体用法跟 Get 方法相似,只是 Request 的构造不同。Post 的参数是包含在请求体中的,所以通过 FormEncodingBuilder 添加多个 String 键值对,然后构造 RequestBody,完成 Request 的构造:
FormEncodingBuilder builder = new FormEncodingBuilder();
builder.add("username", "Savage_Lin");
Request request = new Request.Builder().url(url).post(builder.build()).build();
mOkHttpClient.newCall(request).enqueue(new Callback() {});
2.3 基于 Http 的文件上传
当需要类似表单上传的时候,可以使用 MultipartBuilder 来构造 RequestBody 实现。
File file = new File(Environment.getExternalStorageDirectory(), "balabala.mp4");
RequestBody fileBody = RequestBody.create(MediaType.parse("application/octet-stream"),
file);
RequestBody requestBody = new MultipartBuilder().type(MultipartBuilder.FORM).addPart
(Headers.of("Content-Disposition", "form-data: name \"username\""), RequestBody
.create(null, "Savage-Lin")).addPart(Headers.of("Content-Disposition",
"form-data: name=\"mFile\"; filename=\"wjd.mp4\""), fileBody).build();
Request request = new Request.Builder().url("server url").post(requestBody).build();
Call call = mOkHttpClient.newCall(request);
call.enqueue(new Callback() {
// ...
});
上述代码向服务器传递了一个键值对 username:Savage-Lin
和一个文件。通过 MultipartBuilder 的 addPart 方法添加键值对或文件。
这里类似于凭借模拟浏览器行为的方式,详细可参考HTTP 网络请求原理。
图片下载是通过回调的 Response 拿到 byte[] 然后 decode 成图片;文件下载是拿到 inputStream 做写文件操作,关于用法,可以参考泡网OkHttp使用教程。
3. 对基本请求的封装
因为 okhttp 中每次网络请求方式基本相似,因此对其进行一次封装,方便后续的开发与维护,框架类图如下:
其中,BasicHttp 类封装了基本的网络请求参数以及网络请求抽象方法,LSHttp 类通过 SubAPI 类中传递过来的参数,实现了具体的网络请求方法,发起具体的网络请求;BasicAPI 类封装发起网络请求所需的参数以及网络请求回调方法,LSAPI 类相当于 API 类的中间类,封装了同一类业务中相同的请求参数以及请求操作,具体的参数设置以及具体的回调方法在 SubAPI 类中实现。
3.1 BasicHttp 类
BasicHttp 类是一个抽象类,封装了网络请求所需的基本参数及配置,代码如下:
public abstract class BasicHttp implements Callback {
public BasicAPI basicAPI;
public String url;
public Headers headers;
public RequestBody requestBody;
// 创建一个 OkHttp 的请求对象
public Request request;
public Request.Builder requestBuilder = new Request.Builder();
public BasicHttp(BasicAPI basicAPI) {
this.basicAPI = basicAPI;
}
/**
* 配置请求 URL 地址
*/
public abstract void setURL();
/**
* 添加请求头部
*/
public abstract void setHeaders();
/**
* 添加请求参数
*/
public abstract void setParams();
/**
* 配置 Request
*/
public abstract void setRequest();
public void request() {
setURL();
setHeaders();
setRequest();
// 将请求加入请求队列
App.getOkHttpClientInstance().newCall(request).enqueue(this);
}
}
OkHttp官方文档并不建议我们创建多个 OkHttpClient,因此全局使用一个(如果有需要,可以使用 clone 方法,再进行自定义),在 App 类中实例化后调用,同时实现 Callback 接口用于处理请求回调。
LSHttp 类继承了 BasicHttp 类,实现了对 Request 参数的设置。代码如下:
public class LSHttp extends BasicHttp {
private LSAPI api;
// 创建一个 FormEncodingBuilder 对象,用于存放表单中的 item
public FormEncodingBuilder bodyBuilder = new FormEncodingBuilder();
public LSHttp(LSAPI api) {
super(api);
this.api = api;
}
@Override
public void setURL() {
if (api.getMethod() == RequestMethod.GET) {
// if method is GET
String param = "?";
try {
for (Map.Entry<String, String> entry :
api.getParams().entrySet()) {
param += entry.getKey() + "=" + entry.getValue() + "&";
}
url = api.getUrl() + param;
} catch (Exception e) {
url = api.getUrl();
}
} else {
// if method is OTHER
url = api.getUrl();
}
}
@Override
public void setHeaders() {
headers = Headers.of(api.getHeaders());
}
@Override
public void setParams() {
for (Map.Entry<String, String> entry : api.getParams().entrySet()) {
bodyBuilder.add(entry.getKey(), entry.getValue());
}
requestBody = bodyBuilder.build();
}
@Override
public void setRequest() {
if (api.getMethod().equals(RequestMethod.GET)) {
requestBody = null;
} else {
setParams();
}
String method = api.getMethod().getMethod();
request = requestBuilder.url(url).headers(headers).method(method, requestBody).build();
}
@Override
public void onFailure(Request request, IOException e) {
api.requestFailure(600L, "服务繁忙,请稍后再试");
}
@Override
public void onResponse(Response response) throws IOException {
JSONObject jsonResponse;
if (response == null) {
api.requestFailure(600L, "response null");
return;
} else {
try {
jsonResponse = new JSONObject(response.body().string());
} catch (JSONException e) {
e.printStackTrace();
jsonResponse = new JSONObject();
}
}
// 判断服务器是否返回请求数据
boolean status = false;
long code = -1;
String msg = null;
try {
if (jsonResponse.has("status") && jsonResponse.has("code")) {
status = jsonResponse.getBoolean("status");
code = jsonResponse.getLong("code");
if (jsonResponse.has("msg")) {
msg = jsonResponse.getString("msg");
}
}
} catch (JSONException e) {
e.printStackTrace();
}
if (200L == code && status) {
// 请求成功
JSONObject data;
try {
data = jsonResponse.getJSONObject("data");
api.requestSuccess(data, msg);
} catch (JSONException e) {
e.printStackTrace();
}
} else {
// 请求失败
api.requestFailure(600L, "request error");
}
}
}
若是 GET 请求,则将参数拼接到 URL 中,同时将 requestBody 赋值为 null,若是其他请求,则将参数封装到 requestBody 中。同时对请求结果进行初次处理,若请求成功,且服务器返回的数据为 JSON 类型,如下:
<!--服务器返回的数据如下:-->
{"status":true,"code":200,"data":{"result":{"method":"put","param":"good!"}}}
解析出 data 中的数据,交由 API 类处理。不解析到 result 字段是因为服务器返回来的数据可能是一个 JSONObject 对象,也有可能是一个 JSONArray 数据,还有可能就是一个字段,因此解析到 data。
BasicAPI 类和 LSAPI 类封装了 Request 中所需参数的 getter/setter 方法以及对返回数据的处理抽象方法。具体的业务 API 类只需实现 getUrl()、getMethod()、success()、error()
方法即可,如下:
public class HelloAPI extends LSAPI {
public HelloAPI() {
addParam("param", "hello");
new LSHttp(this).request();
}
@Override
public String getUrl() {
return "http://api.6shangwang.com/region/app/v2/region/orgin";
}
@Override
public void success(JSONObject data, String msg) throws Exception {
Log.e("HelloAPI success", data.toString());
}
@Override
public void error(Long code, String msg) {
Log.e("HelloAPI error", msg);
}
@Override
public RequestMethod getMethod() {
return RequestMethod.POST;
}
}
在需要发送该网络请求时,只需一行代码即可发送该网络请求:
new HelloAPI();
4. 对文件上传/下载的封装
以上,即是对 OkHttp 框架的简单介绍以及 Android 整合 OkHttp 的网络框架。