前言
講完了Volley,我們接下來看看目前比較火的網路框架OkHttp, 它處理了很多網路疑難雜症:會從很多常用的連線問題中自動恢復。如果您的伺服器配置了多個IP地址,當第一個IP連線失敗的時候,OkHttp會自動嘗試下一個IP,此外OkHttp還處理了代理伺服器問題和SSL握手失敗問題。
1.使用前準備
eclipse引入jar包地址:
okhttp-2.7.5.jar
okio-1.7.0.jar
Android Studio 配置gradle:
1 2 |
compile 'com.squareup.okhttp:okhttp:2.7.5' compile 'com.squareup.okio:okio:1.7.0' |
2.非同步GET請求
最簡單的get請求,老規矩請求百度:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
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請求
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
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請求
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
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:
1 2 3 4 |
mOkHttpClient = new OkHttpClient(); File sdcache = getExternalCacheDir(); int cacheSize = 10 * 1024 * 1024; mOkHttpClient.setCache(new Cache(sdcache.getAbsoluteFile(), cacheSize)); |
接下來非同步GET請求baidu:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
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(); } }); } }); } |
第一次請求會請求網路得到資料,第二次以及後面的請求則會從快取中取出資料:
當然也有種情況是有的請求每次都需要最新的資料,則在建立Request,來設定cacheControl為“CacheControl.FORCE_NETWORK”,用來表示請求會一直請求網路得到資料:
1 2 3 4 |
final Request request = new Request.Builder() .url("http://www.baidu.com") .cacheControl(CacheControl.FORCE_NETWORK) .build(); |
執行程式結果為:
6.設定超時時間
另外我們也需要設定超時的時間用來處理各種網路超時的情況,超時的原因可能是網路問題也可能是伺服器響應慢等問題,OkHttp當然不會忽略這一點,它支援連線、讀取和寫入超時的時間設定:
1 2 3 4 |
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。
為了模擬這個場景我們首先建立一個定時的執行緒池:
1 |
private ScheduledExecutorService executor = Executors.newScheduledThreadPool(1); |
接下來的程式碼為:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
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(),為了能讓請求耗時,我們設定每次請求都要請求網路,執行程式並且不斷的快速點選傳送請求按鈕:
很明顯每次cancel()都失敗了,仍舊成功的訪問了網路,在cancel()時已經有讀寫操作了所以會報IOException。每隔100毫秒來呼叫call.cancel()顯然時間間隔太長,我們設定為1毫秒並不斷的快速的點選傳送請求按鈕:
沒有請求網路的log,幾乎每次都取消成功了。
8.關於封裝
如果每次請求網路都需要寫重複的程式碼絕對是令人頭疼的,網上也有很多對OkHttp封裝的優秀開源專案,功能也非常強大,封裝的意義就在於更加方便的使用,具有擴充性,但是對OkHttp封裝最需要解決的是以下的兩點:
- 避免重複程式碼呼叫
- 將請求結果回撥改為UI執行緒
根據以上兩點,我們也簡單封裝一下,在此只是舉個例子,如果想要使用OkHttp封裝的開源庫,推薦使用OkHttpFinal。
首先呢我們寫一個抽象類用於請求回撥:
1 2 3 4 5 6 |
public abstract class ResultCallback<T> { public abstract void onError(Request request, Exception e); public abstract void onResponse(Response response); } |
接下來封裝OkHttp,並實現了非同步GET請求:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
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請求 * @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()方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
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之間的使用方式上有什麼區別。