一步步教你爲網站開發Android客戶端

注:本文來自友盟-安卓巴士教程大賽第一名獲獎做品,做者安卓巴士的ID爲liupeinye。推薦給全部剛剛開始接觸Android開發的朋友們!html

 

本文面向Android初級開發者,有必定的Java和Android知識便可。android

 

文章覆蓋知識點:HttpWatch抓包,HttpClient模擬POST請求,Jsoup解析HTML代碼,動態更新ListViewweb

 

背景介紹:客戶端(Client)或稱爲用戶端,是指與服務器相對應,爲客戶提供本地服務的程序。而android系統上的90%客戶端軟件都有一個共性,就是爲了改善網頁在android系統上體驗不佳而生,最具備影響力的軟件有:新浪微博、人人網、淘寶等,這類軟件最突出的特色就是,先有網站再有軟件。因爲網絡技術發展的多樣性,手機瀏覽器每每沒法跟隨它的步伐,爲改善用戶體驗,網站客戶端軟件印運而生。數據庫

 

如下內容100%原創,並在安卓巴士論壇首發,如需轉載,請註明做者和出處。謝謝合做。數組

 

開發Android網站客戶端一般有兩種方法:第一種,經過服務端的開放平臺,調用提供的API接口來開發,好比說open sina;第二種,服務端沒有提供任何接口,你也沒有服務端任何數據庫訪問權限,就是一個純純粹粹的網站,要你作客戶端。今天,我要和你們分享的正是第二種狀況。瀏覽器

這是一個簡單的示意圖,告訴咱們,數據是由網頁從數據庫中取出,咱們要爲這個系統作客戶端,咱們就應該這樣去改造它。服務器

 

經過這樣間接的方法來訪問數據庫,只要網頁能看到的內容,咱們的客戶端都能獲取到,而UI是由你自行製做,就可使使用體驗上一個臺階。網絡

既然網頁是咱們的數據樞紐,咱們就從網頁分析着手。eclipse

 

該教程來自本人項目-掌上民大-真實經驗,因此用項目中的」掌上圖書館」板塊來示範。ide

 

該項目任務爲中南民族大學圖書館圖書查詢功能製做客戶端。

 

首先打開該網址http://www.lib.scuec.edu.cn/,咱們會看到主界面

正中間就是查詢入口,咱們輸入」android」進行查詢

獲得結果的網頁,但咱們能發現,這個頁面是ILAS圖書管理系統,因此真正的入口並非上面紅圈的地方。

因此我繼續找,通過短暫的觀察,發現入口在這裏

 

咱們點擊進入

果真就是這貨,咱們點擊書目查詢

就是它了。真正的入口就在這裏,這時咱們打開HttpWatch軟件,點」Record」,在搜索框裏輸入」android」,點擊查詢,抓取以」android」爲關鍵字搜索時瀏覽器的全部包。待結果界面載入完成後,HttpWatch上就會出現以下信息

咱們先看Summary選項卡,咱們能夠初步瞭解,這是一個POST請求(Http請求中的一種,另外一種是GET),POST到的網址是http://coin.lib.scuec.edu.cn/cgi-bin/IlaswebBib

這樣咱們的思路就清晰了,咱們的客戶端須要模擬瀏覽器,向上述地址POST一個包,那個地址確定會返回一個Content給咱們,不出意外的話,Content裏面就是咱們要的書目信息。那麼,瀏覽器POST上去的內容是什麼呢?咱們點擊這條POST請求,看詳細信息,

 

 

因爲是POST請求,咱們先看POST DATA,裏面是以鍵值對的形式存儲的,這裏顯示了咱們瀏覽器在咱們搜索」android」時,POST的全部數據。那這些鍵值對又表明了什麼呢,咱們打開這個網頁的源碼來一探究竟。

 

 

從這段能夠看出v_index是表示查找途徑的它有TITLE,AUTHOR,SUBJECT,CLASSNO,ISBN,CALLNO六種值

 

 

FLD_DAT_BEG和FLD_DAT_END分別是開始和結束年份

 

v_value表示用戶在搜索框中輸入的內容

 

v_paggnum表示每頁顯示的書目條數,有10 15 20三種

 

 

v_seldatabases是檢索庫  有0 1 2三種值v_LogicSrch是檢索方式   有0 1兩種值

 

Submit是查詢或重填,有 查 詢 和 重 填 兩種值

 

至此,咱們弄清楚了POST Data裏全部內容的含義和取值可能。但咱們模擬POST請求爲何,其實就是爲了獲得搜索的書目信息,因此咱們看一下返回的Content是否是咱們要的東西

 

果真,就是咱們搜到的書目信息,就以String的形式放在Content裏面。最後咱們查看一下Stream,截圖,以防等下咱們須要這裏面的東西

 

 

好了,這個頁面的工做原理咱們已經弄清楚了:用戶在網頁中輸入搜索內容後,點擊查詢,瀏覽器會POST一個Data到目標網址,該網址的返回信息就是搜到的書目。 

咱們開始編寫代碼,模擬這個過程,先打開eclipse創建一個Java項目(注意是Java項目,由於Java項目能夠完美移植到Android項目中且調試方便,而且模擬Http請求這一過程沒有用到任何Android功能)。

