android软件开发--天气预报

此生再无相见时 提交于 2020-02-29 17:22:14

    这两天开发了一个天气预报软件。基本上用到了很多之前学习的内容,然后发现,只有实践,才能发现更加多的问题,也才能了解其中的原理,甚至可以辨别你以前的知识是否是正确。

    本来我想把源码发上来的,但是发现没有添加附件的功能。只有通过代码分享了。http://www.oschina.net/code/snippet_1016021_21811

    界面比较简单,主要是实现功能。


    程序说明

    1、进入程序之后,可以通过点击城市的名字来设置当前城市。(一开始默认为广州)

    2、进入设置城市界面之后,省市的选择为级联下拉列表。可以选择点击保存按钮,则会返回主界面,并且更新当前城市为你所选的值。也可以选择取消,则直接返回主界面。

    3、点击Menu,可以进入设置界面对查询天气以及附带信息进行选择,或者可以选择退出程序。

    4、点击查询按钮完成查询。

    学习要点

一、android工程正确导入jar包(MyEclipse下)

   这个工程要用到SOAP技术,所以要导入ksoap2-android-assembly-3.0.0-jar-with-dependencies。根据以前的做法,一般都是直接新建一个lib目录,把jar包复制进去,然后右键,接着Build path。但是在android工程中,这样做是不正确的。会出现红叉或者叹号。

     正确的做法是:

    1、右键工程, Build path

    2、点击“Add Libraries”

    3、选择“User library”,点击“下一步”

    4、点击“User librarys”按钮在出现的界面中点击“New..”按钮。在弹出的界面中随便起一个名字,点击“确定”

    5、点击“Add jars”按钮选择第三方jar包,点击“确定”完成操作。这样该jar包会被一起打包到apk中。

二、省市下拉列表实现二级级联

    首先将省市信息以<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) {
	}
});
    我不知道还有没有更加方便方法,不过我的这个方法在我这边确实可行。形成界面为上方图二。


三、SQLite保存城市数据

   使用SQLite而不使用Intent传递参数,是因为当用户下次打开程序时,当前城市应该为TA最后一次的选择。关于SQLite的使用,网上有很多文章,比如:http://52android.blog.51cto.com/2554429/478368 之前也学习过一些,但发现看懂跟实际编程还是有很大差距的。特别是数据库的关闭以及Cursor的关闭,出现了好些问题。在这一块要仔细一点。

四、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数据。可 通过下面三种方式:

    1、getPreferences():可以获取同一activity中的preference;

    2、getSharedPreferences():可以获取应用级别的preferences,即封装在同一app中,使用SharePreferences prefs = getSharedPreferences(packName+name ,0)

    3、getDefaultSharedPreferences():通过Android的管理器来获取其所管理的preferences。

   由于这里不是同一个Activity,所以不能使用getPreferences()。我这里只有一个preference,因此使用PreferenceManager.getDefaultSharedPreferences(this);来获取较方便。

五、通过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;
	}
}

六、开启service处理联网操作

    我一开始直接在MainActivity中联网加载天气信息,不过,发现在点击查询按钮到信息显示出来之前,界面时卡死的,而且容易出现ANR(程序无法响应异常)。所以我就想通过service来处理。在我的记忆里,service是作为后台运行的,而且网上大部分的文章,都有提到耗时操作要使用service。可以当我真的这么做的时候,我才发现,界面依旧卡住了。

    我当时就纳闷了,怎么会这样呢?难道service开启后,会在主线程运行?查了资料后发现,果真是如此。这是才发现,在service里面还要开启一个线程来执行耗时操作。咦?如果是这样的话,那我还不如直接在MainActivity中另开线程,这样不是更加方便吗,还省去了Activity与Service之间的通信的麻烦。网上有些大神说service有它的生命周期,更加方便管理,以及还有其他一些优点。恩恩,确实吧。不过就我的程序而言,我觉得直接在MainActivity中另开线程获取天气,然后通过Handler更新UI显示天气可能会更加方便。(我最后还是使用了service,因为可以学到更多的东西)

    现在来说创建service的过程

     1、新建类继承Service;

    2、必须重写onBind方法(如果你通过bindService方法启动service,则在这个方法内执行操作

    3、重写onStart方法(由于本程序中,每次点击查询按钮,service就要进行联网操作,因此我通过startService方法启动service,则每次startService,都会执行onStart方法。注意:在service停止前,onCreate只会执行一次

    4、在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类似。

    5、在需要启动service的地方,添加一下代码

Intent intent =new Intent("WeatherService");
//传递数据,可以由onStart接收
intent.putExtra("city", city);
this.startService(intent);

    6、如果是通过bindService启动service,则可以不执行unbindService。因为只要程序退出,service也将被摧毁。但是,如果是通过startService启动service,则必须通过stopService将其停止,否则即使程序退出,service依旧在运行。我们可以在MainActivity的onDestroy中执行stopService。

protected void onDestroy() {
	//停止service
	stopService(new Intent("WeatherService"));
	super.onDestroy();
}

七、用BroadcastReceiver实现从service到Activity的通信

    这只是其中一种方法而已。

    1、创建广播接收器。(可以直接在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);
		}
	}
}
    2、通过代码动态注册广播接收器。(也可以在AndroidManifest中添加<receiver>属性

//注册广播接收器
IntentFilter filter = new IntentFilter();
myBroadcastRecever = new MyBroadcastRecever();
//设置接收广播的类型,这里要和Service里设置的类型匹配,还可以在AndroidManifest.xml文件中注册 
//BROADCAST_ACTION=“某个自定义字符串”。如果有多个广播,则要唯一
filter.addAction(BROADCAST_ACTION);
registerReceiver(myBroadcastRecever, filter);
    3、通过广播发送消息

Intent i = new Intent();
i.putExtra("weather", weather);
//BROADCAST_ACTION与注册时的字符串一致
i.setAction(BROADCAST_ACTION);
sendBroadcast(i);



易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!