安卓教程----手把手教你做一个河北空气质量客户端

从零开始,我们来做一个河北省空气质量自动发布系统的客户端,这这个软件的制作过程中,我会介绍关于信息获取异步获取网络数据数据分析界面设计程序逻辑等内容,下面介绍一个完整的程序是如何做出来的。文章面向0基础的、只看过一点安卓教程的同学,对于比较基础的内容,也会用红字的链接标出,大家可以点开看详细的介绍。

其实做这个,完全是因为老爸的原因,河北的空气质量太差了,所以他决定天天根据空气质量来决定散步不散步。总是上这个网站过于复杂,于是我就有了做一个客户端的想法。

首先需要找到程序的数据源,找到从网上获得数据接口的网址。

其次,要把数据从网上的格式,转换成我们可以使用的格式。

接下来进行布局的设计,最后把数据填充到布局里,整个程序就完成了。

下面是这个系统的网站,和我做的客户端:



1、数据获取

想做这个软件我们先要有数据源,数据是河北省环境监测中心给出的,我们现在要找到它的接口。
打开网址: http://121.28.49.85:8080/ 我们可以看到这是一个flash做的页面,而且有明显的加载过程,说明浏览器获取过数据。我们使用HttpAnalyze或者Smsniff来查看浏览器发送出去的数据包,当然最方便的是使用Chrome的功能。
打开Chrome --> F12 --> 选择NetWork标签 --> 打开上面的网络地址,下面会出现很多条请求的数据,我们按Size排序后找最大的,就是我们需要的数据。如下图:


发送的请求的地址


得到的回应

如上图所示:打开网页后浏览器发送了若干条数据,其中有一条远大于其它数据的包,大小为59.75k,我们可以认为这就是数据的来源了,而我们看到它指向了网址 http://121.28.49.85:8080/datas/hour/130000.xml。在回复中,发现编码是UTF-8的编码。 打开这个网址,我们可以看到如下图所示的XML数据:


下面我们就以上面的数据为基础,做一个客户端。

2、异步信息获取

2.1 新建一个Android项目

打开一个配置好ADT(Android Developer Tools)的Eclipse(如果没有配置好点这个教程),选择File --> New --> Android Application Project,在Application Name里给程序起一个名字比如HebeiAir,然后在最小需求SDK为API14(低一点其实也不影响),其它保持默认,确定。
建立好以后我们的程序至少会有下面这些文件:


2.2 异步获取网络数据

在第一章里,我们找到了获取数据的网址,在这里,我们要把这个网址的数据抓下来供我们使用。在src包里建立一个新的Class,名字定为Util,在里面定义一个新的静态方法:HttpGet,这个方法可以模拟浏览器的访问,我们输入参数是网址,这个函数返回得到的网页源代码:

	public static String HttpGet(String url) throws ClientProtocolException, IOException {
		//新建一个默认的连接
		DefaultHttpClient client = new DefaultHttpClient();
		//新建一个Get方法
		HttpGet get = new HttpGet(url);
		//得到网络的回应
		HttpResponse response = client.execute(get);
		//获得的网页源代码(xml)
		String content = null;

		//如果服务器响应的是OK的话!
		if (response.getStatusLine().getStatusCode() == 200) {
			//以下是把网络数据分段读取下来的过程
			InputStream in = response.getEntity().getContent();
			byte[] data = new byte[1024];
			int length = 0;
			ByteArrayOutputStream bout = new ByteArrayOutputStream();
			while ((length = in.read(data)) != -1) {
				bout.write(data, 0, length);
			}
			//最后把字节流转为字符串 转换的编码为utf-8.
			content = new String(bout.toByteArray(), "utf-8");
		}
		//返回得到的字符串 也就是网页源代码
		return content;
	}

在上面的Chrome信息窗口中看到,返回值是utf-8编码的,所以在这里我们使用了utf-8编码,如果这里我们使用"gbk"或者"gb-2312"就会出现乱码,每个网站的编码都不相同,具体情况要具体分析。

要注意的是,在我们的程序里并不能直接使用这个函数,因为Android 4.0中,主线程不能进行网络操作,所以我们需要开启一个新的线程。在Android中,有一个成熟的类:AsyncTask(异步任务),可以完成这项工作(Thread + Handler也是一种方法,由于我们的工作比较简单,暂时不提及它们)。

