這兩天開發了一個天氣預報軟件。基本上用到了不少以前學習的內容,而後發現,只有實踐,才能發現更加多的問題,也才能瞭解其中的原理,甚至能夠辨別你之前的知識是不是正確。 java
原本我想把源碼發上來的,可是發現沒有添加附件的功能。只有經過代碼分享了。http://www.oschina.net/code/snippet_1016021_21811 android
界面比較簡單,主要是實現功能。 web
程序說明 數據庫
一、進入程序以後,能夠經過點擊城市的名字來設置當前城市。(一開始默認爲廣州) 編程
二、進入設置城市界面以後,省市的選擇爲級聯下拉列表。能夠選擇點擊保存按鈕,則會返回主界面,而且更新當前城市爲你所選的值。也能夠選擇取消,則直接返回主界面。 數組
三、點擊Menu,能夠進入設置界面對查詢天氣以及附帶信息進行選擇,或者能夠選擇退出程序。 網絡
四、點擊查詢按鈕完成查詢。 app
學習要點 ide
1、android工程正確導入jar包(MyEclipse下) 學習
這個工程要用到SOAP技術,因此要導入ksoap2-android-assembly-3.0.0-jar-with-dependencies。根據之前的作法,通常都是直接新建一個lib目錄,把jar包複製進去,而後右鍵,接着Build path。可是在android工程中,這樣作是不正確的。會出現紅叉或者歎號。
正確的作法是:
一、右鍵工程, Build path
二、點擊「Add Libraries」
三、選擇「User library」,點擊「下一步」
四、點擊「User librarys」按鈕在出現的界面中點擊「New..」按鈕。在彈出的界面中隨便起一個名字,點擊「肯定」
五、點擊「Add jars」按鈕選擇第三方jar包,點擊「肯定」完成操做。這樣該jar包會被一塊兒打包到apk中。
2、省市下拉列表實現二級級聯
首先將省市信息以<string-array>的形式保存到名爲arrays.xml的文件中(我記得貌似必定要把文件名取爲arrays.xml)。其中,name屬性能夠理解爲數組名和ID名。這裏要注意:省份的順序要與對應擁有的城市順序一致。即臺灣爲最後最後一個省,那麼它的對應城市也要寫在最後。(下面會有解釋)
<resources> <string-array name="provinces"> <item>--請選擇--</item> <item>北京</item> <item>天津</item> ... <item>海南省</item> <item>臺灣省</item> </string-array> <string-array name="beijing_array"> <item>北京</item> </string-array> ... <string-array name="taiwan_array"> <item>臺北</item> <item>高雄</item> <item>臺中</item> </string-array> </resources>而後,在選擇城市界面對應的Activity中,經過下面代碼將省份列表顯示。其中R.array.provinces就是咱們上面定義的name屬性值。
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this,R.array.provinces,android.R.layout.simple_spinner_item); provinceSpinner.setAdapter(adapter);接着,對省份下拉列表進行監聽。這裏有一個比較麻煩的地方,由於當你選擇不一樣的省份的時候,須要顯示該省份對應的城市。面對那麼多的省份,若是咱們經過if或者switch來操做的話,使得代碼很冗長,也難以維護。我一開始想在網上找答案,可是沒有發現好的想法,甚至連相關的實例都沒有。不過,後來我發現這裏是經過R.array.name這種形式來顯示下拉列內容的。因而,我經過觀察R文件,發現了必定的規律。R文件中的array類的int屬性值,是根據咱們寫入順序,從0x7f050000開始,逐個+1造成的。即
public static final int provinces=0x7f050000; public static final int beijing_array=0x7f050001; public static final int tianjin_array=0x7f050002;可能R文件中沒有按照此順序排列,不過,不影響這一性質。因此我就想到了只要城市數組的順序與省份一一對應(上面提到過),就能夠經過所選省份的position,跟ID初始值 0x7f050000相加,得出所屬城市的數組。具體看看代碼
provinceSpinner.setOnItemSelectedListener(new OnItemSelectedListener() { public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { if(position != 0){//選擇了省份,position=0時,爲「--請選擇--」 /*這是一個小技巧 *0x7f050000爲R文件中省份數組對應的id值,只要加上position,便可得到對應選項(省份)的城市 *若是不是用這個方法,可能就要用一大堆的判斷語句來級聯城市 */ int cityID = 0x7f050000 + position; ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(getApplicationContext(), cityID, android.R.layout.simple_spinner_item); citySpinner.setAdapter(adapter); } } public void onNothingSelected(AdapterView<?> parent) { } });我不知道還有沒有更加方便方法,不過個人這個方法在我這邊確實可行。造成界面爲上方圖二。
3、SQLite保存城市數據
使用SQLite而不使用Intent傳遞參數,是由於當用戶下次打開程序時,當前城市應該爲TA最後一次的選擇。關於SQLite的使用,網上有不少文章,好比:http://52android.blog.51cto.com/2554429/478368 以前也學習過一些,但發現看懂跟實際編程仍是有很大差距的。特別是數據庫的關閉以及Cursor的關閉,出現了好些問題。在這一塊要仔細一點。
4、PreferenceActivity做爲設置界面
參照Android系統的設置,用PreferenceActivity來對系統進行信息配置和管理。這裏我也採用PreferenceActivity做爲設置界面。(上方圖三)
首先,編寫xml文件。PreferenceCategory:類別(用於分組)。key:惟一標識(獲取信息時使用)。title:顯示標題。summary:小標題。還有defaultValue:默認值。我這裏值用到了CheckBoxPreference,它還有EditTextPreperence,RingtonePreference,ListPreference,Preference等。
<?xml version="1.0" encoding="utf-8"?> <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" > <PreferenceCategory android:title="信息選擇" > <CheckBoxPreference android:key="threeDay" android:summary="今明後三天的天氣預報,若是不選,則只有當天的天氣" android:title="三天預報" /> <CheckBoxPreference android:key="cityInfo" android:summary="關於當前城市的簡要介紹" android:title="城市簡介" /> </PreferenceCategory> </PreferenceScreen>而後,新建Activity繼承 PreferenceActivity ,重寫onCreate方法,經過 addPreferencesFromResource(R.xml.xx); 加載Preference。
public class SetupActivity extends PreferenceActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); addPreferencesFromResource(R.xml.setup); } }最後,獲取preference數據。可 經過下面三種方式:
一、getPreferences():能夠獲取同一activity中的preference;
二、getSharedPreferences():能夠獲取應用級別的preferences,即封裝在同一app中,使用SharePreferences prefs = getSharedPreferences(packName+name ,0)
三、getDefaultSharedPreferences():經過Android的管理器來獲取其所管理的preferences。
因爲這裏不是同一個Activity,因此不能使用getPreferences()。我這裏只有一個preference,所以使用PreferenceManager.getDefaultSharedPreferences(this);來獲取較方便。
5、經過WebService獲取天氣信息
WebService獲取天氣的網址爲:http://www.webxml.com.cn/webservices/weatherwebservice.asmx 上面有較爲詳細的介紹,以及相關圖標的下載。
我這裏經過SOAP技術獲取天氣信息。
// 保存獲取到的信息 SoapObject detail = null; // 1.實例化SoapObject對象 SoapObject soapObject = new SoapObject(NAMESPACE, METHOD_NAME); // 2.若是方法須要參數,設置參數 soapObject.addProperty("theCityName", cityName); // 3.設置Soap的請求信息,得到序列化envelope,參數部分爲Soap協議的版本號 SoapSerializationEnvelope envelope = new SoapSerializationEnvelope( SoapEnvelope.VER11); envelope.bodyOut = soapObject; envelope.dotNet = true; envelope.setOutputSoapObject(soapObject); // 4.構建傳輸對象 int timeout = 10000;// 設置超時爲10秒 MyAndroidHttpTransport httpTransportSE = new MyAndroidHttpTransport(URL, timeout); httpTransportSE.debug = true; // 5.訪問WebService,第一個參數爲命名空間 + 方法名,第二個參數爲Envelope對象 httpTransportSE.call(SOAP_ACTION, envelope); detail = (SoapObject) envelope.getResponse();// 獲取詳細天氣信息 if (detail != null) {// 當前城市有天氣信息 return parseWeather(detail);//解析天氣 }這裏要注意一下,代碼18行 MyAndroidHttpTransport爲繼承了HttpTransportSE的內部類。雖然ksoap2版本中的HttpTransportSE已經能夠設置timeout(超時時間),可是運行後發現沒有效果。查找資料後,才知道HttpTransportSE的源碼中並無把timeout做爲參數傳遞給ServiceConnectionSE。所以咱們須要建立一個類,使得timeout起做用。
class MyAndroidHttpTransport extends HttpTransportSE { private int timeout = 20000; // 默認超時時間爲20s public MyAndroidHttpTransport(String url) { super(url); } public MyAndroidHttpTransport(String url, int timeout) { super(url); this.timeout = timeout; } //此方法使得超時有效 public ServiceConnection getServiceConnection() throws IOException { ServiceConnectionSE serviceConnection = new ServiceConnectionSE(this.url,timeout); return serviceConnection; } }
6、開啓service處理聯網操做
我一開始直接在MainActivity中聯網加載天氣信息,不過,發如今點擊查詢按鈕到信息顯示出來以前,界面時卡死的,並且容易出現ANR(程序沒法響應異常)。因此我就想經過service來處理。在個人記憶裏,service是做爲後臺運行的,並且網上大部分的文章,都有提到耗時操做要使用service。能夠當我真的這麼作的時候,我才發現,界面依舊卡住了。
我當時就納悶了,怎麼會這樣呢?難道service開啓後,會在主線程運行?查了資料後發現,果然是如此。這是才發現,在service裏面還要開啓一個線程來執行耗時操做。咦?若是是這樣的話,那我還不如直接在MainActivity中另開線程,這樣不是更加方便嗎,還省去了Activity與Service之間的通訊的麻煩。網上有些大神說service有它的生命週期,更加方便管理,以及還有其餘一些優勢。恩恩,確實吧。不過就個人程序而言,我以爲直接在MainActivity中另開線程獲取天氣,而後經過Handler更新UI顯示天氣可能會更加方便。(我最後仍是使用了service,由於能夠學到更多的東西)
如今來講建立service的過程
一、新建類繼承Service;
二、必須重寫onBind方法(若是你經過bindService方法啓動service,則在這個方法內執行操做)
三、重寫onStart方法(因爲本程序中,每次點擊查詢按鈕,service就要進行聯網操做,所以我經過startService方法啓動service,則每次startService,都會執行onStart方法。注意:在service中止前,onCreate只會執行一次)
四、在AndroidManifest.xml文件中添加
<service android:name="className" > <intent-filter > <action android:name="serviceName" /> </intent-filter> </service>className爲類名全稱:如vaint.wyt .service.WeatherService。若是跟MainActivity在同一個包,能夠直接寫 .WeatherService。
serviceName爲startService(new Intent(String action))的action,bindService相似。
五、在須要啓動service的地方,添加一下代碼
Intent intent =new Intent("WeatherService"); //傳遞數據,能夠由onStart接收 intent.putExtra("city", city); this.startService(intent);
六、若是是經過bindService啓動service,則能夠不執行unbindService。由於只要程序退出,service也將被摧毀。可是,若是是經過startService啓動service,則必須經過stopService將其中止,不然即便程序退出,service依舊在運行。咱們能夠在MainActivity的onDestroy中執行stopService。
protected void onDestroy() { //中止service stopService(new Intent("WeatherService")); super.onDestroy(); }
這只是其中一種方法而已。
一、建立廣播接收器。(能夠直接在MainActivity中做爲內部類建立)重寫onReceive方法,接收從service傳遞過來的天氣信息。
//定義一個廣播接收器,用於接收Service得到的天氣信息 class MyBroadcastRecever extends BroadcastReceiver{ @Override public void onReceive(Context context, Intent intent) { String[] weatherInfo = intent.getStringArrayExtra("weather"); if(weatherInfo==null){ Toast.makeText(MainActivity.this, "沒有當前城市的天氣信息", 1000).show(); }else if(weatherInfo.length==1){//即weatherInfo = new String[]{"timeOut"}; Toast.makeText(MainActivity.this, "鏈接超時,請檢查網絡", 1000).show(); }else{ showWeather(weatherInfo); } } }二、經過代碼動態註冊廣播接收器。(也能夠在AndroidManifest中添加<receiver>屬性 )
//註冊廣播接收器 IntentFilter filter = new IntentFilter(); myBroadcastRecever = new MyBroadcastRecever(); //設置接收廣播的類型,這裏要和Service裏設置的類型匹配,還能夠在AndroidManifest.xml文件中註冊 //BROADCAST_ACTION=「某個自定義字符串」。若是有多個廣播,則要惟一 filter.addAction(BROADCAST_ACTION); registerReceiver(myBroadcastRecever, filter);三、經過廣播發送消息
Intent i = new Intent(); i.putExtra("weather", weather); //BROADCAST_ACTION與註冊時的字符串一致 i.setAction(BROADCAST_ACTION); sendBroadcast(i);