安卓作品:河北空氣質量客戶端
原文: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麵向對象高級--實例分析—寵物商店