接下来我们尝试把XML源代码显示出来
在MainActivity类中新建一个类GetSource,这个类继承AsyncTask,用来获取网页上的数据;得到数据后使用Logcat(可以理解为Android上的控制台)打印出来:
	class GetSource extends AsyncTask<String, Void, String>{
		
		//此函数用来处理后台的事物
		@Override
		protected String doInBackground(String... params) {
			try {
				//这里调用了我们刚才写的下载函数
				return Util.HttpGet("http://121.28.49.85:8080/datas/hour/130000.xml");
			} catch (IOException e) {}
			return null;
		}
		
		//后台事物完成后,此函数用来更改界面的内容
		@Override
		protected void onPostExecute(String result) {
			//让Log输出运行时的记录
			Log.i("test",result);
		}
		
	}

最后在OnCreate函数的最后一行加上下面一句,再添加网络权限,然后我们来看看效果吧!
new GetSource().execute();

3、界面设计

我们的目标是把软件设计成文章开始时的那个样式,这样我们就要简单地修改一下activity_main.xml:
·在上方放置一个TextView,背景为浅绿色,文字颜色为白色;
·下方是一个GridView,其中每个格子的颜色根据空气质量来变化,格子中上方显示城市名,下方显示当前的AQI。
·同时在整个界面上还要显示一个转圈的进度条ProgressBar,加载的时候显示这个进度条
代码如下:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity" >

        <LinearLayout
            android:id="@+id/ll"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:visibility="gone"
            android:orientation="vertical" >

            <TextView
                android:id="@+id/tv_time"
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:layout_margin="5dp"
                android:background="#7a7"
                android:padding="5dp"
                android:textColor="#eee"
                android:textSize="20sp"
                android:textStyle="bold" />

            <GridView
                android:id="@+id/gv"
                android:layout_width="match_parent"
                android:layout_height="fill_parent"
                android:layout_margin="5dp"
                android:horizontalSpacing="3dp"
                android:verticalSpacing="3dp"
                android:numColumns="3" >
            </GridView>
        </LinearLayout>

        <ProgressBar
            android:id="@+id/pb1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerHorizontal="true"
            android:visibility="visible"
            android:layout_centerVertical="true" />

</RelativeLayout>

GridView 是一种网格样式布局,在上面的代码里我设置的每行格子个数为3个,还设置了格子之间的间距。每个格子中的布局需要另一个文件来控制:
gv.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/bg"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="8dp"
    android:orientation="vertical" >

    <TextView
        android:id="@+id/tv_city"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="石家庄"
        android:textColor="#4bd"
        android:textSize="20sp"
        android:textStyle="bold" />

    <TextView
        android:id="@+id/tv_aqi"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="223"
        android:textColor="#4bd"
        android:textSize="18sp"
        android:textStyle="bold" />

</LinearLayout>

4、数据分析

4.1 解析XML数据

