安卓作品:河北空气质量客户端

简介: <p>原文:http://blog.csdn.net/icyfox_bupt/article/details/18953581#comments</p> <p></p> <p style="color:rgb(51,51,51); font-family:Arial; font-size:14px; line-height:26px"> 本文将与你一起从零开始,做一个<a targe

原文:http://blog.csdn.net/icyfox_bupt/article/details/18953581#comments

本文将与你一起从零开始,做一个河北省空气质量自动发布系统的客户端,文章面向零基础的、只看过一点安卓教程的同学,对于比较基础的内容,也会用红字的链接标出,大家可以点开看详细的介绍。

        其实做这个,完全是因为老爸的原因,河北的空气质量太差了,所以他决定天天根据空气质量来决定散步不散步。总是上这个网站过于复杂,于是我就有了做一个客户端的想法。下面分几步介绍关于信息获取异步获取网络数据数据分析界面设计程序逻辑等内容,下面介绍一个完整的程序是如何做出来的。

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

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

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

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



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,这个方法可以模拟浏览器的访问,我们输入参数是网址,这个函数返回得到的网页源代码:

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. public static String HttpGet(String url) throws ClientProtocolException, IOException {  
  2.     //新建一个默认的连接  
  3.     DefaultHttpClient client = new DefaultHttpClient();  
  4.     //新建一个Get方法  
  5.     HttpGet get = new HttpGet(url);  
  6.     //得到网络的回应  
  7.     HttpResponse response = client.execute(get);  
  8.     //获得的网页源代码(xml)  
  9.     String content = null;  
  10.   
  11.     //如果服务器响应的是OK的话!  
  12.     if (response.getStatusLine().getStatusCode() == 200) {  
  13.         //以下是把网络数据分段读取下来的过程  
  14.         InputStream in = response.getEntity().getContent();  
  15.         byte[] data = new byte[1024];  
  16.         int length = 0;  
  17.         ByteArrayOutputStream bout = new ByteArrayOutputStream();  
  18.         while ((length = in.read(data)) != -1) {  
  19.             bout.write(data, 0, length);  
  20.         }  
  21.         //最后把字节流转为字符串 转换的编码为utf-8.  
  22.         content = new String(bout.toByteArray(), "utf-8");  
  23.     }  
  24.     //返回得到的字符串 也就是网页源代码  
  25.     return content;  
  26. }  

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

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

        接下来我们尝试把XML源代码显示出来
        在MainActivity类中新建一个类 GetSource,这个类继承AsyncTask,用来获取网页上的数据;得到数据后使用 Logcat(可以理解为Android上的控制台)打印出来:
