本文將與你一起從零開始,做一個河北省空氣品質自動發布系統的用戶端,文章面向零基礎的、只看過一點安卓教程的同學,對於比較基礎的內容,也會用紅字的連結標出,大家可以點開看詳細的介紹。
其實做這個,完全是因為老爸的原因,河北的空氣品質太差了,所以他決定天天根據空氣品質來決定散步不散步。總是上這個網站過於複雜,於是我就有了做一個用戶端的想法。下面分幾步介紹關於資訊擷取,非同步擷取網路資料,資料分析,介面設計和程式邏輯等內容,下面介紹一個完整的程式是如何做出來的。
首先需要找到程式的資料來源,找到從網上獲得資料介面的網址。
其次,要把資料從網上的格式,轉換成我們可以使用的格式。
接下來進行布局的設計,最後把資料填充到布局裡,整個程式就完成了。
下面是這個系統的網站,和我做的用戶端:
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>{//此函數用來處理背景事物@Overrideprotected String doInBackground(String... params) {try {//這裡調用了我們剛才寫的下載函數return Util.HttpGet("http://121.28.49.85:8080/datas/hour/130000.xml");} catch (IOException e) {}return null;}//後台事物完成後,此函數用來更改介面的內容@Overrideprotected 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中的代碼為:
@Overrideprotected 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中取出相應標籤中內容的functionprivate 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;}//根據資料的多少返回相應的數值@Overridepublic int getCount() {return cdList.size();}@Overridepublic Object getItem(int position) {return null;}@Overridepublic long getItemId(int position) {return 0;}@Overridepublic 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() {@Overridepublic 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。 可能光看講解不會太懂,那麼可以到http://download.csdn.net/detail/icyfox_bupt/6908053下載程式的原始碼 轉載請註明來自:http://blog.csdn.net/icyfox_bupt/article/details/18953581