導入HttpClient的4個包commons-codec、commons-httpclient、commons-logging、log4j。

 

  1. //實例化HttpClient
  2. HttpClient client = new HttpClient();
  3. //Stream頁面裏面有Host地址 端口是80
  4. client.getHostConfiguration().setHost("http://coin.lib.scuec.edu.cn", 80);
  5. //用目標地址 實例一個POST方法
  6. PostMethod post = new PostMethod("http://coin.lib.scuec.edu.cn/cgi-bin/IlaswebBib");
  7. //將須要的鍵值對寫出來
  8. NameValuePair beg = new NameValuePair("FLD_DAT_BEG" , 「」);
  9. NameValuePair end = new NameValuePair("FLD_DAT_END" , 「」);
  10. NameValuePair submit = new NameValuePair("submit" , "查 詢"));
  11. NameValuePair vIndex = new NameValuePair("v_index" , 「TITLE」);
  12. NameValuePair vLogicSrch = new NameValuePair("v_LogicSrch" , "0");
  13. NameValuePair vPagenum = new NameValuePair("v_pagenum" , "10");
  14. NameValuePair vSeldatabase = new NameValuePair("v_seldatabase" , "0");
  15. NameValuePair vValue = new NameValuePair("v_value" ,」android」);
  16.  
  17. //給POST方法加入上述鍵值對
  18. post.setRequestBody(new NameValuePair[] {beg , end , submit , vIndex , vLogicSrch , vPagenum , vSeldatabase , vValue});
  19. //執行POST方法
  20. client.executeMethod(post);
  21. //將POST返回的數據以流的形式讀入,再把輸入流流至一個buff緩衝字節數組
  22. //StreamTool類是我本身寫的一個工具類,其內容將在下文附出
  23. byte[] buff = StreamTool.readInputStream(post.getResponseBodyAsStream());
  24. //將返回的內容格式化爲String存在html中
  25. String html = new String(buff);
  26. //任務完成了,釋放鏈接
  27. post.releaseConnection();

 

  1. //StreamTool類以下
  2. public class StreamTool {
  3.         /**
  4.          * 從輸入流中獲取數據
  5.          * @param inputStream 輸入流
  6.          * @return 字節數組
  7.          * @throws Exception
  8.          */
  9.         public static byte[] readInputStream(InputStream inputStream) throws Exception
  10.         {
  11. //實例化一個輸出流
  12.                 ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
  13. //一個1024字節的緩衝字節數組
  14.                 byte[] buffer = new byte[1024];
  15.                 int len = 0;
  16. //讀流的基本知識
  17.                 while ((len=inputStream.read(buffer)) != -1) {
  18.                         outputStream.write(buffer, 0, len);
  19.                 }
  20. //用完要關,你們都懂的
  21.                 inputStream.close();
  22.                 return outputStream.toByteArray();
  23.         }
  24. }

如今,咱們獲得了POST方法返回的String,咱們輸出到控制檯看看是什麼

  1. System.out.println(html);

沒錯,就是咱們上文看到的HttpWatch 抓到的返回Content,也就是一段HTML代碼,這說明,咱們模擬瀏覽器POST請求成功了!

咱們再試試別的搜索內容,來一個」android開發」(即將v_value鍵值對的值改爲」android開發」),這時運行後,咱們卻從控制檯獲得了這樣的結果:

通過幾回試驗後,發現一個規律,只要搜索內容中包括中文,就搜不到。

因此能夠斷定是中文編碼的問題,(在開發這類客戶端時候,中文編碼每每是個很具困難的問題。安卓巴士開發3羣的某羣友提到:服務器交流用的編碼是」ISO-8859-1」,跟我起初用到的編碼一致,但真實性仍需考證)因此咱們修改上面的代碼,將表明搜索內容的v_value對應的值編碼爲」ISO-8859-1」

 

就將上段代碼中的

  1. NameValuePair vValue = new NameValuePair("v_value" ,」android」);

改成

  1. NameValuePair vValue = new NameValuePair("v_value" , new String(「android開發」.getBytes(),"ISO-8859-1"));

這時再運行,控制檯成功輸出以」 android開發」爲關鍵字的Content。

至此,咱們POST請求才真正完成。 觀察控制檯的HTML後發現,咱們須要的書目信息就在裏面,只不過被一些HTML標籤包裹住了,下一步咱們就要解放這些信息,存儲到容器裏。

這裏咱們要用到Jsoup,一個Java開源HTML解析器(來自org.jsoup包)。

咱們直接上代碼,逐行解釋(你們最好對應上面的HTML代碼來理解)