[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. class GetSource extends AsyncTask<String, Void, String>{  
  2.       
  3.     //此函数用来处理后台的事物  
  4.     @Override  
  5.     protected String doInBackground(String... params) {  
  6.         try {  
  7.             //这里调用了我们刚才写的下载函数  
  8.             return Util.HttpGet("http://121.28.49.85:8080/datas/hour/130000.xml");  
  9.         } catch (IOException e) {}  
  10.         return null;  
  11.     }  
  12.       
  13.     //后台事物完成后,此函数用来更改界面的内容  
  14.     @Override  
  15.     protected void onPostExecute(String result) {  
  16.         //让Log输出运行时的记录  
  17.         Log.i("test",result);  
  18.     }  
  19.       
  20. }  

        最后在OnCreate函数的最后一行加上下面一句,再 添加网络权限,然后我们来看看效果吧!
[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. new GetSource().execute();  

3、界面设计

        我们的目标是把软件设计成文章开始时的那个样式,这样我们就要简单地修改一下activity_main.xml:
        ·在上方放置一个TextView,背景为浅绿色,文字颜色为白色;
        ·下方是一个GridView,其中每个格子的颜色根据空气质量来变化,格子中上方显示城市名,下方显示当前的AQI。
        ·同时在整个界面上还要显示一个转圈的进度条ProgressBar,加载的时候显示这个进度条
        代码如下:
[html]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     xmlns:tools="http://schemas.android.com/tools"  
  3.     android:layout_width="match_parent"  
  4.     android:layout_height="match_parent"  
  5.     tools:context=".MainActivity" >  
  6.   
  7.         <LinearLayout  
  8.             android:id="@+id/ll"  
  9.             android:layout_width="match_parent"  
  10.             android:layout_height="wrap_content"  
  11.             android:visibility="gone"  
  12.             android:orientation="vertical" >  
  13.   
  14.             <TextView  
  15.                 android:id="@+id/tv_time"  
  16.                 android:layout_width="fill_parent"  
  17.                 android:layout_height="wrap_content"  
  18.                 android:layout_margin="5dp"  
  19.                 android:background="#7a7"  
  20.                 android:padding="5dp"  
  21.                 android:textColor="#eee"  
  22.                 android:textSize="20sp"  
  23.                 android:textStyle="bold" />  
  24.   
  25.             <GridView  
  26.                 android:id="@+id/gv"  
  27.                 android:layout_width="match_parent"  
  28.                 android:layout_height="fill_parent"  
  29.                 android:layout_margin="5dp"  
  30.                 android:horizontalSpacing="3dp"  
  31.                 android:verticalSpacing="3dp"  
  32.                 android:numColumns="3" >  
  33.             </GridView>  
  34.         </LinearLayout>  
  35.   
  36.         <ProgressBar  
  37.             android:id="@+id/pb1"  
  38.             android:layout_width="wrap_content"  
  39.             android:layout_height="wrap_content"  
  40.             android:layout_centerHorizontal="true"  
  41.             android:visibility="visible"  
  42.             android:layout_centerVertical="true" />  
  43.   
  44. </RelativeLayout>  

        GridView 是一种网格样式布局,在上面的代码里我设置的每行格子个数为3个,还设置了格子之间的间距。每个格子中的布局需要另一个文件来控制:
gv.xml:
[html]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     android:id="@+id/bg"  
  4.     android:layout_width="match_parent"  
  5.     android:layout_height="wrap_content"  
  6.     android:padding="8dp"  
  7.     android:orientation="vertical" >  
  8.   
  9.     <TextView  
  10.         android:id="@+id/tv_city"  
  11.         android:layout_width="wrap_content"  
  12.         android:layout_height="wrap_content"  
  13.         android:text="石家庄"  
  14.         android:textColor="#4bd"  
  15.         android:textSize="20sp"  
  16.         android:textStyle="bold" />  
  17.   
  18.     <TextView  
  19.         android:id="@+id/tv_aqi"  
  20.         android:layout_width="wrap_content"  
  21.         android:layout_height="wrap_content"  
  22.         android:text="223"  
  23.         android:textColor="#4bd"  
  24.         android:textSize="18sp"  
  25.         android:textStyle="bold" />  
  26.   
  27. </LinearLayout>  

4、数据分析

4.1 解析XML数据

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

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. @Override  
  2. protected void onPostExecute(String result) {  
  3.     //建立一个解析器  
  4.     DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();  
  5.     DocumentBuilder builder;  
  6.     try {  
  7.         builder = factory.newDocumentBuilder();  
  8.         InputStream is = new ByteArrayInputStream(result.getBytes());  
  9.         Document document = builder.parse(is);  
  10.         Element element = document.getDocumentElement();  
  11.         //获得所有的Citys节点数据  
  12.         NodeList cityList = element.getElementsByTagName("Citys");  
  13.           
  14.         //获得mapstitle数据,并分解为两部分  
  15.         NodeList title = element.getElementsByTagName("MapsTitle");  
  16.         String text1 = title.item(0).getTextContent();  
  17.         String t[] = text1.split("\\(");  
  18.         text1 = t[0];  
  19.         t = t[1].split(",");  
  20.         tv_time.setText(text1+ "\n" +t[0]);  
  21.           
  22.         Element citys = (Element)cityList.item(0);  
  23.         NodeList city = citys.getChildNodes();  
  24.           
  25.         for (int i=0;i < city.getLength();i++){  
  26.             //此时的city节点的item上,有的是一个城市的所有数据  
  27.             Node node = city.item(i);  
  28.             if (node.getNodeName().equalsIgnoreCase("city")) { //这是一个有效的节点  
  29.                 CityData cd = new CityData(node);  
  30.                 nodeList.add(node);  
  31.                 cdList.add(cd);  
  32.             }  
  33.         }  
  34.     } catch (Exception e) {}  

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

4.2 应用数据到布局

        接下来要编写一个Adapter来连接布局和代码。我们假设每一个每一个城市的数据都是一个 Node(节点),GridView的数据存在一个列表里,格子的个数与列表的长度有关。 (所以如果河北省城市监测点变多了,程序也可以进行变化而自适应)分析数据源的XML可以得到,我们需要的是其中城市,和每个城市中监测点的列表。所以,在这里新建两个类: CityData、Pointer,其中,CityData包括一个Pointer的列表。以下是CityData类,Pointer类与这个相似:
[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. public class CityData implements Serializable{  
  2.       
  3.     /** 
  4.      * 继承Serializable是为了在两个不同的界面中进行值的传递 
  5.      */  
  6.     private static final long serialVersionUID = -8473485404751986234L;  
  7.     //城市类包含了一个城市的数据  
  8.     public String name,dataTime,aqi,level,maxPoll,color,intro,tips;  
  9.     public List<Pointer> pointerList;  
  10.   
  11.     public CityData(Node cityNode) {  
  12.         super();  
  13.         //按标签挨个取出相应标签的内容  
  14.         this.name = getByTag(cityNode, "name");  
  15.         this.dataTime = getByTag(cityNode, "datatime");  
  16.         this.aqi = getByTag(cityNode, "aqi");  
  17.         this.level = getByTag(cityNode, "level");  
  18.         this.maxPoll = getByTag(cityNode, "maxpoll");  
  19.         String tmp = getByTag(cityNode, "color");  
  20.         this.color = tmp.replace("0x""#");  
  21.         this.intro = getByTag(cityNode, "intro");  
  22.         this.tips = getByTag(cityNode, "tips");  
  23.           
  24.         Element city = (Element)cityNode;  
  25.         NodeList pointers = city.getElementsByTagName("Pointer");  
  26.           
  27.         //向city的pointer列表中添加监测点  
  28.         pointerList = new ArrayList<Pointer>();  
  29.         for (int i=0;i<pointers.getLength();i++){  
  30.             Node pNode = pointers.item(i);  
  31.             pointerList.add(new Pointer(pNode));  
  32.         }  
  33.     }  
  34.   
  35.     //从XML的node中取出相应标签中内容的function  
  36.     private String getByTag(Node node,String tag) {  
  37.         for (int i=0;i<node.getChildNodes().getLength();i++){  
  38.             if (tag.equalsIgnoreCase(node.getChildNodes().item(i).getNodeName()))  
  39.                 return node.getChildNodes().item(i).getTextContent();  
  40.         }  
  41.         return null;  
  42.     }  
  43. }  

        负责把数据填充到布局的Adapter:
[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. class gvAdapter extends BaseAdapter{  
  2.   
  3.     //Adapter的数据源,根据这个数据来填充网格列表  
  4.     List<CityData> cdList;  
  5.       
  6.     public gvAdapter(List<CityData> cdList) {  
  7.         super();  
  8.         this.cdList = cdList;  
  9.     }  
  10.   
  11.     //根据数据的多少返回相应的数值  
  12.     @Override  
  13.     public int getCount() {  
  14.         return cdList.size();  
  15.     }  
  16.   
  17.     @Override  
  18.     public Object getItem(int position) {  
  19.         return null;  
  20.     }  
  21.   
  22.     @Override  
  23.     public long getItemId(int position) {  
  24.         return 0;  
  25.     }  
  26.   
  27.     @Override  
  28.     public View getView(int position, View convertView, ViewGroup parent) {  
  29.         //解析之前写好的每个网格的布局  
  30.         convertView = MainActivity.this.getLayoutInflater().inflate(R.layout.gv, null);  
  31.         //找到布局中的元素,和布局的背景  
  32.         TextView tv_city = (TextView)convertView.findViewById(R.id.tv_city);  
  33.         TextView tv_aqi = (TextView)convertView.findViewById(R.id.tv_aqi);  
  34.         View bg = convertView.findViewById(R.id.bg);  
  35.         //根据数据填充每个格子的内容和背景色  
  36.         tv_city.setText(cdList.get(position).name);  
  37.         tv_aqi.setText(cdList.get(position).aqi);  
  38.         bg.setBackgroundColor(Color.parseColor(cdList.get(position).color));  
  39.         return convertView;  
  40.     }  
  41.       
  42. }  

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

5、其它程序逻辑

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

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. gv.setOnItemClickListener(new OnItemClickListener() {  
  2.   
  3.     @Override  
  4.     public void onItemClick(AdapterView<?> parent, View view,int position, long id) {                                     
  5.                 //从当前的界面跳转到城市详细信息界面  
  6.     <span style="white-space:pre">  </span>Intent it = new Intent(MainActivity.this, CityActivity.class);  
  7.                 //对象本身无法使用Intent传递,但是我们继承了Serializable,使传递成为了可能  
  8.         it.putExtra("node", cdList.get(position));  
  9.         startActivity(it);  
  10.     }  
  11.             });  

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




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

目录
相关文章
|
5月前
|
Linux Android开发 iOS开发
基于.Net开发的ChatGPT客户端,兼容Windows、IOS、安卓、MacOS、Linux
基于.Net开发的ChatGPT客户端,兼容Windows、IOS、安卓、MacOS、Linux
88 0
|
移动开发 Java 开发工具
Android客户端三步完成支付宝支付SDK接入
Android客户端三步完成支付宝支付SDK接入
1896 0
|
4月前
|
XML JSON Java
Android App开发即时通信中通过SocketIO在客户端与服务端间传输文本和图片的讲解及实战(超详细 附源码)
Android App开发即时通信中通过SocketIO在客户端与服务端间传输文本和图片的讲解及实战(超详细 附源码)
66 0
|
7月前
|
缓存 安全 Android开发
[Android 毕业设计/课程设计] 小而美的阅读客户端 App
[Android 毕业设计/课程设计] 小而美的阅读客户端 App
|
9月前
|
Android开发 iOS开发 Windows
无影产品动态|iOS & Android客户端6.0.0版本发布,提升触控灵敏度,操作体验更丝滑
无影ios & Android客户端6.0.0版本发布!移动端触控体验更舒适,用户操作更便捷,一起来看看!
676 0
无影产品动态|iOS & Android客户端6.0.0版本发布,提升触控灵敏度,操作体验更丝滑
|
Web App开发 弹性计算 Android开发
阿里云无影云桌面客户端下载Win/Mac/iOS/安卓/Web端均支持
阿里云无影客户端下载系统Win/Mac/iOS/安卓/Web端均支持
3548 0
阿里云无影云桌面客户端下载Win/Mac/iOS/安卓/Web端均支持
|
Android开发
《58同城Android客户端Walle框架演进与实践之路》电子版地址
58同城Android客户端Walle框架演进与实践之路
56 0
《58同城Android客户端Walle框架演进与实践之路》电子版地址
|
Ubuntu Linux 开发工具
Android 使用Linphone SDK开发SIP客户端
Android 使用Linphone SDK开发SIP客户端
982 0
Android 使用Linphone SDK开发SIP客户端