這是一個簡單的示意圖,告訴我們,數據是由網頁從數據庫中取出,我們要爲這個系統做客戶端,我們就應該這樣去改造它。
所以我繼續找,經過短暫的觀察,發現入口在這裏
我們先看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。
-
//實例化HttpClient
-
HttpClient client = new HttpClient();
-
//Stream頁面裏面有Host地址 端口是80
-
client.getHostConfiguration().setHost("http://coin.lib.scuec.edu.cn", 80);
-
//用目標地址 實例一個POST方法
-
PostMethod post = new PostMethod("http://coin.lib.scuec.edu.cn/cgi-bin/IlaswebBib");
-
//將需要的鍵值對寫出來
-
NameValuePair beg = new NameValuePair("FLD_DAT_BEG" , “”);
-
NameValuePair end = new NameValuePair("FLD_DAT_END" , “”);
-
NameValuePair submit = new NameValuePair("submit" , "查 詢"));
-
NameValuePair vIndex = new NameValuePair("v_index" , “TITLE”);
-
NameValuePair vLogicSrch = new NameValuePair("v_LogicSrch" , "0");
-
NameValuePair vPagenum = new NameValuePair("v_pagenum" , "10");
-
NameValuePair vSeldatabase = new NameValuePair("v_seldatabase" , "0");
-
NameValuePair vValue = new NameValuePair("v_value" ,”android”);
-
-
//給POST方法加入上述鍵值對
-
post.setRequestBody(new NameValuePair[] {beg , end , submit , vIndex , vLogicSrch , vPagenum , vSeldatabase , vValue});
-
//執行POST方法
-
client.executeMethod(post);
-
//將POST返回的數據以流的形式讀入,再把輸入流流至一個buff緩衝字節數組
-
//StreamTool類是我自己寫的一個工具類,其內容將在下文附出
-
byte[] buff = StreamTool.readInputStream(post.getResponseBodyAsStream());
-
//將返回的內容格式化爲String存在html中
-
String html = new String(buff);
-
//任務完成了,釋放連接
-
post.releaseConnection();
-
//StreamTool類如下
-
public class StreamTool {
-
/**
-
* 從輸入流中獲取數據
-
* @param inputStream 輸入流
-
* @return 字節數組
-
* @throws Exception
-
*/
-
public static byte[] readInputStream(InputStream inputStream) throws Exception
-
{
-
//實例化一個輸出流
-
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
-
//一個1024字節的緩衝字節數組
-
byte[] buffer = new byte[1024];
-
int len = 0;
-
//讀流的基本知識
-
while ((len=inputStream.read(buffer)) != -1) {
-
outputStream.write(buffer, 0, len);
-
}
-
//用完要關,大家都懂的
-
inputStream.close();
-
return outputStream.toByteArray();
-
}
-
}
- System.out.println(html);
沒錯,就是我們上文看到的HttpWatch 抓到的返回Content,也就是一段HTML代碼,這說明,我們模擬瀏覽器POST請求成功了!
我們再試試別的搜索內容,來一個”android開發”(即將v_value鍵值對的值改成”android開發”),這時運行後,我們卻從控制檯得到了這樣的結果:
經過幾次試驗後,發現一個規律,只要搜索內容中包括中文,就搜不到。
所以可以判定是中文編碼的問題,(在開發這類客戶端時候,中文編碼往往是個很具困難的問題。安卓巴士開發3羣的某羣友提到:服務器交流用的編碼是”ISO-8859-1”,跟我起初用到的編碼一致,但真實性仍需考證)所以我們修改上面的代碼,將代表搜索內容的v_value對應的值編碼爲”ISO-8859-1”
就將上段代碼中的
- NameValuePair vValue = new NameValuePair("v_value" ,”android”);
-
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基礎知識)
-
//所以有
-
List<Map<String,Object>> list = new ArrayList<Map<String,Object>>();
-
//開始使用Jsoup
-
//Jsoup支援一個Document類 將剛纔的html轉化成Document
-
Document document = Jsoup.parse(html);
-
//一個Document又由elements組成 我們選擇”tr”開頭的標籤,存入 trs元素羣中
-
Elements trs = document.select("tr");
-
//得到整個HTML中包含tr的標籤的個數
-
int totalTrs = trs.size();
-
//我們可以觀察上面沒有搜索結果的那個HTML。發現,如果totalTrs<=3就表示沒結果。
-
//只要有書目結果totalTrs必定大於3,於是
-
if(totalTrs > 3)
-
for(int i = 0;i < totalTrs - 3;i++)
-
{
-
//觀察HTML,從第i+2個tr開始,包含的纔是我們要的書目信息
-
//我們從每個tr中選出td標籤元素羣
-
Elements tds = trs.get(i + 2).select("td");
-
//得到每個tr中td的個數
-
int totalTds = tds.size();
-
//一個臨時的HashMap,裏面是String-Object鍵值對
-
Map<String,Object> map = new HashMap<String,Object>();
-
//j是一個標識數
-
for(int j =0;j < totalTds ;j++)
-
{
-
switch (j) {
-
//0表示第一個,即書名
-
//put方法即向map加入一條鍵值對
-
//html()方法就得到標籤括起來的內容
-
case 0:
-
map.put("book_title", tds.get(j).html().toString());
-
break;
-
case 1:
-
//1表示第二個,即作者
-
map.put("book_author", tds.get(j).html().toString());
-
break;
-
case 2:
-
//2表示第三個,即出版信息
-
map.put("book_press", tds.get(j).html().toString());
-
break;
-
case 3:
-
//3表示第四個,即頁數
-
map.put("book_page", tds.get(j).html().toString());
-
break;
-
case 4:
-
//4表示第五個,即價格
-
map.put("book_price", tds.get(j).html().toString());
-
break;
-
case 5:
-
//5表示第六個,即索取號
-
map.put("book_noFor", tds.get(j).html().toString());
-
break;
-
case 6:
-
//6表示第七個,即那段網址
-
//那段網址td中又包含一個a標籤,a標籤的href屬性的值就是網址
-
//attr(“href”)可以返回href屬性的值
-
map.put("book_detail", tds.get(j).select("a").attr("href").toString());
-
break;
-
default:
-
break;
-
}
-
}
-
list.add(map);
- }
上面所有代碼調通後,我們只需一些簡單的複製粘貼,就可以放在我們的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方法
-
@Override
-
public void onScroll(AbsListView view, int firstVisibleItem,
-
int visibleItemCount, int totalItemCount) {
-
// TODO Auto-generated method stub
-
//關鍵的判斷代碼,這句話表示用戶將ListView拉至最底部
-
if(firstVisibleItem + visibleItemCount == totalItemCount)
-
//你只需要把繼續得到下面十項的代碼寫在這裏,就可以實現上述功能了。
-
//同樣再使用一次POST方法,不再贅述
- //代碼由於同樣原因不予以展示,敬請諒解
至此,文章開頭的幾個知識點已經全部講解完畢,時間倉促,事物繁忙,可能會影響文章質量,還請大家多多包涵。 如果有問題,可以直接回帖、發論壇信息或通過Email:[email protected]聯繫我。
項目成品展示:
看看這些信息是不是就是上面用網頁以"android"爲關鍵字搜索到的?
最後感謝安卓巴士論壇、安卓王子、安卓巴士開發3羣的羣友的支持。