本文首发于微信公众号「后厂技术官」

相关文章
Android网络编程系列

前言

讲完了Volley,我们接下来看看目前比较火的网络框架OkHttp, 它处理了很多网络疑难杂症:会从很多常用的连接问题中自动恢复。如果您的服务器配置了多个IP地址,当第一个IP连接失败的时候,OkHttp会自动尝试下一个IP,此外OkHttp还处理了代理服务器问题和SSL握手失败问题。

1.使用前准备

eclipse引入jar包地址:
okhttp-2.7.5.jar
okio-1.7.0.jar

Android Studio 配置gradle:

compile 'com.squareup.okhttp:okhttp:2.7.5'
compile 'com.squareup.okio:okio:1.7.0'

2.异步GET请求

最简单的get请求,老规矩请求百度:

  private void getAsynHttp() {
//创建okHttpClient对象
OkHttpClient mOkHttpClient = new OkHttpClient();
final Request request = new Request.Builder()
.url("http://www.baidu.com")
.build();
Call call = mOkHttpClient.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Request request, IOException e) {
}
@Override
public void onResponse(final Response response) throws IOException {
String str = response.body().string();
Log.i("wangshu", str);
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(getApplication(), "请求成功", Toast.LENGTH_SHORT).show();
}
});
}
});
}

运行程序log打印出来的是百度首页的html文件,基本的步骤很简单,就是创建OkHttpClient、Request和Call,最后调用Call的enqueue()方法。但是每次这么写肯定是很麻烦,肯定是要进行封装的。需要注意的是onResponse回调并不是在UI线程。

3.同步GET请求

private String getSyncHttp() throws IOException{
OkHttpClient mOkHttpClient = new OkHttpClient();
//创建请求Request
final Request request = new Request.Builder()
.url("http://www.baidu.com")
.build();
Call call = mOkHttpClient.newCall(request);
Response mResponse=call.execute();
if (mResponse.isSuccessful()) {
return mResponse.body().string();
} else {
throw new IOException("Unexpected code " + mResponse);
}
}

同步Get请求和异步调用区别就是调用了call的execute()方法。

4.异步POST请求

private void postAsynHttp() {
OkHttpClient mOkHttpClient = new OkHttpClient();
RequestBody formBody = new FormEncodingBuilder()
.add("size", "10")
.build();
Request request = new Request.Builder()
.url("http://api.1-blog.com/biz/bizserver/article/list.do")
.post(formBody)
.build();
Call call = mOkHttpClient.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Request request, IOException e) {
}
@Override
public void onResponse(Response response) throws IOException {
String str = response.body().string();
Log.i("wangshu", str);
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(getApplicationContext(), "请求成功", Toast.LENGTH_SHORT).show();
}
});
}
});
}

post与get不同的就是要要创建RequestBody并传进Request中,同样onResponse回调不是在UI线程。

5.请求缓存设置

首先我们设置缓存路径和大小并设置给OkHttpClient:

mOkHttpClient = new OkHttpClient();
File sdcache = getExternalCacheDir();
int cacheSize = 10 * 1024 * 1024;
mOkHttpClient.setCache(new Cache(sdcache.getAbsoluteFile(), cacheSize));

接下来异步GET请求baidu:

private void getAsynHttp() {
//创建请求Request
final Request request = new Request.Builder()
.url("http://www.baidu.com")
.build();
Call call = mOkHttpClient.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Request request, IOException e) {
}
@Override
public void onResponse(final Response response) throws IOException {
if (null != response.cacheResponse()) {
String str = response.cacheResponse().toString();
Log.i("wangshu", "cache---" + str);
} else {
response.body().string();
String str=response.networkResponse().toString();
Log.i("wangshu", "network---" + str);
}
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(getApplicationContext(), "请求成功", Toast.LENGTH_SHORT).show();
}
});
}
});
}

第一次请求会请求网络得到数据,第二次以及后面的请求则会从缓存中取出数据:
VKJilD.png

当然也有种情况是有的请求每次都需要最新的数据,则在创建Request,来设置cacheControl为“CacheControl.FORCE_NETWORK”,用来表示请求会一直请求网络得到数据:

final Request request = new Request.Builder()
.url("http://www.baidu.com")
.cacheControl(CacheControl.FORCE_NETWORK)
.build();

运行程序结果为:

VKJPSO.png

6.设置超时时间

另外我们也需要设置超时的时间用来处理各种网络超时的情况,超时的原因可能是网络问题也可能是服务器响应慢等问题,OkHttp当然不会忽略这一点,它支持连接、读取和写入超时的时间设置:

mOkHttpClient = new OkHttpClient();
mOkHttpClient.setConnectTimeout(15, TimeUnit.SECONDS);
mOkHttpClient.setWriteTimeout(20, TimeUnit.SECONDS);
mOkHttpClient.setReadTimeout(20, TimeUnit.SECONDS);

7.取消请求

使用call.cancel()可以立即停止掉一个正在执行的call。如果一个线程正在写请求或者读响应,将会引发IOException。当用户离开一个应用时或者跳到其他界面时,使用Call.cancel()可以节约网络资源,另外不管同步还是异步的call都可以取消。
也可以通过tags来同时取消多个请求。当你构建一请求时,使用RequestBuilder.tag(tag)来分配一个标签。之后你就可以用OkHttpClient.cancel(tag)来取消所有带有这个tag的call。

