本文主要介紹android用戶端如何使用webservice。第一篇介紹ksoap2,第二篇介紹rest。
android基礎知識10:webservice 01:KSOAP2
android基礎知識10:webservice 02:REST
1、webservice概述
本部分內容來源於《http://www.w3school.com.cn/webservices/index.asp》
1.1 什麼是Web Services?
- Web Services 是應用程式組件
- Web Services 使用開放協議進行通訊
- Web Services 是獨立的(self-contained)並可自我描述
- Web Services 可通過使用UDDI來發現
- Web Services 可被其他應用程式使用
- XML 是 Web Services 的基礎
1.2 它如何工作?
- 基礎的 Web Services 平台是 XML + HTTP。
- HTTP 協議是最常用的網際網路協議。
- XML 提供了一種可用於不同的平台和程式設計語言之間的語言。
- Web services 平台的元素:
- SOAP (簡易對象訪問協議)
- UDDI (通用描述、發現及整合)
- WSDL (Web services 描述語言)
1.3 Web services 有兩種類型的應用
有一些功能是不同的應用程式常常會用到的。那麼為什麼要周而復始地開發它們呢?
Web services 可以把應用程式組件作為服務來提供,比如匯率轉換、天氣預報或者甚至是語言翻譯等等。
比較理想的情況是,每種應用程式組件只有一個最優秀的版本,這樣任何人都可以在其應用程式中使用它。
通過為不同的應用程式提供一種連結其資料的途徑,Web services有助於解決協同工作的問題。
通過使用 Web services,您可以在不同的應用程式與平台之間來交換資料。
1.4 實現手段
WebService一般分為.Net版和Java版,今天我們主要來實現Java版的WebService,.Net版本的還是比較簡單的。 什麼是WebServices?
它是一種構建應用程式的普遍模型,可以在任何支援網路通訊的作業系統中實施運行;它是一種新的web應用程式分支,是自包含、自描述、模組化的應用,發行就緒、定位、通過web調用。Web Service是一個應用組件,它邏輯性的為其他應用程式提供資料與服務.各應用程式通過網路通訊協定和規定的一些標準資料格式(Http,XML,Soap)來訪問Web Service,通過Web Service內部執行得到所需結果.Web Service可以執行從簡單的請求到複雜商務處理的任何功能。一旦部署以後,其他Web Service應用程式可以發現並調用它部署的服務。
關鍵的技術和規則
在構建和使用Web Service時,主要用到以下幾個關鍵的技術和規則:
1.XML:描述資料的標準方法.
2.SOAP:表示資訊交換的協議.
3.WSDL:Web服務描述語言.
4.UDDI:通用描述、發現與整合,它是一種獨立於平台的,基於XML語言的用於在互連網上描述商務的協議。
可擴充的標記語言(XML)是Web service平台中表示資料的基本格式。除了易於建立和易於分析外,XML主要的優點在於它既是平台無關的,又是廠商無關的。無關性是比技術優越性更重要的:軟體廠商是不會選擇一個由競爭者所發明的技術的。
SOAP是web service的標準通訊協定,SOAP為simple object access protocoll的縮寫,簡易物件存取通訊協定 (SOAP). 它是一種標準化的傳輸訊息的XML訊息格式。
WSDL的全稱是web service Description Language,是一種基於XML格式的關於web服務的描述語言。其主要目的在於web service的提供者將自己的web服務的所有相關內容,如所提供的服務的傳輸方式,服務方法介面,介面參數,服務路徑等,產生相應的完全文檔,發布給使用者。使用者可以通過這個WSDL文檔,建立相應的SOAP請求訊息,通過HTTP傳遞給webservice提供者;web服務在完成服務要求後,將SOAP返回訊息傳回要求者,服務要求者再根據WSDL文檔將SOAP返回訊息解析成自己能夠理解的內容。
將web service進行UDDI註冊發布,UDDI是一種建立註冊表格服務的規範,以便大家將自己的web service進行註冊發布供使用者尋找.然而當服務提供者想將自己的web service向全世界公布,以便外部找到其服務時,那麼服務提供者可以將自己的web service註冊到相應的UDDI商用註冊網站,目前全球有IBM等4家UDDI商用註冊網站。因為WSDL檔案中已經給定了web service的地址URI,外部可以直接通過WSDL提供的URI進行相應的web service調用。所以UDDI並不是一個必需的web
service組件,服務方完全可以不進行UDDI的註冊。
2、android端webservice的使用
本文僅介紹如何從android手機端使用webservice。
目前比較通用的webservice為ksoap2和rest。下面來分別介紹。
3、使用KSOAP2調用WebService
3.1 擷取並使用KSOAP包
我們在PC機器java用戶端中,需要一些庫,比如XFire,Axis2,CXF等等來支援訪問WebService,但是這些庫並不適合我們資源有限的android手機用戶端,做過JAVA ME的人都知道有KSOAP這個第三方的類庫,可以協助我們擷取伺服器端webService調用,當然KSOAP已經提供了基於android版本的jar包了,那麼我們就開始吧:
首先下載KSOAP包:ksoap2-android-assembly-2.5.2-jar-with-dependencies.jar包
然後建立android項目:並把下載的KSOAP包放在android項目的lib目錄下:右鍵->build path->configure build path--選擇Libraries,
以下分為七個步驟來調用WebService方法:
第一:執行個體化SoapObject 對象,指定webService的命名空間(從相關WSDL文檔中可以查看命名空間),以及調用方法名稱。如:
//命名空間 private static final String serviceNameSpace="http://WebXml.com.cn/"; //調用方法(獲得支援的城市) private static final String getSupportCity="getSupportCity";//執行個體化SoapObject對象 SoapObject request=new SoapObject(serviceNameSpace, getSupportCity);
第二步:假設方法有參數的話,設定調用方法參數,這一步是可選的,如果方法沒有參數,可以省略這一步。
request.addProperty("參數名稱","參數值");
要注意的是,addProperty方法的第1個參數雖然表示調用方法的參數名,但該參數值並不一定與服務端的WebService類中的方法參數名一致,只要設定參數的順序一致即可。
第三步:設定SOAP請求資訊(參數部分為SOAP協議版本號碼,與你要調用的webService中版本號碼一致):
//獲得序列化的Envelope SoapSerializationEnvelope envelope=new SoapSerializationEnvelope(SoapEnvelope.VER11); envelope.bodyOut=request;
建立SoapSerializationEnvelope對象時需要通過SoapSerializationEnvelope類的構造方法設定SOAP協議的版本號碼。該版本號碼需要根據服務端WebService的版本號碼設定。在建立SoapSerializationEnvelope對象後,不要忘了設定SoapSerializationEnvelope類的bodyOut屬性,該屬性的值就是在第1步建立的SoapObject對象。
第四步:註冊Envelope,
(new MarshalBase64()).register(envelope);
第五步:構建傳輸對象,並指明WSDL文檔URL:
//請求URL private static final String serviceURL="http://www.webxml.com.cn/webservices/weatherwebservice.asmx";//Android傳輸對象 AndroidHttpTransport transport=new AndroidHttpTransport(serviceURL); transport.debug=true;
第六步:調用WebService(其中參數為1:命名空間+方法名稱,2:Envelope對象):
transport.call(serviceNameSpace+getWeatherbyCityName, envelope);
call方法的第1個參數一般為null,第2個參數就是在第3步建立的SoapSerializationEnvelope對象。
第七步:解析返回資料:
if(envelope.getResponse()!=null){ return parse(envelope.bodyIn.toString()); }/************** * 解析XML * @param str * @return */ private static List<String> parse(String str){ String temp; List<String> list=new ArrayList<String>(); if(str!=null && str.length()>0){ int start=str.indexOf("string"); int end=str.lastIndexOf(";"); temp=str.substring(start, end-3); String []test=temp.split(";"); for(int i=0;i<test.length;i++){ if(i==0){ temp=test[i].substring(7); }else{ temp=test[i].substring(8); } int index=temp.indexOf(","); list.add(temp.substring(0, index)); } } return list; }
這樣就成功啦。
3.2 樣本:通過WebService查詢產品資訊
本例涉及到一個WebService服務端程式和一個OPhone用戶端程式。讀者可直接將服務端程式(axis2目錄)複製到<Tomcat安裝目錄>\webapps目錄中,然後啟動Tomcat,並在瀏覽器地址欄中輸入如下的URL:
http://localhost:8080/axis2
如果在瀏覽器中顯示2所示的頁面,說明服務端程式已經安裝成功。
這個服務端WebService程式是SearchProductService,實際上SearchProductService是一個Java類,只是利用Axis2將其映射成WebService。在該類中有一個getProduct方法。這個方法有一個String類型的參數,表示產品名稱。該方法返回一個Product對象,該對象有3個屬性:name、price和productNumber。讀者可以使用如下的URL來查看SearchProductService的WSDL文檔。
http://localhost:8080/axis2/services/SearchProductService?wsdl
顯示WSDL文檔的頁面3所示。
在圖3中的黑框中就是WebService的命名空間,也是SoapObject類的構造方法的第1個參數值。這個WebService程式可以直接使用如下的URL進行測試。
http://localhost:8080/axis2/services/SearchProductService/getProduct?param0=iphone
測試的結果4所示。
從所示的測試結果可以看出,Axis2將getProduct方法返回的Product對象直接轉換成了XML文檔(實際上是SOAP格式)返回。
下面我們來根據前面介紹的使用KSOAP2的步驟來編寫調用WebService的OPhone用戶端程式,代碼如下:
package net.blogjava.mobile.wsclient; import org.ksoap2.SoapEnvelope;import org.ksoap2.serialization.SoapObject;import org.ksoap2.serialization.SoapSerializationEnvelope;import org.ksoap2.transport.HttpTransportSE;import android.app.Activity;import android.os.Bundle;import android.view.View;import android.view.View.OnClickListener;import android.widget.Button;import android.widget.EditText;import android.widget.TextView; public class Main extends Activity implements OnClickListener{ @Override public void onClick(View view) { EditText etProductName = (EditText)findViewById(R.id.etProductName); TextView tvResult = (TextView)findViewById(R.id.tvResult); // WSDL文檔的URL,192.168.17.156為PC的ID地址 String serviceUrl = "http://192.168.17.156:8080/axis2/services/SearchProductService?wsdl"; // 定義調用的WebService方法名 String methodName = "getProduct"; // 第1步:建立SoapObject對象,並指定WebService的命名空間和調用的方法名 SoapObject request = new SoapObject("http://service", methodName); // 第2步:設定WebService方法的參數 request.addProperty("productName", etProductName.getText().toString()); // 第3步:建立SoapSerializationEnvelope對象,並指定WebService的版本 SoapSerializationEnvelope envelope = new SoapSerializationEnvelope(SoapEnvelope.VER11); // 設定bodyOut屬性 envelope.bodyOut = request; // 第4步:建立HttpTransportSE對象,並指定WSDL文檔的URL HttpTransportSE ht = new HttpTransportSE(serviceUrl); try { // 第5步:調用WebService ht.call(null, envelope); if (envelope.getResponse() != null) { // 第6步:使用getResponse方法獲得WebService方法的返回結果 SoapObject soapObject = (SoapObject) envelope.getResponse(); // 通過getProperty方法獲得Product對象的屬性值 String result = "產品名稱:" + soapObject.getProperty("name") + "\n"; result += "產品數量:" + soapObject.getProperty("productNumber") + "\n"; result += "產品價格:" + soapObject.getProperty("price"); tvResult.setText(result); } else { tvResult.setText("無此產品."); } } catch (Exception e) { } } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); Button btnSearch = (Button) findViewById(R.id.btnSearch); btnSearch.setOnClickListener(this); }}
在編寫上面代碼時應注意如下兩點:
1)在第2步中addProperty方法的第1個參數值是productName,該值雖然是getProduct方法的參數名,但addProperty方法的第1個參數值並不限於productName,讀者可以將這個參數設為其他的任何字串(但該值必須在XML中是合法的,例如,不是設為“<”、“>”等XML預留的字串)。
2)通過SoapObject類的getProperty方法可以獲得Product對象的屬性值,這些屬性名稱就是圖4所示的測試結果中的屬性名稱。
運行本例,在文字框中輸入“htc hero”,單擊【查詢】按鈕,會在按鈕下方顯示5所示的查詢結果。
防止UI組件阻塞
從功能上看,本文樣本中給出的代碼並沒有任何問題。但可能有的讀者會有這樣的擔心:如果調用WebService的使用者很多,至使服務端響應遲緩;或服務端的IP根本就不對,那麼在這些情況下,使用者介面的按鈕和文字框組件豈不是象“死”了一樣無法響應使用者的其他動作。當然,發生這種情況的可能性是有的,尤其是在複雜的網路環境中發生的可能性是很大的,一但發生這種事情,就會使整個軟體系統在使用者體驗上變得非常糟糕。
使用者和開發人員都希望改善這種糟糕的情況。最理想的狀態是單擊按鈕調用WebService方法時,即使由於某種原因,WebService方法並未立即返回,介面上的組件仍然會處於活動狀態,也就是說,使用者仍然可以使用當前介面中的其他組件。
在OPhone中可以採用非同步方式來達到這個目的。非同步實際上就是通過多線程的方式來實現。一般使用new Thread(this).start()來建立和開始一個線程。但本節並不使用Thread來實現非同步,而是通過AsyncTask類使要執行的任務(調用WebService)在後台執行。
下面先看看改進後的代碼。
package net.blogjava.mobile.wsclient; import org.ksoap2.SoapEnvelope;import org.ksoap2.serialization.SoapObject;import org.ksoap2.serialization.SoapSerializationEnvelope;import org.ksoap2.transport.HttpTransportSE;import android.app.Activity;import android.os.AsyncTask;import android.os.Bundle;import android.view.View;import android.view.View.OnClickListener;import android.widget.Button;import android.widget.EditText;import android.widget.TextView; public class Main extends Activity implements OnClickListener{ private EditText etProductName; private TextView tvResult; class WSAsyncTask extends AsyncTask { String result = ""; @Override protected Object doInBackground(Object... params) { try { String serviceUrl = "http://192.168.17.156:8080/axis2/services/SearchProductService?wsdl"; String methodName = "getProduct"; SoapObject request = new SoapObject("http://service", methodName); request.addProperty("productName", etProductName.getText().toString()); SoapSerializationEnvelope envelope = new SoapSerializationEnvelope( SoapEnvelope.VER11); envelope.bodyOut = request; HttpTransportSE ht = new HttpTransportSE(serviceUrl); ht.call(null, envelope); if (envelope.getResponse() != null) { SoapObject soapObject = (SoapObject) envelope.getResponse(); result = "產品名稱:" + soapObject.getProperty("name") + "\n"; result += "產品數量:" + soapObject.getProperty("productNumber") + "\n"; result += "產品價格:" + soapObject.getProperty("price"); } else { result = "無此產品."; } } catch (Exception e) { result = "調用WebService錯誤."; } // 必須使用post方法更新UI組件 tvResult.post(new Runnable() { @Override public void run() { tvResult.setText(result); } }); return null; } } @Override public void onClick(View view){ // 非同步執行調用WebService的任務 new WSAsyncTask().execute(); } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); Button btnSearch = (Button) findViewById(R.id.btnSearch); btnSearch.setOnClickListener(this); etProductName = (EditText) findViewById(R.id.etProductName); tvResult = (TextView) findViewById(R.id.tvResult); }}
調用WebService的核心代碼與樣本中的代碼完全一樣,在這裡就不再做具體的介紹了。但在編寫上面的代碼時還需要注意如下幾點。
1. 一般需要編寫一個AsyncTask的子類來完成後台執行任務的工作。
2. AsyncTask的核心方法是doInBackground,當調用AsyncTask類的execute方法時,doInBackground方法會非同步執行。因此,可以將執行任務的代碼寫在doInBackground方法中。
3. 由於本例中的TextView組件是在主線程(UI線程)中建立的,因此,在其他的線程(doInBackground方法所在的線程)中不能直接更新TextVew組件。為了更新TextView組件,需要使用TextView類的post方法。該方法的參數是一個Runnable對象,需要將更新TextView組件的代碼寫在Runnable介面的run方法中。
4. 雖然不能在其他線程中更新UI組件,但可以從其他線程直接讀取UI組件的值。例如,在doInBackground方法中直接讀取了EditText組件的值。
5. 調用AsyncTask類的execute方法後會立即返回。execute方法的參數就是doInBackground方法的參數。doInBackground方法的傳回值可以通過AsyncTask.execute(...).get()方法獲得。
讀者可以將本例中的IP改成其他的值,看看單擊按鈕後,是否還可在文字框中輸入其他的內容。如果這個IP是正確的,並且WebService可訪問,那麼會在TextView組件中輸出相應的傳回值。
參考文獻:
http://www.w3school.com.cn
在OPhone 中使用KSOAP2調用WebService
Android開發WebService(Java版)
Android與伺服器端資料互動(基於SOAP協議整合android+webservice)