安卓作品:河北空气质量客户端
原文:https://blog.csdn.net/icyfox_bupt/article/details/18953581comments
本文将与你一起从零开始,做一个河北省空气质量自动发布系统的客户端,文章面向零基础的、只看过一点安卓教程的同学,对于比较基础的内容,也会用红字的链接标出,大家可以点开看详细的介绍。
其实做这个,完全是因为老爸的原因,河北的空气质量太差了,所以他决定天天根据空气质量来决定散步不散步。总是上这个网站过于复杂,于是我就有了做一个客户端的想法。下面分几步介绍关于信息获取,异步获取网络数据,数据分析,界面设计和程序逻辑等内容,下面介绍一个完整的程序是如何做出来的。
首先需要找到程序的数据源,找到从网上获得数据接口的网址。
其次,要把数据从网上的格式,转换成我们可以使用的格式。
接下来进行布局的设计,最后把数据填充到布局里,整个程序就完成了。
下面是这个系统的网站,和我做的客户端:
1、数据获取
想做这个软件我们先要有数据源,数据是河北省环境监测中心给出的,我们现在要找到它的接口。
打开网址: https://121.28.49.85:8080/ 我们可以看到这是一个flash做的页面,而且有明显的加载过程,说明浏览器获取过数据。我们使用HttpAnalyze或者Smsniff来查看浏览器发送出去的数据包,当然最方便的是使用Chrome的功能。
打开Chrome --> F12 --> 选择NetWork标签 --> 打开上面的网络地址,下面会出现很多条请求的数据,我们按Size排序后找最大的,就是我们需要的数据。如下图:

发送的请求的地址

得到的回应
如上图所示:打开网页后浏览器发送了若干条数据,其中有一条远大于其它数据的包,大小为59.75k,我们可以认为这就是数据的来源了,而我们看到它指向了网址 https://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("https://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="https://schemas.android.com/apk/res/android"
- xmlns:tools="https://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="https://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) {
- //从当前的界面跳转到城市详细信息界面
- <span style="white-space:pre"> </span>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。
可能光看讲解不会太懂,那么可以到https://download.csdn.net/detail/icyfox_bupt/6908053下载程序的源代码
最后更新:2017-04-03 12:55:03
上一篇:
android多屏幕分辨率适配
下一篇:
基于XMPP协议的Android即时通信系
如何让云计算加速传统商业
Java之dead code——无用代码
HTTP的请求头 Last-Modified 与 If-Modified-Since 和 If-None-Match 与 ETags
停车场收费管理系统嵌入式技术应用
Windows 下 Docker 的安装与配置概述
Android异步下载图片并且缓存图片到本地
PostgreSQL 10.0 preview 变化 - pg_xlog,pg_clog,pg_log目录更名为pg_wal,pg_xact,log
快照省钱的不二秘笈
跟着实例学习ZooKeeper的用法: Curator框架应用
Java面向对象高级--实例分析—宠物商店