为了模拟这个场景我们首先创建一个定时的线程池:

private ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);

接下来的代码为:

private  void cancel(){
final Request request = new Request.Builder()
.url("http://www.baidu.com")
.cacheControl(CacheControl.FORCE_NETWORK)
.build();
Call call=null;
call = mOkHttpClient.newCall(request);
final Call finalCall = call;
//100毫秒后取消call
executor.schedule(new Runnable() {
@Override public void run() {
finalCall.cancel();
}
}, 100, TimeUnit.MILLISECONDS);
call.enqueue(new Callback() {
@Override
public void onFailure(Request request, IOException e) {
}
@Override
public void onResponse(final Response response) {
if (null != response.cacheResponse()) {
String str = response.cacheResponse().toString();
Log.i("wangshu", "cache---" + str);
} else {
try {
response.body().string();
} catch (IOException e) {
Log.i("wangshu", "IOException");
e.printStackTrace();
}
String str = response.networkResponse().toString();
Log.i("wangshu", "network---" + str);
}
}
});
Log.i("wangshu", "是否取消成功"+call.isCanceled());
}

100毫秒后调用call.cancel(),为了能让请求耗时,我们设置每次请求都要请求网络,运行程序并且不断的快速点击发送请求按钮:
VKJpY6.png

很明显每次cancel()都失败了,仍旧成功的访问了网络,在cancel()时已经有读写操作了所以会报IOException。每隔100毫秒来调用call.cancel()显然时间间隔太长,我们设置为1毫秒并不断的快速的点击发送请求按钮:
VKJ9fK.png

没有请求网络的log,几乎每次都取消成功了。

8.关于封装

如果每次请求网络都需要写重复的代码绝对是令人头疼的,网上也有很多对OkHttp封装的优秀开源项目,功能也非常强大,封装的意义就在于更加方便的使用,具有拓展性,但是对OkHttp封装最需要解决的是以下的两点:

  1. 避免重复代码调用
  2. 将请求结果回调改为UI线程

根据以上两点,我们也简单封装一下,在此只是举个例子,如果想要使用OkHttp封装的开源库,推荐使用OkHttpFinal
首先呢我们写一个抽象类用于请求回调:

public abstract class ResultCallback<T>
{
public abstract void onError(Request request, Exception e);
public abstract void onResponse(Response response);
}

接下来封装OkHttp,并实现了异步GET请求:

public class OkHttpEngine {
private volatile static OkHttpEngine mInstance;
private OkHttpClient mOkHttpClient;
private Handler mHandler;
public static OkHttpEngine getInstance() {
if (mInstance == null) {
synchronized (OkHttpEngine.class) {
if (mInstance == null) {
mInstance = new OkHttpEngine();
}
}
}
return mInstance;
}
private OkHttpEngine() {
mOkHttpClient = new OkHttpClient();
mOkHttpClient.setConnectTimeout(15, TimeUnit.SECONDS);
mOkHttpClient.setWriteTimeout(20, TimeUnit.SECONDS);
mOkHttpClient.setReadTimeout(20, TimeUnit.SECONDS);
mHandler = new Handler();
}
public OkHttpEngine setCache(Context mContext) {
File sdcache = mContext.getExternalCacheDir();
int cacheSize = 10 * 1024 * 1024;
mOkHttpClient.setCache(new Cache(sdcache.getAbsoluteFile(), cacheSize));
return mInstance;
}
/**
* 异步get请求
* @param url
* @param callback
*/
public void getAsynHttp(String url, ResultCallback callback) {
final Request request = new Request.Builder()
.url(url)
.build();
Call call = mOkHttpClient.newCall(request);
dealResult(call, callback);
}
private void dealResult(Call call, final ResultCallback callback) {
call.enqueue(new Callback() {
@Override
public void onFailure(Request request, IOException e) {
sendFailedCallback(request, e, callback);
}
@Override
public void onResponse(final Response response) throws IOException {
sendSuccessCallback(response, callback);
}
private void sendSuccessCallback(final Response object, final ResultCallback callback) {
mHandler.post(new Runnable() {
@Override
public void run() {
if (callback != null) {
callback.onResponse(object);
}
}
});
}
private void sendFailedCallback(final Request request, final Exception e, final ResultCallback callback) {
mHandler.post(new Runnable() {
@Override
public void run() {
if (callback != null)
callback.onError(request, e);
}
});
}
});
}
}

原理很简单就是,写一个双重检查模式的单例,不了解双重检查模式的请查看设计模式之单例模式的七种写法这篇文章。在开始创建的时候配置好OkHttpClient,在请求网络的时候用Handler将请求的结果回调给UI线程。

最后调用这个OkHttpEngine的getAsynHttp()方法:

OkHttpEngine.getInstance().getAsynHttp("http://www.baidu.com", new ResultCallback() {
@Override
public void onError(Request request, Exception e) {
}
@Override
public void onResponse(Response response) {
String str = response.networkResponse().toString();
Log.i("wangshu", str);
Toast.makeText(getApplicationContext(), "请求成功", Toast.LENGTH_SHORT).show();
}
});

使用起来简单多了,而且请求结果回调是在UI线程的。下一篇我们会讲到OkHttp3,来看看它与OkHttp2.x之间的使用方式上有什么区别。

github源码下载