網路程式設計的最佳實踐
目前你已經掌握了 HttpURLConnection 和 HttpClient 的用法,知道了如何發起 HTTP 請求,以及解析伺服器返回的資料,但也許你還沒有發現,之前我們的寫法其實是很有問題的。因為每一個應用程式很有可能會在很多地方都使用到網路功能,而傳送 HTTP 請求的程式碼基本都是相同的,如果我們每次都去編寫一遍傳送 HTTP 請求的程式碼,這顯然是非常差勁的做法。
沒錯,通常情況下我們都應該將這些通用的網路操作提取到一個公共的類裡,並提供一個靜態方法,當想要發起網路請求的時候只需簡單地呼叫一下這個方法即可。比如使用如下的寫法:
以後每當需要發起一條 HTTP 請求的時候就可以這樣寫:public class HttpUtil { public static String sendHttpRequest(String address) { HttpURLConnection connection = null; try { URL url = new URL(address); connection = (HttpURLConnection) url.openConnection(); connection.setRequestMethod("GET"); connection.setConnectTimeout(8000); connection.setReadTimeout(8000); connection.setDoInput(true); connection.setDoOutput(true); InputStream in = connection.getInputStream(); BufferedReader reader = new BufferedReader(new InputStreamReader(in)); StringBuilder response = new StringBuilder(); String line; while ((line = reader.readLine()) != null) { response.append(line); } return response.toString(); } catch (Exception e) { e.printStackTrace(); return e.getMessage(); } finally { if (connection != null) { connection.disconnect(); } } } }
String address = "http://www.baidu.com";
String response = HttpUtil.sendHttpRequest(address);
在獲取到伺服器響應的資料後我們就可以對它進行解析和處理了。但是需要注意,網路請求通常都是屬於耗時操作,而 sendHttpRequest() 方法的內部並沒有開啟執行緒,這樣就有可能導致在呼叫 sendHttpRequest() 方法的時候使得主執行緒被阻塞住。
你可能會說,很簡單嘛,在 sendHttpRequest() 方法內部開啟一個執行緒不就解決這個問題了嗎?其實不是像你想象中的那麼容易,因為如果我們在 sendHttpRequest() 方法中開啟了一個執行緒來發起 HTTP 請求,那麼伺服器響應的資料是無法進行返回的,所有的耗時邏輯都是在子執行緒裡進行的,sendHttpRequest() 方法會在伺服器還沒來得及響應的時候就執行結束了,當然也就無法返回響應的資料了
那麼遇到這種情況應該怎麼辦呢?其實解決方法並不難,只需要使用 Java 的回撥機制就可以了,下面讓我們來學習一下回調機制到底是如何使用的。
首先需要定義一個介面,比如將它命名成 HttpCallbackListener,程式碼如下所示:
public interface HttpCallbackListener {
void onFinish(String response);
void onError(Exception e);
}
可以看到,我們在介面中定義了兩個方法,onFinish() 方法表示當伺服器成功想要我們請求的時候呼叫,onError() 表示當進行網路操作出現錯誤的時候呼叫。這兩個方法都帶有引數,onFinish() 方法中的引數代表著伺服器返回的資料,而 onError() 方法中的引數記錄著錯誤的詳細資訊。
接著修改 HttpUtil 中的程式碼,如下所示:
public class HttpUtil {
public static void sendHttpRequest(final String address, final HttpCallbackListener listener) {
new Thread(new Runnable() {
@Override
public void run() {
HttpURLConnection connection = null;
try {
URL url = new URL(address);
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setConnectTimeout(8000);
connection.setReadTimeout(8000);
connection.setDoInput(true);
connection.setDoOutput(true);
InputStream in = connection.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
StringBuilder response = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
response.append(line);
}
if (listener != null) {
// 回撥 onFinish() 方法
listener.onFinish(response.toString());
}
} catch (Exception e) {
if (listener != null) {
// 回撥 onError() 方法
listener.onError(e);
}
} finally {
if (connection != null) {
connection.disconnect();
}
}
}
}).start();
}
}
我們首先給 sendHttpRequest() 方法添加了一個 HttpCallbackListener 引數,並在方法的內部開啟了一個子執行緒,然後在子執行緒裡去執行具體的網路操作。注意子執行緒中是無法通過 onFinish() 方法中,如果出現了異常就將異常原因傳入到 onError() 方法中。
現在 sendHttpRequest() 方法接收兩個引數了,因此我們在呼叫它的時候還需要將 HttpCallbackListener 的例項傳入,如下所示:
HttpUtil.sendHttpRequest(address, new HttpCallbackListener() {
@Override
public void onFinish(String response) {
// 在這裡根據返回內容執行具體的邏輯
}
@Override
public void onError(Exception e) {
// 在這裡對異常情況進行處理
}
});
這樣的話,當伺服器成功響應的時候我們就可以在 onFinish() 方法裡對響應資料進行處理了,類似地,如果出現了異常,就可以在 onError() 方法裡對異常情況進行處理。如此一來,我們就巧妙地利用回撥機制將響應資料成功返回給呼叫方了。
另外需要注意的是,onFinish() 方法和 onError() 方法最終還是在子執行緒中執行的,因此我們不可以在這裡執行任何的 UI 操作,如果需要根據返回的結果來更新 UI,則仍然要使用
摘自《第一行程式碼》