android軟件開發--天氣預報

    這兩天開發了一個天氣預報軟件。基本上用到了不少以前學習的內容,而後發現,只有實踐,才能發現更加多的問題,也才能瞭解其中的原理,甚至能夠辨別你之前的知識是不是正確。 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,它還有EditTextPreperenceRingtonePreference,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();
}

7、用BroadcastReceiver實現從service到Activity的通訊

    這只是其中一種方法而已。

    一、建立廣播接收器。(能夠直接在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);
相關文章
相關標籤/搜索