首先咱們建一個容器來裝這些解析到的數據,因爲個人項目是將這些數據以ListView呈現給用戶,而ListView的數據是由Adapter提供,Adapter須要傳一個特殊容器-包含HashMap的ArrayList(Android基礎知識)

  1. //因此有
  2. List<Map<String,Object>> list = new ArrayList<Map<String,Object>>();
  3. //開始使用Jsoup
  4. //Jsoup支援一個Document類   將剛纔的html轉化成Document
  5. Document document = Jsoup.parse(html);
  6. //一個Document又由elements組成  咱們選擇」tr」開頭的標籤,存入 trs元素羣中
  7. Elements trs = document.select("tr");
  8. //獲得整個HTML中包含tr的標籤的個數
  9. int totalTrs = trs.size();
  10. //咱們能夠觀察上面沒有搜索結果的那個HTML。發現,若是totalTrs<=3就表示沒結果。
  11. //只要有書目結果totalTrs一定大於3,因而
  12. if(totalTrs > 3)
  13. for(int i = 0;i < totalTrs – 3;i++)
  14. {
  15. //觀察HTML,從第i+2個tr開始,包含的纔是咱們要的書目信息
  16. //咱們從每一個tr中選出td標籤元素羣
  17.                         Elements tds = trs.get(i + 2).select("td");
  18. //獲得每一個tr中td的個數
  19.                         int totalTds = tds.size();
  20. //一個臨時的HashMap,裏面是String-Object鍵值對
  21.                         Map<String,Object> map = new HashMap<String,Object>();
  22. //j是一個標識數
  23.                         for(int j =0;j < totalTds ;j++)
  24.                         {
  25.                                 switch (j) {
  26. //0表示第一個,即書名
  27. //put方法即向map加入一條鍵值對
  28. //html()方法就獲得標籤括起來的內容
  29.                                 case 0:
  30.                                         map.put("book_title", tds.get(j).html().toString());
  31.                                         break;
  32.                                 case 1:
  33. //1表示第二個,即做者
  34.                                         map.put("book_author", tds.get(j).html().toString());
  35.                                         break;
  36.                                 case 2:
  37. //2表示第三個,即出版信息
  38.                                         map.put("book_press", tds.get(j).html().toString());
  39.                                         break;
  40.                                 case 3:
  41. //3表示第四個,即頁數
  42.                                         map.put("book_page", tds.get(j).html().toString());
  43.                                         break;
  44.                                 case 4:
  45. //4表示第五個,即價格
  46.                                         map.put("book_price", tds.get(j).html().toString());
  47.                                         break;
  48.                                 case 5:
  49. //5表示第六個,即索取號
  50.                                         map.put("book_noFor", tds.get(j).html().toString());
  51.                                         break;
  52.                                 case 6:
  53. //6表示第七個,即那段網址
  54. //那段網址td中又包含一個a標籤,a標籤的href屬性的值就是網址
  55. //attr(「href」)能夠返回href屬性的值
  56.                                         map.put("book_detail", tds.get(j).select("a").attr("href").toString());
  57.                                         break;
  58.                                 default:
  59.                                         break;
  60.                                 }
  61.                         }
  62.                         list.add(map);
  63. }

list就是咱們須要的ArrayList啦

上面全部代碼調通後,咱們只需一些簡單的複製粘貼,就能夠放在咱們的Android工程中,加上一段簡單的代碼就可讓ListView顯示這個ArrayList。(因爲沒有任何技術含量,以及該項目暫未上線,此段代碼不予以展現,敬請諒解) 

 

接下來,咱們一個頁面最多隻包含10個書目信息,而咱們校圖書館,光以」Java」爲關鍵字的書就超過1000本,怎麼來顯示徹底呢,一次顯示全部的書確定不現實。首先數據量太大,手機沒法承受;消耗流量過大,用戶體驗極差。因此,咱們就須要ListView可以動態加載數據,即一開始顯示十項,若是用戶此時拉動ListView顯示完十項以後,自動聯網,再加載十項(若是還有十項的話),這樣的用戶體驗會很是順暢。

這個功能的核心是,咱們的ListView須要實現OnScrollListener接口。

若是你的ListView所在的Activity繼承的是ListActivity的話,只需在extends ListActivity後面加上implements OnScrollListener,這時你須要複寫onScroll和onScrollStateChanged。若是你的ListView是從XMLgetView 獲得的,你只需爲它setOnScrollListener,也會須要你複寫onScroll和onScrollStateChanged。

無論你用哪一種方法,咱們只用修改onScroll方法

  1. @Override
  2.         public void onScroll(AbsListView view, int firstVisibleItem,
  3.                         int visibleItemCount, int totalItemCount) {
  4.                 // TODO Auto-generated method stub
  5. //關鍵的判斷代碼,這句話表示用戶將ListView拉至最底部
  6.                 if(firstVisibleItem + visibleItemCount == totalItemCount)
  7. //你只須要把繼續獲得下面十項的代碼寫在這裏,就能夠實現上述功能了。
  8. //一樣再使用一次POST方法,再也不贅述
  9. //代碼因爲一樣緣由不予以展現,敬請諒解

至此,文章開頭的幾個知識點已經所有講解完畢,時間倉促,事物繁忙,可能會影響文章質量,還請你們多多包涵。 若是有問題,能夠直接回帖、發論壇信息或經過Email:anliupeinye@gmail.com聯繫我。

 

項目成品展現:

看看這些信息是否是就是上面用網頁以"android"爲關鍵字搜索到的?

相關文章
相關標籤/搜索