第八章 網絡的時代—網絡開發(2)
8.3基於最成熟的Web協議—HTTP協議編程
8.3.1 HTTP協議簡單介紹
超文本傳輸協定(HTTP。HyperTextTransferProtocol)是互聯網上應用最為廣泛的一種網絡協議。
全部的WWW文件都必須遵守這個標準。設計HTTP最初的目的是為了提供一種公布和接收HTML頁面的方法。
HTTP是一個client和server端請求和應答的標準(TCP)。client是終端用戶,server端是站點。通過使用Web瀏覽器、網絡爬蟲或者其它的工具,client發起一個到server上指定port(默認port為80)的HTTP請求。
我們稱這個client為用戶代理(useragent)。
應答的server上存儲著一些資源,比方HTML文件和圖像。我們稱這個應答server為源server。在用戶代理和源server中間可能存在多個中間層,比方代理,網關,或者隧道(tunnel)。雖然TCP/IP協議是互聯網上最流行的應用,HTTP協議並沒有規定必須使用它和基於它支持的層。
其實,HTTP能夠在不論什麽其它互聯網協議上。或者在其它網絡上實現。HTTP僅僅假定其下層協議提供可靠的傳輸,不論什麽能夠提供這樣的保證的協議都能夠被其使用。
通常,由HTTPclient發起一個請求,建立一個到server指定port(默認是80port)的TCP連接。HTTPserver則在那個port監聽client發送過來的請求。
一旦收到請求,server向client發回一個狀態行,比方“HTTP/1.1200 OK”,和響應的消息,消息的消息體可能是請求的文件、錯誤消息、或者其它一些信息。
HTTP使用TCP而不是UDP的原因在於打開一個網頁必須傳送很多數據。而TCP協議提供傳輸控制。按順序組織數據,和錯誤糾正。
通過HTTP或者HTTPS協議請求的資源,由統一資源標識符(UniformResource Identifiers。URI)來標識。
HTTP/1.1協議中共定義了八種方法(有時也叫“動作”)來表明Request-URI指定的資源的不同操作方式:
1)OPTIONS
返回server針對特定資源所支持的HTTP請求方法。也能夠利用向Web
2)HEAD
向server索要與GET請求相一致的響應,僅僅只是響應體將不會被返回。這一方法能夠在不必傳輸整個響應內容的情況下,就能夠獲取包括在響應消息頭中的元信息。
3)GET
向特定的資源發出請求。
註意:GET方法不應當被用於產生“副作用”的操作中,比如在WebApplication中。當中一個原因是GET可能會被網絡蜘蛛等任意訪問。
參見安全方法
4)POST
向指定資源提交數據進行處理請求(比如提交表單或者上傳文件)。數據被包括在請求體中。POST請求可能會導致新的資源的建立和/或已有資源的改動。
5)PUT
向指定資源位置上傳其最新內容。
6)DELETE
請求server刪除Request-URI所標識的資源。
7)TRACE
回顯server收到的請求,主要用於測試或診斷。
8)CONNECT
HTTP/1.1協議中預留給能夠將連接改為管道方式的代理server。
當某個請求所針對的資源不支持相應的請求方法的時候,server應當返回狀態碼405(MethodNot Allowed);當server不認識或者不支持相應的請求方法的時候,應當返回狀態碼501(NotImplemented)。
HTTPserver至少應該實現GET和HEAD方法。其它方法都是可選的。
當然。全部的方法支持的實現都應當符合下述的方法各自的語義定義。此外。除了上述方法。特定的HTTPserver還能夠擴展自己定義的方法。
AndroidSDK提供了多個封裝的類,能夠很方便的實現基於HTML協議的編程。一般在,在Android中針對HTTP進行網絡通信有幾種方式:一種是通過URL類獲取網絡資源。一種是使用HttpURLConnection類(一般通過用URL類的openConnection()方法創建一個HttpURLConnection對象)來實現。一種是使用Apache的HTTPclient組件HttpClient實現。以下會對這幾種方式做詳細的說明。
8.3.2 使用URL類讀取HTTP資源
URL(UniformResourceLocator)對象代表統一資源定位器。它是指向互聯網“資源”的指針。通常情況下,URL由協議名,主機,port,資源組成。例如以下:
http://www.your-host:80/index.php
URL類經常使用的方法有:
StringgetFile();//獲取此URL的資源名 StringgetHost();//獲取此URL的主機名 StringgetPath();//獲取此URL的路勁 IntgetPort();//獲取此URL的port號 StringgetProtocol();//獲取此URL的協議名稱 StringgetQuery();//獲取此URL的查詢字符串 URLConnectionopenConnection();//返回一個URLConnection對象 InputStreamopenStream();//打開連接,並返回一個用於讀取該URL資源的InputStream |
URL對象提供了openStream()方法,就能夠讀取該URL資源的InputStream,很的方便。以下的代碼演示樣例。訪問了Web地址“http://www.google.cn/”。並且將server返回的HTML文本輸出出來。
//import略 publicclass URLTest extends Activity { @Override publicvoid onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); TextViewtv = new TextView(this); StringmyString=""; try{ //定義獲取文件內容的URL URLmURL = new URL("http://www.google.cn/"); //打開URL鏈接 //讀取數據 InputStreamis = mURL.openStream(); BufferedInputStreambis = new BufferedInputStream(is); //使用用ByteArrayBuffer緩存 ByteArrayBufferbaf = new ByteArrayBuffer(50); intcurrent = 0; while((current= bis.read()) != -1) { baf.append((byte)current); } //將緩存的內容轉化為String,用UTF-8編碼 myString= EncodingUtils.getString(baf.toByteArray(), "UTF-8"); }catch(Exception e) { myString= e.getMessage(); } //設置屏幕顯示 tv.setText(myString); this.setContentView(tv); } } |
須要特別註意的是,這裏僅僅是簡單舉個樣例簡單說明怎樣讀取URL網絡資源。能夠看到。我們將全部的代碼都寫到了Activity的onCreate()中。在真實的項目開發過程中,這樣的方式是有問題的。因為網絡的堵塞,可能會出現ANR(ApplicationNot Response)錯誤,導致程序退出。正常的做法,應該是使用異步的方式請求網絡數據。後面會有詳細的樣例說明詳細怎樣做。
經驗分享: 為了避免頻繁讀取字節流。提高讀取效率,用BufferedInputStream緩存讀到的字節流。 InputStreamis = mURL.openStream(); BufferedInputStreambis =new BufferedInputStream(is); //準備好BufferdInputStream後,我們就能夠用read方法讀入網絡數據 ByteArrayBufferbaf = new ByteArrayBuffer(50); intcurrent = 0; while((current= bis.read())! = -1) { baf.append((byte)current); } 因為讀到的數據僅僅是字節流,無法直接顯示到屏幕上,所以我們得在顯示之前將字節流轉換為可讀取的字符串。 假設讀取的是.txt等文件是UTF-8格式的。就須要對數據進行專門的轉換 myString= EncodingUtils.getString(baf.toByteArray(),"UTF-8"); |
8.3.3使用HttpURLConnection類訪問HTTP資源
HttpURLConnection繼承於URLConnection。它在URLConnection的基礎上提供了例如以下的便捷的方法。
voidsetResponseMethod(String method);//設置發送請求 intgetResponseCode();//獲取server的響應代碼 StringgetResponseMessage();//獲取server的響應消息 StringgetResponseMethod();//獲取發送請求 |
使用HttpURLConnection類訪問HTTP資源的基本過程例如以下:
1)創建URL以及HttpURLConnection對象。
2)設置連接參數。
3)連接到server。
4)向server寫數據。
5)從server讀取數據。
以下提供一段代碼說明詳細怎樣實現。
try { //創建一個URL對象 URLurl = new URL(your_url); //創建一個URL連接。假設有代理的話能夠指定一個代理。 URLConnectionconnection = url.openConnection(Proxy_yours); //對於HTTP連接能夠直接轉換成HttpURLConnection。這樣就能夠使用一些HTTP連接特定的方法。如setRequestMethod()等:HttpURLConnectionconnection = (HttpURLConnection)url.openConnection(Proxy_yours); //在開始和server連接之前,可能須要設置一些網絡參數 connection.setConnectTimeout(10000); //連接到server connection.connect(); //與server交互: OutputStreamoutStream = connection.getOutputStream(); ObjectOutputStreamobjOutput = new ObjectOutputStream(outStream); objOutput.writeObject(newString(“this is a string…”)); objOutput.flush(); InputStreamin = connection.getInputStream(); //處理數據 省略詳細代碼… }catch (Exception e) { //網絡讀寫操作往往會產生一些異常,所以在詳細編寫網絡應用時 //最好捕捉每個詳細以採取相應措施 } |
8.3.4使用Apache的HttpClient
ApacheHttpClient 是一個開源項目。它對java.net中的類進行了封裝,彌補了java.net.*靈活性不足的缺點,更適合在Android中開發網絡應用,支持client的HTTP編程,更加方便高效。
一般的。使用HttpClient進行網絡開發的過程例如以下:
1)創建HttpClient對象。
2)假設須要發送GET請求,創建HttpGet對象;假設須要發送Post請求。創建HttpPost對象。
3)假設須要設置請求參數,可使用HttpGet和HttpPost共同的setParams(HttpParamsparams)方法來加入請求參數。HttpPost還能夠調用setEntity(HttpEntityentity)方法來設置。
4)調用HttpClient對象的execute(HttpUriRequestrequest)發送請求。運行該方法返回一個HttpResponse。
5)調用HttpResponse的getAllHeaders()。getHeaders(Stringname)等方法可獲取響應頭。調用HttpResponse的getEntity()方法可獲取HttpEntity對象,該對象封裝了響應的內容。
以下是一個詳細的通用的網絡連接類,包括了GET、POST的完整代碼及註解。
//import略 publicclass HttpConnecter { /** *封裝的Get方法 */ publicstatic String get(String uri) throws ClientProtocolException,IOException { //獲取系統默認的HttpClient鏈接 HttpClienthttpClient = new DefaultHttpClient(); HttpGethttpGet = new HttpGet(uri); //發送GET請求 HttpResponsehttpResponse = httpClient.execute(httpGet); intstatusCode = httpResponse.getStatusLine().getStatusCode(); //獲取server響應信息。200代表成功響應 if(statusCode >= 200 && statusCode < 400) { StringBuilderstringBuilder = new StringBuilder(); //httpResponse.getEntity().getContent()用來獲取響應的內容 BufferedReaderreader = new BufferedReader(new InputStreamReader( httpResponse.getEntity().getContent(),"UTF-8")); for(String s = reader.readLine(); s != null; s = reader.readLine()) { //讀出內容 stringBuilder.append(s); } reader.close(); Log.d("HttpConnecter","HTTPGET:" + uri.toString()); Log.d("HttpConnecter","Response:"+ stringBuilder.toString()); returnstringBuilder.toString(); } returnnull; } /** *封裝的Post方法 */ publicstatic String post(String uri, List<NameValuePair>formparams) throwsClientProtocolException, IOException { HttpClienthttpClient = new DefaultHttpClient(); UrlEncodedFormEntityentity =new UrlEncodedFormEntity(formparams,"UTF-8"); HttpPosthttpPost = new HttpPost(uri); //設置請求參數 httpPost.setEntity(entity); //發送Post請求 HttpResponsehttpResponse = httpClient.execute(httpPost); intstatusCode = httpResponse.getStatusLine().getStatusCode(); if(statusCode >= 200 && statusCode < 400) { StringBuilderstringBuilder = new StringBuilder(); //httpResponse.getEntity().getContent()用來獲取響應的內容 BufferedReaderreader = new BufferedReader(new InputStreamReader( httpResponse.getEntity().getContent(),"UTF-8")); for(String s = reader.readLine(); s != null; s = reader.readLine()) { stringBuilder.append(s); } reader.close(); Log.d("HttpConnecter","HTTPPOST:" + uri.toString()); Log.d("HttpConnecter","Response:"+ stringBuilder.toString()); returnstringBuilder.toString(); } returnnull; } } |
從上面的編程過程我們能夠看出。使用Apache的HttpClient更加簡單,並且它比HttpURLConnection提供了很多其它的功能。所以普通情況下,我們能夠在項目中用HttpClient封裝一些Get、Post、下載、上傳的接口,以供其它代碼直接調用。
經驗分享: 在實現Android網絡應用的開發過程中。須要特別留意兩個問題:一個是網絡流量的問題。還有一個是網絡連接可能不穩定的問題。 對於Android設備的上網方式。一般的有WIFI、3G、2G幾種方式。 對於WIFI的用戶。對於流量不會太在意。而對於3G、甚至2G上網的用戶來說。流量是關系到用戶錢包的大問題。所以,對於整個應用的設計,就要充分考慮流量的問題。 或者在項目後期做單獨的優化工作。比方,假設應用中須要輪詢server獲取信息,那麽我們就能夠依據用戶的上網方式,自己主動調整輪詢時間,為3G、2G的用戶節省流量。這裏僅僅是舉這樣一個樣例。詳細的,還要依據業務需求進行細致挖掘。 用戶使用Android設備,一般都是碎片時間。可能是在辦公室,也可能是在乘坐公交車或者地鐵,網絡信號未必會一直穩定。網絡連接可能會時斷時續。 我們在設計網絡應用的時候。就要充分考慮這樣的情況。一個是要考慮怎樣對網絡連接異常進行處理,一個是要考慮網絡恢復後怎樣處理。 |
第八章 網絡的時代—網絡開發(2)