使用OkHttp进行网络请求操作

介绍

OkHttp是Square为发送、接收基于HTTP的网络请求而开发的一个第三方类库。它是基于Okio类库开发的,Okio试图通过创建一个共享内存池来让数据的读写操作比Java的I/O库更加效率。OkHttp还作为Retrofit的基础库为其使用基于REST的API而提供类型安全。

发送及接收网络请求

首先,我们需要实例化一个OkHttpClient对象和一个Request对象。

1
2
3
4
5
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url("https://api.github.com/users/bovink/repos")
.build();

如果需要添加查询参数,可以使用HttpUrl类进行配置。

1
2
3
4
5
6
7
8
9
HttpUrl.Builder urlBuilder = HttpUrl.parse("https://api.github.com/users/bovink/repos").newBuilder();
urlBuilder.addQueryParameter("page", "1");
urlBuilder.addQueryParameter("per_page", "5");
String url = urlBuilder.build().toString();
Request request = new Request.Builder()
.url(url)
.build();

同步网络调用

我们可以创建一个Call对象来同步分发网络请求。

1
Response response = client.newCall(request).execute();

由于Android不允许在主线程进行网络调用,你可以创建一个单独的线程或一个后台服务来处理网络请求。也可以使用AsyncTask来处理轻量级的网络请求。

异步网络调用

我们还可以通过创建一个Call对象用enquene()方法来进行异步调用,并生成一个实现了onFaulure()onResponse()的匿名Callback对象。

1
2
3
4
5
6
7
8
9
10
11
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, final Response response) throws IOException {
}
}

OkHttp创建了一个工作线程来分发网络请求,并使用同一个的线程来处理响应报文。这个过程主要基于Java类库,因此并没有处理Android框架关于只能在主线程更新UI的限制。所以如果你想更新视图,你可以使用runOnUiThread()方法或者将结果传递给主线程。

1
2
3
4
5
6
7
8
9
10
11
client.newCall(request).enqueue(new Callback() {
@Override
public void onResponse(Call call, Response response) throws IOException {
MainActivity.this.runOnUiThread(new Runnable() {
@Override
public void run() {
}
}
}
});

解析响应报文

假设请求没有被取消并且没有联网问题,onResponse()方法将会触发。这个方法传递了一个Response对象,可以用来查看状态码,响应正文,和返回的响应头。调用isSuccessful()判断返回的状态码是否是2XX系列。

1
2
3
if (!response.isSuccessful()) {
throw new IOException("Unexpected code " + response);
}

响应头同时作为list返回:

1
2
3
4
Headers responseHeaders = response.headers();
for (int i = 0; i < responseHeaders.size(); i++) {
Log.d("DEBUG", responseHeaders.name(i) + ": " + responseHeaders.value(i));
}

也可以直接用response.header()访问响应头:

1
String header = response.header("Date");

我们还可以通过response.body()得到响应正文,然后调用string()来读取加载的数据。注意,response.body()只能运行一次并且只能在后台线程运行。

解析JSON数据

我们调用GitHub的API进行测试,它返回的数据是基于JSON的数据。

1
2
3
Request request = new Request.Builder()
.url("https://api.github.com/users/bovink/repos")
.build();

根据返回的数据格式,可以将其转换成JSONObject或者JSONArray

1
2
3
4
5
6
7
8
9
10
11
client.newCall(request).enqueue(new Callback() {
@Override
public void onResponse(Call call, final Response response) throws IOException {
try {
String responseData = response.body().string();
JSONArray json = new JSONArray(responseData);
} catch (JSONException e) {
}
}
});

用Gson解析JSON数据

注意string()方法会将所有数据全部加载到内存中。为了更有效的使用内存,我们建议用charStream()解析数据。这种方法要求使用Gson类库。

为了使用Gson类库,我们必须先声明一个类来映射JSON数据。

1
2
3
4
5
6
7
8
9
public class Repo {
private String id;
private String name;
private String full_name;
...
...
...
}

然后我们可以直接用Gson解析器将数据转换成Java模型

1
2
3
4
5
6
7
8
9
10
final Gson gson = new Gson();
client.newCall(request).enqueue(new Callback() {
@Override
public void onResponse(Call call, final Response response) throws IOException {
List<Repo> repos = gson.fromJson(response.body().charStream(), new TypeToken<List<Repo>>() {
}.getType());
}
}
这是一个简单的Demo

参考文献