前言
講完了Volley,我們接下來看看目前比較火的網(wǎng)絡(luò)框架OkHttp践樱, 它處理了很多網(wǎng)絡(luò)疑難雜癥:會(huì)從很多常用的連接問題中自動(dòng)恢復(fù)踩萎。如果您的服務(wù)器配置了多個(gè)IP地址,當(dāng)?shù)谝粋€(gè)IP連接失敗的時(shí)候登夫,OkHttp會(huì)自動(dòng)嘗試下一個(gè)IP广匙,此外OkHttp還處理了代理服務(wù)器問題和SSL握手失敗問題。
1.使用前準(zhǔn)備
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請(qǐng)求
最簡(jiǎn)單的get請(qǐng)求恼策,老規(guī)矩請(qǐng)求百度:
private void getAsynHttp() {
//創(chuàng)建okHttpClient對(duì)象
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(), "請(qǐng)求成功", Toast.LENGTH_SHORT).show();
}
});
}
});
}
運(yùn)行程序log打印出來的是百度首頁的html文件鸦致,基本的步驟很簡(jiǎn)單,就是創(chuàng)建OkHttpClient涣楷、Request和Call分唾,最后調(diào)用Call的enqueue()方法。但是每次這么寫肯定是很麻煩狮斗,肯定是要進(jìn)行封裝的绽乔。需要注意的是onResponse回調(diào)并不是在UI線程。
3.同步GET請(qǐng)求
private String getSyncHttp() throws IOException{
OkHttpClient mOkHttpClient = new OkHttpClient();
//創(chuàng)建請(qǐng)求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請(qǐng)求和異步調(diào)用區(qū)別就是調(diào)用了call的execute()方法碳褒。
4.異步POST請(qǐng)求
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(), "請(qǐng)求成功", Toast.LENGTH_SHORT).show();
}
});
}
});
}
post與get不同的就是要要?jiǎng)?chuàng)建RequestBody并傳進(jìn)Request中折砸,同樣onResponse回調(diào)不是在UI線程看疗。
5.請(qǐng)求緩存設(shè)置
首先我們?cè)O(shè)置緩存路徑和大小并設(shè)置給OkHttpClient:
mOkHttpClient = new OkHttpClient();
File sdcache = getExternalCacheDir();
int cacheSize = 10 * 1024 * 1024;
mOkHttpClient.setCache(new Cache(sdcache.getAbsoluteFile(), cacheSize));
接下來異步GET請(qǐng)求baidu:
private void getAsynHttp() {
//創(chuàng)建請(qǐng)求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(), "請(qǐng)求成功", Toast.LENGTH_SHORT).show();
}
});
}
});
}
第一次請(qǐng)求會(huì)請(qǐng)求網(wǎng)絡(luò)得到數(shù)據(jù),第二次以及后面的請(qǐng)求則會(huì)從緩存中取出數(shù)據(jù):
當(dāng)然也有種情況是有的請(qǐng)求每次都需要最新的數(shù)據(jù)鞍爱,則在創(chuàng)建Request鹃觉,來設(shè)置cacheControl為“CacheControl.FORCE_NETWORK”挪蹭,用來表示請(qǐng)求會(huì)一直請(qǐng)求網(wǎng)絡(luò)得到數(shù)據(jù):
final Request request = new Request.Builder()
.url("http://www.baidu.com")
.cacheControl(CacheControl.FORCE_NETWORK)
.build();
運(yùn)行程序結(jié)果為:
6.設(shè)置超時(shí)時(shí)間
另外我們也需要設(shè)置超時(shí)的時(shí)間用來處理各種網(wǎng)絡(luò)超時(shí)的情況择葡,超時(shí)的原因可能是網(wǎng)絡(luò)問題也可能是服務(wù)器響應(yīng)慢等問題凰锡,OkHttp當(dāng)然不會(huì)忽略這一點(diǎn)蚜退,它支持連接降淮、讀取和寫入超時(shí)的時(shí)間設(shè)置:
mOkHttpClient = new OkHttpClient();
mOkHttpClient.setConnectTimeout(15, TimeUnit.SECONDS);
mOkHttpClient.setWriteTimeout(20, TimeUnit.SECONDS);
mOkHttpClient.setReadTimeout(20, TimeUnit.SECONDS);
7.取消請(qǐng)求
使用call.cancel()可以立即停止掉一個(gè)正在執(zhí)行的call有鹿。如果一個(gè)線程正在寫請(qǐng)求或者讀響應(yīng)摔握,將會(huì)引發(fā)IOException糕韧。當(dāng)用戶離開一個(gè)應(yīng)用時(shí)或者跳到其他界面時(shí)翼闹,使用Call.cancel()可以節(jié)約網(wǎng)絡(luò)資源斑鼻,另外不管同步還是異步的call都可以取消。
也可以通過tags來同時(shí)取消多個(gè)請(qǐng)求猎荠。當(dāng)你構(gòu)建一請(qǐng)求時(shí)坚弱,使用RequestBuilder.tag(tag)來分配一個(gè)標(biāo)簽。之后你就可以用OkHttpClient.cancel(tag)來取消所有帶有這個(gè)tag的call关摇。
為了模擬這個(gè)場(chǎng)景我們首先創(chuàng)建一個(gè)定時(shí)的線程池:
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毫秒后調(diào)用call.cancel()荒叶,為了能讓請(qǐng)求耗時(shí),我們?cè)O(shè)置每次請(qǐng)求都要請(qǐng)求網(wǎng)絡(luò)输虱,運(yùn)行程序并且不斷的快速點(diǎn)擊發(fā)送請(qǐng)求按鈕:
很明顯每次cancel()都失敗了些楣,仍舊成功的訪問了網(wǎng)絡(luò),在cancel()時(shí)已經(jīng)有讀寫操作了所以會(huì)報(bào)IOException宪睹。每隔100毫秒來調(diào)用call.cancel()顯然時(shí)間間隔太長(zhǎng)愁茁,我們?cè)O(shè)置為1毫秒并不斷的快速的點(diǎn)擊發(fā)送請(qǐng)求按鈕:
沒有請(qǐng)求網(wǎng)絡(luò)的log,幾乎每次都取消成功了亭病。
8.關(guān)于封裝
如果每次請(qǐng)求網(wǎng)絡(luò)都需要寫重復(fù)的代碼絕對(duì)是令人頭疼的鹅很,網(wǎng)上也有很多對(duì)OkHttp封裝的優(yōu)秀開源項(xiàng)目,功能也非常強(qiáng)大罪帖,封裝的意義就在于更加方便的使用道宅,具有拓展性,但是對(duì)OkHttp封裝最需要解決的是以下的兩點(diǎn):
- 避免重復(fù)代碼調(diào)用
- 將請(qǐng)求結(jié)果回調(diào)改為UI線程
根據(jù)以上兩點(diǎn)胸蛛,我們也簡(jiǎn)單封裝一下污茵,在此只是舉個(gè)例子,如果想要使用OkHttp封裝的開源庫葬项,推薦使用OkHttpFinal泞当。
首先呢我們寫一個(gè)抽象類用于請(qǐng)求回調(diào):
public abstract class ResultCallback<T>
{
public abstract void onError(Request request, Exception e);
public abstract void onResponse(Response response);
}
接下來封裝OkHttp,并實(shí)現(xiàn)了異步GET請(qǐng)求:
public class OkHttpEngine {
private 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請(qǐng)求
* @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);
}
});
}
});
}
}
原理很簡(jiǎn)單就是民珍,寫一個(gè)雙重檢查模式的單例襟士,不了解雙重檢查模式的請(qǐng)查看設(shè)計(jì)模式之單例模式的七種寫法這篇文章盗飒。在開始創(chuàng)建的時(shí)候配置好OkHttpClient,在請(qǐng)求網(wǎng)絡(luò)的時(shí)候用Handler將請(qǐng)求的結(jié)果回調(diào)給UI線程陋桂。
最后調(diào)用這個(gè)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(), "請(qǐng)求成功", Toast.LENGTH_SHORT).show();
}
});
使用起來簡(jiǎn)單多了逆趣,而且請(qǐng)求結(jié)果回調(diào)是在UI線程的。下一篇我們會(huì)講到OkHttp3嗜历,來看看它與OkHttp2.x之間的使用方式上有什么區(qū)別宣渗。