原文: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 plaincopy
public ? static ?String?HttpGet(String?url)? throws ?ClientProtocolException,?IOException?{?? ?????? ????DefaultHttpClient?client?=?new ?DefaultHttpClient();?? ?????? ????HttpGet?get?=?new ?HttpGet(url);?? ?????? ????HttpResponse?response?=?client.execute(get);?? ?????? ????String?content?=?null ;?? ?? ?????? ????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);?? ????????}?? ?????????? ????????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上的控制臺)打印出來:
[java] ?view plaincopy
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.i("test" ,result);?? ????}?? ?????? }?? ? ? ? ??最后在OnCreate函數的最后一行加上下面一句,再
添加網絡權限 ,然后我們來看看效果吧!
[java] ?view plaincopy
new ?GetSource().execute();??
3、界面設計
我們的目標是把軟件設計成文章開始時的那個樣式,這樣我們就要簡單地修改一下activity_main.xml:
·在上方放置一個TextView,背景為淺綠色,文字顏色為白色;
·下方是一個GridView,其中每個格子的顏色根據空氣質量來變化,格子中上方顯示城市名,下方顯示當前的AQI。
·同時在整個界面上還要顯示一個轉圈的進度條ProgressBar,加載的時候顯示這個進度條
代碼如下:
[html] ?view plaincopy
< 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:
[html] ?view plaincopy
<? 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 中的代碼為:
[java] ?view plaincopy
@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();?? ?????????? ????????NodeList?cityList?=?element.getElementsByTagName("Citys" );?? ?????????? ?????????? ????????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++){?? ?????????????? ????????????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類與這個相似:
[java] ?view plaincopy
public ? class ?CityData? implements ?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" );?? ?????????? ?????????? ????????pointerList?=?new ?ArrayList<Pointer>();?? ????????for ?( int ?i= 0 ;i<pointers.getLength();i++){?? ????????????Node?pNode?=?pointers.item(i);?? ????????????pointerList.add(new ?Pointer(pNode));?? ????????}?? ????}?? ?? ?????? ????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:
[java] ?view plaincopy
class ?gvAdapter? extends ?BaseAdapter{?? ?? ?????? ????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寫好布局和邏輯。
點擊主界面中的網格,自動跳轉到新的界面:
[java] ?view plaincopy
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 );?? ?????????????????? ????????it.putExtra("node" ,?cdList.get(position));?? ????????startActivity(it);?? ????}?? ????????????});?? ? ? ? ??1、在城市詳細信息界面,點擊每個監測點,顯示該監測點的詳細數據。這里需要用到對話框AlertDialog來顯示詳細數據。
2、更改
res --> values -->string.xml ?中的內容,個性化我們的程序,如下:
[html] ?view plaincopy
< resources > ?? ????< string ? name = "app_name" > 河北空氣質量 </ string > ?? ????< string ? name = "title_activity_city" > 城市數據 </ string > ?? </ resources > ?? ? ? ? ??這樣,程序的名字就變成了“河北空氣質量”,在進入到詳細信息界面的時候,標題欄也變成了“城市數據”
3、找一個圖片,做成png格式,覆蓋res/drawable-hdpi/ic_launcher.png 文件,這樣就更改了在手機APP列表中的圖標了。
其實做一個APP非常的簡單,只要有想法,上面的工作在一天內就可以完成。本文主要是想帶給大家一個思路,如何發掘身邊的一些內容,來做出自己的APP。
可能光看講解不會太懂,那么可以到http://download.csdn.net/detail/icyfox_bupt/6908053下載程序的源代碼
總結
以上是生活随笔 為你收集整理的安卓作品:河北空气质量客户端 的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔 網站內容還不錯,歡迎將生活随笔 推薦給好友。