网上获得的XML数据需要转换成我们可以使用的结构化数据,这就使用了基于DOM的XML解析器。更改GetSource中的OnPostExecute中的代码为:

		@Override
		protected void onPostExecute(String result) {
			//建立一个解析器
			DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
			DocumentBuilder builder;
			try {
				builder = factory.newDocumentBuilder();
				InputStream is = new ByteArrayInputStream(result.getBytes());
				Document document = builder.parse(is);
				Element element = document.getDocumentElement();
				//获得所有的Citys节点数据
				NodeList cityList = element.getElementsByTagName("Citys");
				
				//获得mapstitle数据,并分解为两部分
				NodeList title = element.getElementsByTagName("MapsTitle");
				String text1 = title.item(0).getTextContent();
				String t[] = text1.split("\\(");
				text1 = t[0];
				t = t[1].split(",");
				tv_time.setText(text1+ "\n" +t[0]);
				
				Element citys = (Element)cityList.item(0);
				NodeList city = citys.getChildNodes();
				
				for (int i=0;i < city.getLength();i++){
					//此时的city节点的item上,有的是一个城市的所有数据
					Node node = city.item(i);
					if (node.getNodeName().equalsIgnoreCase("city")) { //这是一个有效的节点
						CityData cd = new CityData(node);
						nodeList.add(node);
						cdList.add(cd);
					}
				}
			} catch (Exception e) {}

这样每个城市的数据就成为了一个Node,解析XML的过程比较复杂,需要不断地去尝试。

4.2 应用数据到布局

接下来要编写一个Adapter来连接布局和代码。我们假设每一个每一个城市的数据都是一个Node(节点),GridView的数据存在一个列表里,格子的个数与列表的长度有关。(所以如果河北省城市监测点变多了,程序也可以进行变化而自适应)分析数据源的XML可以得到,我们需要的是其中城市,和每个城市中监测点的列表。所以,在这里新建两个类:CityData、Pointer,其中,CityData包括一个Pointer的列表。以下是CityData类,Pointer类与这个相似:
public class CityData implements Serializable{
	
	/**
	 * 继承Serializable是为了在两个不同的界面中进行值的传递
	 */
	private static final long serialVersionUID = -8473485404751986234L;
	//城市类包含了一个城市的数据
	public String name,dataTime,aqi,level,maxPoll,color,intro,tips;
	public List<Pointer> pointerList;

	public CityData(Node cityNode) {
		super();
		//按标签挨个取出相应标签的内容
		this.name = getByTag(cityNode, "name");
		this.dataTime = getByTag(cityNode, "datatime");
		this.aqi = getByTag(cityNode, "aqi");
		this.level = getByTag(cityNode, "level");
		this.maxPoll = getByTag(cityNode, "maxpoll");
		String tmp = getByTag(cityNode, "color");
		this.color = tmp.replace("0x", "#");
		this.intro = getByTag(cityNode, "intro");
		this.tips = getByTag(cityNode, "tips");
		
		Element city = (Element)cityNode;
		NodeList pointers = city.getElementsByTagName("Pointer");
		
		//向city的pointer列表中添加监测点
		pointerList = new ArrayList<Pointer>();
		for (int i=0;i<pointers.getLength();i++){
			Node pNode = pointers.item(i);
			pointerList.add(new Pointer(pNode));
		}
	}

	//从XML的node中取出相应标签中内容的function
	private String getByTag(Node node,String tag) {
		for (int i=0;i<node.getChildNodes().getLength();i++){
			if (tag.equalsIgnoreCase(node.getChildNodes().item(i).getNodeName()))
				return node.getChildNodes().item(i).getTextContent();
		}
		return null;
	}
}

负责把数据填充到布局的Adapter:
	class gvAdapter extends BaseAdapter{

		//Adapter的数据源,根据这个数据来填充网格列表
		List<CityData> cdList;
		
		public gvAdapter(List<CityData> cdList) {
			super();
			this.cdList = cdList;
		}

		//根据数据的多少返回相应的数值
		@Override
		public int getCount() {
			return cdList.size();
		}

		@Override
		public Object getItem(int position) {
			return null;
		}

		@Override
		public long getItemId(int position) {
			return 0;
		}

		@Override
		public View getView(int position, View convertView, ViewGroup parent) {
			//解析之前写好的每个网格的布局
			convertView = MainActivity.this.getLayoutInflater().inflate(R.layout.gv, null);
			//找到布局中的元素,和布局的背景
			TextView tv_city = (TextView)convertView.findViewById(R.id.tv_city);
			TextView tv_aqi = (TextView)convertView.findViewById(R.id.tv_aqi);
			View bg = convertView.findViewById(R.id.bg);
			//根据数据填充每个格子的内容和背景色
			tv_city.setText(cdList.get(position).name);
			tv_aqi.setText(cdList.get(position).aqi);
			bg.setBackgroundColor(Color.parseColor(cdList.get(position).color));
			return convertView;
		}
		
	}

最后在XML解析完成以后,添加一句:gv.setAdapter(new gvAdapter(cdList)); 即把数据填充到了网格中。

5、其它程序逻辑

当点击每个GridView的item的时候,跳转到相应的城市详细信息页面。
右键项目的目录 New --> Other -->Activity,给新的Activity起名为:CityActivity。依照上面介绍的写法给新的Activity写好布局和逻辑。
点击主界面中的网格,自动跳转到新的界面:

gv.setOnItemClickListener(new OnItemClickListener() {

	@Override
	public void onItemClick(AdapterView<?> parent, View view,int position, long id) {                                   
                //从当前的界面跳转到城市详细信息界面
		Intent it = new Intent(MainActivity.this, CityActivity.class);
                //对象本身无法使用Intent传递,但是我们继承了Serializable,使传递成为了可能
		it.putExtra("node", cdList.get(position));
		startActivity(it);
	}
			});

1、在城市详细信息界面,点击每个监测点,显示该监测点的详细数据。这里需要用到对话框AlertDialog来显示详细数据。
2、更改res --> values -->string.xml 中的内容,个性化我们的程序,如下:
<resources>
    <string name="app_name">河北空气质量</string>
    <string name="title_activity_city">城市数据</string>
</resources>
这样,程序的名字就变成了“河北空气质量”,在进入到详细信息界面的时候,标题栏也变成了“城市数据”
3、找一个图片,做成png格式,覆盖res/drawable-hdpi/ic_launcher.png 文件,这样就更改了在手机APP列表中的图标了。




其实做一个APP非常的简单,只要有想法,上面的工作在一天内就可以完成。本文主要是想带给大家一个思路,如何发掘身边的一些内容,来做出自己的APP。
可能光看讲解不会太懂,那么可以到xxxx下载程序的源代码
转载请注明来自:http://blog.csdn.net/icyfox_bupt/article/details/18953581

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。