前面幾篇博文簡單的介紹了一些常見的Http的操作,這些操作幾乎都是在新開的線程中進行的網絡請求,并在日志中打印出獲取到的網絡數據。那么,問題來了!(呃~感覺下一句是藍翔有木有?)如何在把獲取到的網絡數據顯示在UI界面上呢?如果按照前幾篇博文的例子,并在主線程中直接對子線程獲取的網絡數據直接進行操作就會發現一個問題,那就是在主線程中根本就獲取不到子線程得到的從服務器返回的數據。因為,網絡操作屬于耗時操作,為了不阻塞主線程而放在子線程中,當主線程中的代碼執行完后子線程未必就獲取到服務器返回的數據了。所以,為了解決這樣的問題我們通常在Http的操作中加上異步消息機制,并且為了簡化操作對其進行簡單的封裝,加上回調機制。
?
這篇博文就以HttpClient訪問百度首頁為例子,對之前博文中的Http操作進一步的完善。
首先,先回憶一下使用HttpClient的最簡單的步驟或者說是過程:
(這里的strurl為"http://www.baidu.com",str為從服務器返回的數據。)
HttpClient client = new DefaultHttpClient(); HttpGet request = new HttpGet(strurl); HttpResponse response= client.execute(request); if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { String str = EntityUtils.toString(response.getEntity()); }
?
下面將基于上面的這些操作來實現簡易封裝:
首先加入回調機制:
(如果對回調還不太熟悉,請參考博文《java回調機制解析》)
public interface HttpCallback {void onSuccess(Object result);void onFailure(Exception result);}
?
?
接下來,實現一個Request類:
這個類一共有三個屬性:
method是在該類中用enum類型限定的RequestMethod類型的對象,用于設置請求方式。
url就是網絡請求的路徑。
callback為回調接口對象,用于在網絡請求中獲取到數據后將數據傳遞至調用處。
public class Request {public RequestMethod method;public String url;public HttpCallback callback;public Request(String url, RequestMethod method) {this.method = method;this.url = url;}public Request(String url) {this.method = RequestMethod.GET;this.url = url;}public enum RequestMethod {GET, POST, DELETE, PUT}public void setCallBack(HttpCallback callback) {this.callback = callback;}public void execute() {RequstTask task = new RequstTask(this);task.execute();}}
從上面的代碼可以看出,該類的兩個構造函數,都需要傳入URL,其中一個構造函數可以設置請求方式,另一個設置默認請求方式GET。
在該類中有一個execute()方法,在這個方法中RequestTask類繼承于AsyncTask,它的作用就是在RequestTask中進行網絡請求。
?
?
RequestTask代碼如下:
public class RequstTask extends AsyncTask<Void, integer, Object> {private Request requset;public RequstTask(Request requset) {this.requset = requset;}@Overrideprotected void onPreExecute() {// TODO Auto-generated method stubsuper.onPreExecute();}@Overrideprotected Object doInBackground(Void... params) {try {HttpResponse response = HttpClientUtils.execute(requset);if (requset.callback != null) {//如果進一步抽象化回調接口,使其子抽象類能分別處理多種格式的數據(如:JSON、XML、String、File),可以更方便//這里直接使用返回String類型的數據if(response.getStatusLine().getStatusCode()==HttpStatus.SC_OK){return EntityUtils.toString(response.getEntity());}return "請求失敗";} else {return null;}} catch (Exception e) {return e;}}@Overrideprotected void onPostExecute(Object result) {if (requset.callback != null) {if (result instanceof Exception) {requset.callback.onFailure((Exception) result);} else {requset.callback.onSuccess(result);}}}@Overrideprotected void onProgressUpdate(integer... values) {// TODO Auto-generated method stubsuper.onProgressUpdate(values);}}
這個類的構造函數需要傳遞一個Request對象,會根據這個Request對象的method屬性確定請求方式、url屬性確定請求路徑,根據callback屬性的有無來判斷是否是否將獲取到的網絡數據傳遞至調用接口處。
在doInBackground()中如果Request對象的callback屬性為null則返回null:
當Request對象的callback屬性不為null,則先取出服務器返回的狀態碼(這里的response為服務器返回的信息),如果等于200(也就是HttpStatus.SC_OK)那么就說明響應成功了。再調用getEntity()方法獲取到一個HttpEntity實例,然后再用EntityUtils.toString()這個靜態方法將HttpEntity轉換成字符串并return。返回后的數據將傳入onPostExecute()方法中作為參數。
if (requset.callback != null) {//如果進一步抽象化回調接口,使其子抽象類能分別處理多種格式的數據(如:JSON、XML、String、File),可以更方便//這里直接使用返回String類型的數據if(response.getStatusLine().getStatusCode()==HttpStatus.SC_OK){return EntityUtils.toString(response.getEntity());}return "請求失敗";} else {return null;}
在onPostExecute()中Request對象的callback屬性為null根本就沒返回。反之,doInBackground()的結果result將會傳遞至回調接口的調用處:
if (requset.callback != null) {if (result instanceof Exception) {requset.callback.onFailure((Exception) result);} else {requset.callback.onSuccess(result);}}
?
?
在doInBackground()中有一句代碼:
HttpResponse response = HttpClientUtils.execute(requset);
這里的HttpClientUtils其實就是一個簡單的封裝,其代碼如下:
public class HttpClientUtils {public static HttpResponse execute(Request requst) throws Exception {switch (requst.method) {case GET:return get(requst);}return null;}private static HttpResponse get(Request requst)throws ClientProtocolException, IOException {HttpClient client = new DefaultHttpClient();HttpGet get = new HttpGet(requst.url);HttpResponse response = client.execute(get);return response;}}
從代碼中可以看出,execute()和get()方法都是static類型并且返回類型都是HttpResponse。在execute()方法中根據傳入的Request對象的method屬性結合switch語句執行相對應的網絡請求方式,并返回從服務器獲得HttpResponse類型的信息。這個信息在RequstTask類中被直接賦值使用。
?
?
大體的過程就是這樣了。
最后的最后,在Activity中的使用:
public class MainActivity extends Activity {private Button btn;private TextView tv;private Request request;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.main);btn = (Button) findViewById(R.id.btn);tv = (TextView) findViewById(R.id.tv);request = new Request("http://www.baidu.com", RequestMethod.GET);btn.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {try {request.setCallBack(new HttpCallback() {@Overridepublic void onSuccess(Object result) {tv.setText((String) result);}@Overridepublic void onFailure(Exception result) {tv.setText("請求失敗");}});} catch (Exception e) {// TODO Auto-generated catch block e.printStackTrace();}request.execute();}});}}
Activity的布局文件如下:


<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical" ><Buttonandroid:id="@+id/btn"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="請求" /><ScrollViewandroid:id="@+id/sv"android:layout_width="match_parent"android:layout_height="wrap_content" ><TextViewandroid:id="@+id/tv"android:layout_width="fill_parent"android:layout_height="fill_parent" /></ScrollView></LinearLayout>
?
?
運行程序,效果如下:
?
?
Demo下載:http://download.csdn.net/detail/af74776/8066779