這篇文章是Yahoo天氣API系列文章的第一篇,我們的目標就是使用Yahoo提供的天氣介面獲取當前的氣象情況。之前的兩篇文章已經介紹過了其它的天氣介面,比如openweathermap
介面,如果你感興趣的話,可以看看這兩篇文章文章一和文章二。
這篇文章中,我們主要介紹怎麼用Yahoo的API檢索出各個城市資訊。假設你已經有了一個Yahoo開發者賬戶。如果還沒有,那麼可以通過這個連結註冊一個(註冊連結)。你必須擁有一個appid,雖然它是完全免費的,但是你在使用yahoo的API的時候必須用上它。在分析Yahoo的API的時候,我們順便介紹一些有意思的Android上的控制元件比如AutoCompleteTextView和android上的XML解析器。我們最後要實現一個Android APP,當使用者輸入一部分城市的名字時,就可以顯示出所有和使用者輸入所匹配的城市選項,如下圖所示:
Yahoo Woeid
獲得天氣資訊的第一步就是檢索Woeid,這是Yahoo提供給開發者的一個特殊的ID,用來分辨城市/地區資訊。我們需要根據使用者輸入的城市名稱來獲得這個woeid。
從介面的角度上看,我們希望通過使用者輸入的城市名稱或者城市名稱的部分,加上對應的woeid來獲取與之匹配的城市資訊列表,我們可以使用下面的API來獲取匹配某個公式的城市列表資訊。
http://where.yahooapis.com/v1/places.q(city_name_pattern);count=MAX_RESULT_SIZE?appid=your_app_id
如果你用瀏覽器來執行這個API,那麼你就可以得到一個xml檔案,即匹配city_name_pattern這個式子的城市資訊列表。
在Android中解析Yahoo的xml資料
現在我們要建立一個XML解析器來處理上一步中我們獲取到的資料。首先我們要新建一個資料model(MVC中的模型,也就是javaBean),對於我們這個例子來說,很簡單:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
; html-script: false ] public class CityResult { private String woeid; private String cityName; private String country; public CityResult() {} public CityResult(String woeid, String cityName, String country) { this.woeid = woeid; this.cityName = cityName; this.country = country; } // get and set methods @Override public String toString() { return cityName + "," + country; } } |
然後新建一個名為YahooClient
的類進行解析。這個類主要負責遍歷xml資料,然後進行轉換。裡面有一個靜態方法可以接收一個模式(pattern),通過匹配這個模式可以獲取城市資訊列表。一開始,先要開啟一個HTTP連線獲取資料流資訊,然後把這個資料流資訊傳給這個XML解析器,如下:
1 2 3 4 5 6 |
; html-script: false ] yahooHttpConn= (HttpURLConnection) (new URL(query)).openConnection(); yahooHttpConn.connect(); XmlPullParser parser = XmlPullParserFactory.newInstance().newPullParser(); parser.setInput(new InputStreamReader(yahooHttpConn.getInputStream())); |
接下來可以開始轉換資料了。根據我剛才新建的模型類,可以從xml中尋找我們需要的資訊,我們需要Woeid、城市的名字和地區資訊,xml檔案中還有其它不需要關心的資訊,我們可以不用理會。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
; html-script: false ] int event = parser.getEventType(); CityResult cty = null; String tagName = null; String currentTag = null; // We start parsing the XML while (event != XmlPullParser.END_DOCUMENT) { tagName = parser.getName(); if (event == XmlPullParser.START_TAG) { if (tagName.equals("place")) { // place Tag Found so we create a new CityResult cty = new CityResult(); Log.d("Swa", "New City found"); } currentTag = tagName; Log.d("Swa", "Tag ["+tagName+"]"); } else if (event == XmlPullParser.TEXT) { // We found some text. let's see the tagName to know the tag related to the text if ("woeid".equals(currentTag)) cty.setWoeid(parser.getText()); else if ("name".equals(currentTag)) cty.setCityName(parser.getText()); else if ("country".equals(currentTag)) cty.setCountry(parser.getText()); // We don't want to analyze other tag at the moment } else if (event == XmlPullParser.END_TAG) { if ("place".equals(tagName)) result.add(cty); } event = parser.next(); } |
這段程式碼很簡單。第一行獲取第一個xml事件,然後開始遍歷xml資料檔案直到到達文件末尾。在這個方法結束的時候,就可以獲取我們想要得到的城市列表資料了。
AutoCompleteTextView和帶過濾器的陣列介面卡
在我們知道怎麼利用Yahoo的API從xml檔案中獲取資料後,我們就要向使用者展示這些資料。展示資料有很多中方式,我們使用AutoCompleteTextView
。這個控制元件在Android文件中是這樣定義的: “AutoCompleteTextView是一個可編輯文字檢視(View)。當歐諾個戶輸入時會自動提示符合條件的備選項。提示資訊在下拉選單中顯示。使用者可以選中其中一項替換當前的編輯框內容。”,正好符合我們的需求。使用這個控制元件不難,但是使用陣列介面卡加上過濾操作就有點複雜了。通常來說,使用這個控制元件時都是使用靜態的資料,而現在我們需要從遠端伺服器檢索資料。首先要實現一個自定義的介面卡,繼承 ArrayAdapter
十分簡單,示例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
; html-script: false ] private class CityAdapter extends ArrayAdapter<CityResult> { private Context ctx; private List<CityResult> cityList = new ArrayList<CityResult>(); public CityAdapter(Context ctx, List<CityResult> cityList) { super(ctx, R.layout.cityresult_layout, cityList); this.cityList = cityList; this.ctx = ctx; } @Override public CityResult getItem(int position) { if (cityList != null) return cityList.get(position); return null; } @Override public int getCount() { if (cityList != null) return cityList.size(); return 0; } @Override public View getView(int position, View convertView, ViewGroup parent) { View result = convertView; if (result == null) { LayoutInflater inf = (LayoutInflater) ctx.getSystemService(Context.LAYOUT_INFLATER_SERVICE); result = inf.inflate(R.layout.cityresult_layout, parent, false); } TextView tv = (TextView) result.findViewById(R.id.txtCityName); tv.setText(cityList.get(position).getCityName() + "," + cityList.get(position).getCountry()); return result; } @Override public long getItemId(int position) { if (cityList != null) return cityList.get(position).hashCode(); return 0; } } |
注意:最重要的是我們要從遠端伺服器檢索資料,然後可以通過實現Filterable
介面來處理我們希望得到的資料,所以程式碼還需要:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
; html-script: false ] private class CityAdapter extends ArrayAdapter<CityResult> implements Filterable { .... @Override public Filter getFilter() { Filter cityFilter = new Filter() { @Override protected FilterResults performFiltering(CharSequence constraint) { FilterResults results = new FilterResults(); if (constraint == null || constraint.length() < 2) return results; List<CityResult> cityResultList = YahooClient.getCityList(constraint.toString()); results.values = cityResultList; results.count = cityResultList.size(); return results; } @Override protected void publishResults(CharSequence constraint, FilterResults results) { cityList = (List) results.values; notifyDataSetChanged(); } }; return cityFilter; } .. } |
第四行我們實現了Filter
介面,有兩個方法需要實現,在performFiltering
方法中我們執行HTTP呼叫然後檢索資料。顯然我們這樣會導致ANR問題,因為我們知道不可以在主執行緒中傳送HTTP請求。但是,如果你閱讀performFiltering
方法的文件的話,就會發現這個方法其實是在另外的執行緒中執行的。所以我們不用擔心這個問題。
最後,我們為AutoCompleteTextView設定介面卡然後處理它的使用者點選事件:
1 2 3 4 5 6 7 8 9 10 |
; html-script: false ] AutoCompleteTextView edt = (AutoCompleteTextView) rootView.findViewById(R.id.edtCity); CityAdapter adpt = new CityAdapter(this.getActivity(), null); edt.setAdapter(adpt); edt.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // We handle the onclick event and select the city chosen by the user } }); |
在下一篇文章中我們再介紹怎麼使用Woeid來獲取天氣資料,請繼續期待吧。
本文的原始碼很快就會發布。