Android + Web Service 建國科技大學 資管系 饒瑞佶 2017/3 V1
呼叫 OpenData Web Service http://data.taipei/opendata/datalist/apiAccess?scope=resourceAquire& rid=e7c46724-3517-4ce5-844f-5a4404897b7d
http://data. taipei/opendata/datalist/apiAccess http://data.taipei/opendata/datalist/apiAccess?scope=resourceAquire&rid=e7c46724-3517-4ce5-844f-5a4404897b7d
Notepad++ 解析JSON https://sourceforge.net/projects/nppjsonviewer/?source=typ_redirect
JSON JavaScript Object Notation 一種用於資料傳輸與交換的輕量級資料結構 目前網路資料傳輸或OPEN DATA都支援此格式,也使用此 格式 以文字為主 從JavaScript的陣列子集所演變而來(JS用[]來表示陣列) 並非程式語言
JSON sample { } "fullname" : "Sam Kelly", "telephones" : [ {"type" : "work" , "value" : "123-4567"}, {"type" : "home" , "value" : "987-6543"}, ], "addresses" : [ {"type" : "work" , "value" : "11 1st Ave"}, {"type" : "home" , "value" : "22 Main St"}, ] } 最簡單樣式(單純的文字資料) 3組資料 資料是一個陣列 陣列中每個元素又是一個集合
JSON中的幾個符號 : 用以分隔名稱與資料(資料對) { } 用來表示資料集(物件) [] 用來表示陣列 , 用來分割每段「資料對」
JSON格式 物件 (object) 一個物件以「{」開始,並以「}」結束。 一個物件包含一系列非排序的名稱:值組成(資料對) 每個名稱/值對之間使用「,」分割 名稱/值(collection) 名稱和值之間使用「:」隔開 名稱為一字串 值為字串、數值、物件、布林值、陣列或是null {name:value} 資料對使用「,」分隔 陣列(Array) 使用中括弧「[ ]」將資料收集 多筆資料使用「,」分隔 [collection, collection, collection]
使用JSON 嚴格說是解析JSON 多數程式語言都具備有解析JSON的語法或函式庫 這裡使用jQuery、PHP、ASP.NET三種語法分別示範
Android vs. JSON
JSON解析 Google提供org.json.JSONObject類別 JSONObject(要解析的原字串) get(name) getJSONArray(name) 透過getJSONObject(index)取得JSONObject物件 getXXX(name), 如Int, Double, String…等 取得name對應的資料(依據資料格式)
解析範例 http://data.taipei/opendata/datalist/apiAccess?scope=resourc eAquire&rid=e7c46724-3517-4ce5-844f-5a4404897b7d
新增專案
新增上網權限 <uses-permission android:name="android.permission.INTERNET"></uses-permission> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
layout <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin"> <TextView android:id="@+id/main_textView1" android:layout_height="wrap_content" android:text="@string/app_name" android:textAppearance="?android:attr/textAppearanceLarge" /> <LinearLayout android:orientation="horizontal">
layout <Button android:id="@+id/main_button2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" android:text="重新設定" /> android:id="@+id/main_button1" android:text="解析JSON" /> </LinearLayout> <ListView android:id="@+id/main_listView1" android:layout_width="match_parent" android:layout_height="wrap_content" />
layout
strings.xml <resources> <string name="app_name">AndroidWebService</string> <string name="str_parsing">查詢API中,請稍候</string> <string name="connection_error">無法找到可用連線,請檢查網路連線</string> <string name="lost_connection">失去網路連線</string> <string name="wifi_connected">Wi-Fi已連線</string> <string name="str_parsing_ok">解析成功</string> <string name="str_user_choose">您選擇的是:</string> </resources>
加入畫面對應物件與按鈕程式
public class MainActivity extends AppCompatActivity { private TextView mTextView01; //標題 private Button mButton01, mButton02; private ListView mListView01; // 顯示資料 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mTextView01 = (TextView) findViewById(R.id.main_textView1); //標題 mButton01 = (Button) findViewById(R.id.main_button1); //解析JSON mButton02 = (Button) findViewById(R.id.main_button2); //重新設定 mListView01 = (ListView) findViewById(R.id.main_listView1); // 顯示資料 // 開始透過WebService取得資料 mButton01.setOnClickListener(new Button.OnClickListener() { public void onClick(View v) { if (refreshDisplay) { // 如果有網路連線 mTextView01.setText(getString(R.string.str_parsing)); // 呼叫DownloadTask連線WebService進行資料取得與解析,傳入URL new DownloadTask().execute("http://data.taipei/opendata/datalist/apiAccess?scope=resourceAquire&rid=e7c46724-3517-4ce5-844f-5a4404897b7d"); } else { mTextView01.setText(getString(R.string.connection_error)); } }); // 重新設定,清除listview上的資料 mButton02.setOnClickListener(new Button.OnClickListener() { // 清除 ListView 與 重新設定上方TextView文字 mTextView01.setText(getString(R.string.app_name)); mListView01.setAdapter(null);
檢查網路是否連線? 相關變數宣告 // 網路 public static String sPref = null; private static boolean wifiConnected = false; private static boolean mobileConnected = false; // 檢查網路狀態,並設定是 wifiConnected 或 mobileConnected // 依據網路狀態改變變數wifiConnected或mobileConnected @Override protected void onStart() { super.onStart(); ConnectivityManager connMgr = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo activeInfo = connMgr.getActiveNetworkInfo(); if (activeInfo != null && activeInfo.isConnected()) { wifiConnected = activeInfo.getType() == ConnectivityManager.TYPE_WIFI; mobileConnected = activeInfo.getType() == ConnectivityManager.TYPE_MOBILE; } else { wifiConnected = false; mobileConnected = false; }
註冊BroadcastReceiver偵測網路狀態 // onResume()時,註冊一BroadcastReceiver (receiver物件),捕捉兩個事件 // 1.ConnectivityManager.CONNECTIVITY_ACTION與2.android.net.conn.CONNECTIVITY_CHANGE // 當發生網路連線事件或網路連線改變事件時,被自訂的NetworkReceiver()接收 @Override protected void onResume() { super.onResume(); IntentFilter filter = new IntentFilter(); filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); filter.addAction("android.net.conn.CONNECTIVITY_CHANGE"); filter.addAction("android.net.wifi.STATE_CHANGE"); receiver = new NetworkReceiver(); this.registerReceiver(receiver, filter); } protected void onPause() { super.onPause(); // 離開程式呼叫onPause(),解除receiver物件註冊 this.unregisterReceiver(receiver);
BroadcastReceiver接收系統廣播訊息 // 偵測網路狀態 public class NetworkReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { ConnectivityManager connMgr = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo networkInfo = connMgr.getActiveNetworkInfo(); if (networkInfo != null && networkInfo.isConnected()) { wifiConnected = networkInfo.getType() == ConnectivityManager.TYPE_WIFI; mobileConnected = networkInfo.getType() == ConnectivityManager.TYPE_MOBILE; } else { wifiConnected = false; mobileConnected = false; } // 網路狀態改變時,以Toast顯示連線狀態 if (wifiConnected || mobileConnected) { refreshDisplay = true; Toast.makeText(context, R.string.wifi_connected, Toast.LENGTH_SHORT).show(); refreshDisplay = false; Toast.makeText(context, R.string.lost_connection, Toast.LENGTH_SHORT).show(); // 失去連線,清除ListView內容 mListView01.setAdapter(null); 相關變數宣告 private NetworkReceiver receiver = new NetworkReceiver(); public static boolean refreshDisplay = true;
連線WebService進行資料取得與解析 Android規定與網路連線相關動作屬於可能耗時與會影響 UI操作的行為 需要透過並行的執行序或非同步(AsyncTask)方式進行 非同步(AsyncTask)方式是透過執行前(onPreExecute)、執 行(doInBackground)與執行後(onPostExecute)三步驟完成動 作
連線WebService進行資料取得與解析 private class DownloadTask extends AsyncTask<String, Void, String> { // 背景中執行下載資料的動作 @Override protected String doInBackground(String... urls) { return downloadUrl(urls[0]); // urls[0]為Web Service URL } // 下載完成後,開始解析JSON protected void onPostExecute(String result) { mTextView01.setText(getString(R.string.str_parsing_ok)); mList = new ArrayList<String>(); // 解析JSON try { // result為原始JSON字串 JSONObject json = new JSONObject(result); // 取得原始資料中的result區段物件 JSONObject json2 = json.getJSONObject("result"); // 取得result區段物件內名稱為results的資料(一個陣列) String data = json2.getString("results"); 相關變數宣告 private ArrayList<String> mList = new ArrayList<String>(); private ArrayAdapter<String> adapter;
// 將上面data陣列分割成可操作 JSONArray dataArray = new JSONArray(data); // 宣告陣列name,用來儲存dataArray內分割出的資料 String[] name = new String[dataArray.length()]; for (int i = 0; i < dataArray.length(); i++) { // 依序取出陣列中名稱為name與addr的資料 // 放入mList陣列表中 mList.add(dataArray.getJSONObject(i).getString("name") + "\n" + dataArray.getJSONObject(i).getString("addr")); } // MainActivity為Activity的名稱 adapter = new ArrayAdapter<String>(MainActivity.this, android.R.layout.simple_list_item_1, mList); // 將資料顯示到ListView mListView01.setAdapter(adapter); mListView01.setOnItemClickListener(new ListView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { Toast.makeText(MainActivity.this, getString(R.string.str_user_choose) + mList.get(position), Toast.LENGTH_SHORT).show(); }); } catch (Exception e) { e.printStackTrace();
連線WEBSERVICE下載資料 private String downloadUrl(String urlString) { String strHTML = ""; try { URL url = new URL(urlString); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setReadTimeout(10000); // milliseconds conn.setConnectTimeout(15000); conn.setRequestMethod("GET"); conn.setDoInput(true); conn.connect(); InputStream stream = conn.getInputStream();
if (stream != null) { int leng = 0; byte[] Data = new byte[100]; byte[] totalData = new byte[0]; int totalLeg = 0; do { leng = stream.read(Data); if (leng > 0) { totalLeg += leng; byte[] temp = new byte[totalLeg]; System.arraycopy(totalData, 0, temp, 0, totalData.length); System.arraycopy(Data, 0, temp, totalData.length, leng); totalData = temp; } while (leng > 0); strHTML = new String(totalData, "UTF-8"); } catch (Exception e) { e.printStackTrace(); return strHTML;
執行
透過SOAP存取
SOAP 這節的內容針對的是MS的Web Service或是使用 SOAP(Simple Object Access Protocol)標準建立的 Web Service 針對其它資料庫或是data provider,可以採用 HTTPPost或是HttpGet (前面OPEN DATA的作法)
透過Ksoap函式庫
加入Soap.java 直接從MainActivity複製 修改AndroidManifest.xml
修改WS呼叫
修改downloadUrl // 執行連線WEBSERVICE下載資料的動作 private String downloadUrl(String urlString) { WSClass ws1 = new WSClass(); // 呼叫WEB SERVICE //依據SQL選擇 String ALLNews = ws1.toWS(urlString); // 第1個參數=SQL指令 Log.i("DEBUG", "allnews=" + ALLNews); return ALLNews; // 回傳整個JSON字串 }
WSClass public class WSClass { private static final String NAMESPACE = "http://tempuri.org/"; private String URL = "http://120.109.34.168/ProjectWS/ProjectWS.asmx"; private static final String METHOD_NAME = "GetData"; private static final String SOAP_ACTION = "http://tempuri.org/GetData"; // 回傳的資料 String receivedString; public String toWS(String sqlstr) { try { SoapObject request = new SoapObject(NAMESPACE, METHOD_NAME); PropertyInfo celsiusPI = new PropertyInfo(); celsiusPI.setName("SqlStr"); // Set Value celsiusPI.setValue(sqlstr); // 要執行的SQL指令,透過參數傳入 // Set dataType celsiusPI.setType(double.class); // Add the property to request object request.addProperty(celsiusPI);
WSClass SoapSerializationEnvelope envelope = new SoapSerializationEnvelope( SoapEnvelope.VER11); envelope.setOutputSoapObject(request); envelope.dotNet = true; HttpTransportSE androidHttpTransport = new HttpTransportSE(URL); androidHttpTransport.call(SOAP_ACTION, envelope); SoapPrimitive Result = (SoapPrimitive) envelope.getResponse(); receivedString = Result.toString(); if (androidHttpTransport != null) { androidHttpTransport.reset(); } } catch (Exception e) { receivedString = "not work"; return receivedString;
result
存取網路既有的WS
SOAP WebService 現有可以被呼叫的WebService 攝氏與華氏轉換 http://www.webservicex.net/ConvertTemperature.asmx
加入CelsiusToFahrenheit.java
<LinearLayout xmlns:android="http://schemas. android android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <TextView android:id="@+id/main_textView1" android:layout_height="wrap_content" android:text="請輸入攝氏溫度" android:textAppearance="?android:attr/textAppearanceLarge" /> <EditText android:id="@+id/inputC" android:ems="10" android:inputType="textPersonName" android:text="" /> <Button android:id="@+id/CtoF" android:text="轉換" /> android:id="@+id/finalresult" android:text="結果" /> </LinearLayout> layout
修改CelsiusToFahrenheit.java
修改CelsiusToFahrenheit.java @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.ctof); mTextView01 = (TextView) findViewById(R.id.finalresult); //結果 CtoF = (Button) findViewById(R.id.CtoF); //解析JSON pramater =(EditText) findViewById(R.id.inputC); // 輸入華氏溫度 // 開始透過WebService取得資料 CtoF.setOnClickListener(new Button.OnClickListener() { public void onClick(View v) { if (refreshDisplay) { // 如果有網路連線 mTextView01.setText(getString(R.string.str_parsing)); // 呼叫DownloadTask連線WebService進行資料取得與解析,傳入URL new DownloadTask().execute(pramater.getText().toString()); } else { mTextView01.setText(getString(R.string.connection_error)); } });
修改DownloadTask與downloadUrl // 連線WebService進行資料取得與解析 private class DownloadTask extends AsyncTask<String, Void, String> { // 背景中執行下載資料的動作 @Override protected String doInBackground(String... urls) { return downloadUrl(urls[0]); // urls[0]為Web Service URL } // 下載完成後,開始解析JSON protected void onPostExecute(String result) { mTextView01.setText("轉換後華氏溫度=" + result); // 執行連線WEBSERVICE下載資料的動作 private String downloadUrl(String urlString) { WSCelsiusToFahrenheit ws1 = new WSCelsiusToFahrenheit(); // 呼叫WEB SERVICE String ALLNews = ws1.tempconvert(urlString); // 第1個參數=攝氏溫度 return ALLNews; // 回傳結果
加入WSCelsiusToFahrenheit.java public class WSCelsiusToFahrenheit { private static final String NAMESPACE = "http://www.webserviceX.NET/" ; private static final String URL="http://www.webserviceX.NET/ConvertTemperature.asmx"; private static final String METHOD_NAME = "ConvertTemp"; private static final String SOAP_ACTION = "http://www.webserviceX.NET/ConvertTemp"; // 回傳的資料 String receivedString; public String tempconvert(String temp){ String receivedString="not work"; //預設回傳值 try { SoapObject request = new SoapObject(NAMESPACE, METHOD_NAME); request.addProperty("Temperature", temp); //傳入攝氏溫度 request.addProperty("FromUnit", "degreeCelsius"); //傳入被轉換單位 request.addProperty("ToUnit", "degreeFahrenheit"); //傳入轉換單位 SoapSerializationEnvelope envelope = new SoapSerializationEnvelope(SoapEnvelope.VER11); envelope.setOutputSoapObject(request); envelope.dotNet = true; HttpTransportSE androidHttpTransport = new HttpTransportSE(URL);
result