丁振凡 吳小元
摘 要:獲取數據并顯示是開發手機端app時需要解決的最核心的問題,如何有效的動態遞增顯示服務端批量數據成為其中最基本的問題。文中選取json串作為Android移動終端與遠程web server的傳輸媒介,利用Android控件ListView實現動態遞增顯示網絡數據,根據用戶需求按頁遞增加載,節省了用戶數據流量,為用戶提供更友好的體驗。
關鍵詞:Android;控件ListView;遞增顯示;服務器端;Json
中圖分類號:TP393.09 文獻標識碼:A 文章編號:2095-2163(2014)02-
Research on Data Display by Progressive Increase based on Android ListView Widget
DING Zhenfan,WU Xiaoyuan
(School of Information Engineering, East China Jiao Tong University, Nanchang 330013, China)
Abstract.The core problem of mobile app development is to get data and display, so its basic problem lies in how to dynamicly show bulk servers data efficiently in paging-display way.The text uses json as transmission media between Android platform and the remote web server,which utilizes Android widget ListView to dynamicly realise progressive increasing display of network data, and load data by paging in progressive increasing way according to client's demand in a saving way of user data traffic, therefore provide a good user experience.
Keywords: Android; ListView Widget;Progressive Increasing Display;Web Server;Json
0 引 言
隨著移動互聯網的快速發展,手機應用所需要展示的數據量也在快速增長。在手機應用中,當從遠程服務器端獲取的數據量較大,此時根據用戶實際需求,為了節省用戶網絡流量,常常需要選用動態遞增方式實現數據顯示。由于移動終端手機顯示屏的局限性,當顯示批量數據時,若由用戶自助決定是否加載更多數據,則較為合適且更人性化。
作為Android的一個基本控件,ListView對遞增式加載數據提供了很好的支持。本文利用ListView控件的該特性,以加載批量圖片資源為切入口,快捷高效地將網絡數據累增呈現在用戶面前。
1 Android OS簡介
Android是一種基于Linux的自由、且開放源代碼的操作系統,主要適用于移動設備,由Google公司和開放手機聯盟進行主導與開發。2012年11月數據顯示,Android占據全球智能手機操作系統市場76%的份額,中國市場占有率為90%。
Android系統采用了分層架構,共分為四層[1]。自上而下依次為:
(1)Applications-應用程序層。該層提供多個核心應用程序包;
(2)Application Framework-應用程序框架層。該層提供一些API框架,供用戶調用;
(3) Libraries & Android Rutime-運行庫層。該層用于提供類庫和JAVA運行環境;
(4)Linux Kernel-系統內核層。該層為底層,對安全性、內存、進程、驅動進行管理。
2 ListView控件和Adapter
在Android UI設計中,因其可以實現遞增加載的特性,控件ListView是使用非常頻繁的View。
ListView由View、適配器、數據三個元素組成,是單個View的集合,每個子View都是一個獨立的個體,通常由一個xml指定,并顯示一條數據集合[2]。為了能顯示批量數據,ListView必須與Adapter進行綁定。Adapter可以看作ListView的數據源,由Adapter傳遞數據給ListView進行顯示,二者之間的關系可如圖1所示。
圖1 Adapter提供數據給ListView
Fig.1 ListView get data provided by Adapter
Adapter又稱之為適配器,借助其可將數據和用戶界面View實現綁定。Adapter負責創建用來表示每一個條目的子View,并且提供對底層數據的訪問[3]。Android提供了多種類型的Adapter,也支持用戶自定義適配器,由此而實現ListView控件中的子View元素的布局展示。
3 按頁遞增顯示流程
在實際手機端開發中,當從服務器端獲取的數據量較多時,經常需要動態遞增(按頁遞增)處理顯示。本文采用json傳輸數據到手機端,再利用Android的ListView控件實現數據的按頁遞增顯示,按頁遞增處理流程具體則如圖2所示。
圖2 按頁遞增處理流程
Fig.2 Incremental processing by paging
由圖2可知,詳細的流程步驟分析如下:
(1)觸發手機端的事件,通過url以get方式發送http請求到服務器端,url中包含頁參數;
(2)服務器端響應http請求,返回json數據給手機端;
(3)解析json數據,將json數據封裝成能適配Adapter的數據格式;
(4)將解析的數據綁定到Adapter,并通知Adapter更新;
(5)ListView根據要求按頁遞增顯示數據。
4 按頁遞增顯示實現
由于需要實現的是加載批量圖片資源,現有Android提供的多種Adapter類不能實現此要求,故必須首先自定義Adapter類,該類繼承Android的基類BaseAdapter。在自定義的Adapter類中,方法getView()里封裝item所要呈現的convertView對象,即ListView中顯示的每個item,且每個item都是布局文件layout的形象展示。通過將布局文件layout加載到方法getView()中的convertView對象,并進行處理,就可以在ListView中展示一個子item。當adpter中綁定批量數據時,該批量數據就會通過子item在ListView中逐一展示出來,且當需要遞增顯示時,觸發按頁遞增事件后,只需要將服務器返回的數據綁定到Adapter中,并通知其更新,就可顯示ListView的遞增顯示效果。
4.1封裝數據對象及自定義Adapter類
4.1.1 對象封裝
本文以商戶信息為展示主體,先對需要展示的商戶信息封裝成Merchant類,以便在后續的數據處理中訪問其屬性和方法。
public class Merchant implements Serizable {
private Integer merchant _id;// 商戶id
private String merchant _name;// 商戶名稱
private String merchant _img_url;// 商戶圖片url
/*屬性的getter和setter方法為節省篇幅省去*/
}
4.1.2 自定義Adapter類
自定義Adapter類提供數據源模型。該類繼承BaseAdapter基類,重寫getView方法,將數據一一匹配。
public class LoaderMerchantAdapter extends BaseAdapter {
private ArrayList< Merchant > merchant _list;
private Context mContext;
public LoaderMerchantAdapter(Context context,
ArrayList< Merchant > merchant _list) {
this.mContext = context;
this. merchant _list = merchant _list;
}
public int getCount() {
return merchant _list.size();//返回數組大小,為一個動態變化的值
}
public void addItem(ArrayList< Merchant > item) {
merchant _list.addAll(item);//添加數據
}
public View getView(int position, View convertView, ViewGroup parent) {
…….//重寫getView方法
return convertView;
}
}
4.2 item的布局文件
控件ListView中每一個子item都有相同的布局,以XML文件形式展示,該 XML 文件定義了子item界面采用的布局和組件[4],布局文件中的控件元素ImageView和TextView為需要展示的數據提供載體。
<?xml version="1.0" encoding="utf-8"?>
……> android:id="@+id/ merchant _item_img" …… /> android:id="@+id/ merchant _item_name" ……/>
4.3獲取json串并解析返回數組
Json串是一種輕量級的數據交換格式,本文選取json串作為Android移動終端與遠程web server的傳輸媒介,易于閱讀和編寫,同時也易于機器解析生成用戶關心需要的數據格式[5]。通過url,該url中包含請求地址和頁參數,以get方式發送http請求到服務器端,返回一個json串,解析該json串,封裝返回數組。其中httpGetData()方法實現從服務器端獲取json串的功能。
public ArrayList< Merchant > getMerchantGroup(List
throws UnsupportedEncodingException, JSONException {
String merchantListString = httpGetData(merchantListUrl, params);
ArrayList< Merchant > merchantList = new ArrayList< Merchant >();
JSONObject merchantObject = new JSONObject(merchantListString);
……//解析json串,添加對象數據到merchanList數組中
return merchantList;
}
4.4 ListView按頁遞增顯示
4.4.1按頁遞增主要業務邏輯
創建類MerchantListActivity,繼承基類Activity。在該類中實現按頁遞增顯示的主要業務邏輯,而在onCreate()方法中進行一些初始化操作,如加載界面初始布局,將頁腳View與控件ListView綁定,給按鈕注冊按頁遞增加載監聽事件,加載初始第一頁數據。并且,initParams()方法是對屬性params進行初始化,該params為一個key-value形式的鍵值對集合,其中含有totalPage,currentPage等與頁碼相關的屬性值。
public class MerchantListActivity extends Activity{
private static final String merchantListUrl = URL_SHARE
+ "merchantapi/getmerchantpage";// 獲取商戶信息url
private ListView merchantList;
private int totalPage,currentPage;//總頁數和當前頁
private static final string GET_INITDATA_ SUCCESS=1;
private static final string GET_MOREDATA_ SUCCESS=2;
private LoaderMerchantAdapter merchant_Adapter;
private List
private Button loadMoreButton;//按頁遞增加載按鈕
private View footer;// 頁腳View,與ListView控件綁定,形成一個整體
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.merchant_list);//加載初始布局
// 獲取控件ListView的頁腳View
footer = getLayoutInflater().inflate(R.layout.load_more, null);
loadMoreButton = (Button) footer.findViewById(R.id.loadMoreButton);
loadMoreButton.setOnClickListener(buttonClick);//注冊按頁遞增加載監聽
merchantList = (ListView) findViewById(R.id.merchant_list);
merchantList.addFooterView(footer);//將頁腳View與控件ListView綁定
initParams();//初始化屬性params,含有頁參數
loadData(params); //從服務器端獲取數據
}
}
4.4.2 觸發監聽事件
注冊按頁遞增加載事件監聽,點擊按頁遞增加載按鈕,就可以觸發該監聽事件,執行按頁遞增加載流程。
OnClickListener buttonClick = new OnClickListener() {
public void onClick(View v) {
currentPage += 1;
if (currentPage <= totalPage) {
new Thread() {
public void run() {
initParams();//初始化屬性params,含有頁參數
ArrayList
Message msg = new Message();
msg.what = GET_ MOREDATA _SUCCESS;
msg.obj = itemList;
mHandler.sendMessage(msg); //Handle發送消息
}
}.start();
}
}
};
4.4.3數據獲取并發送
創建一個新的線程,進行數據請求,獲取得到數據;通過Handle-Message消息機制發送獲取的數據。
public void loadData(final List
new Thread() {
public void run() {
ArrayList
Message msg = new Message();
msg.what = GET_ INITDATA _SUCCESS;
msg.obj = itemList;
mHandler.sendMessage(msg);//Handle發送消息
}
}.start();
}
4.4.4 Handle機制處理消息數據
Handle是Message的主要處理者,截獲與自身相關聯的Message消息,通過識別Message中Message.what的狀態值[6],對不同的Message進行處理。
Handler mHandler = new Handler() {
public void handleMessage(Message msg) {
switch(msg.what){
case GET_INITDATA_SUCCESS:{//加載初始頁數據標志
ArrayList
merchant_Adapter = new LoaderMerchantAdapter(data.size(),
MerchantListActivity.this, data);
merchantList.setAdapter(merchant_Adapter);
if (totalPage == 1) //判斷頁情況
merchantList.removeFooterView(footer);
break;
}
case GET_MOREDATA_SUCCESS:{//加載非初始頁數據標志
ArrayList
merchant_Adapter.addItem(result);// 適配器添加數據
if (currentPage == totalPage) {//判斷頁情況,是否為最后一頁
merchantList.removeFooterView(footer);
}
merchant_Adapter.notifyDataSetChanged();// 通知適配器自動更新數據
break;
}
}
};
};
4.5 ListView按頁遞增加載數據界面展示
圖3為手機端的商戶信息按頁展示圖。在該界面中,點擊底部的“查看更多”按鈕,就會觸發按頁遞增加載事件,加載下一頁數據遞增展示在控件ListView中,可以上下滑動滾動條查看以前的頁數據。頁碼、總頁數和每頁包含的數據條數在url參數中指定。
圖3 商戶信息按頁展示圖
Fig.3 Business information's display figure by paging
5 ListView按頁遞增加載注意事項及優化方案
5.1 注意事項
ListView在執行按頁遞增加載時,往往會由于一些小的疏忽導致加載失敗。下面列出ListView按頁遞增加載中需要注意的一些細節:
(1)連接網絡獲取數據,加載圖片等耗時操作,必須創建一個子線程,將一些耗時操作加載進子線程中執行[7],防止Android報ANR錯誤,發生界面假死,并出現“強制關閉”的錯誤提示。在子線程中獲取數據,并將獲取的數據返回主線程中,由主線程去執行更新UI的操作。
(2)對自定義的Adapter類,重寫getCount()方法時,返回的count大小必須是動態變化的。若返回的count為一固定大小時,當在自定義Adapter類中添加數據通知其自動更新時,ListView控件檢測不到新增的數據,因而不會顯示新添加的數據。
(3)在執行耗時操作時,盡量不用AsyncTask線程類。AsyncTask內部的實現機制運用了線程執行池,由其產生的Thread對象的生命周期并不確定,這是應用程序無法控制的,容易出現內存泄露的問題。
5.2 優化方案
在ListView按頁遞增加載過程中,需要加載大量的子view。加載多張較大的圖片資源,經常會出現如下兩個問題:1)ListView滑動過程中圖片顯示重復錯亂;2)圖片OutOfMemory異常。針對以上兩個問題,本文提供了優化方案。
5.2.1 ListView滑動過程中圖片顯示重復錯亂
為了實現性能優化,ListView提供了緩存機制,ListView會緩存行item(某行對應的View),即通過Adapter的getView函數獲得每行的item。滑動過程中:
(1)如果行item已經滑出屏幕,且該item不在緩存內,則送進緩存,否則更新緩存;
(2)在獲取滑入屏幕的行item之前,通常首先判斷緩存中是否有可用的item,如果有,作為convertView參數傳遞給Adapter的getView()方法。分析ListView item緩存機制可知,出現錯亂的原因是異步加載及對象復用造成的,如果每次ListView通過Adapter的getView()方法能給對象一個標識,在異步加載完成時比較標識與當前行item的標識是否一致,一致則顯示,否則不做處理即可。
5.2.2圖片OutOfMemory異常
在ListView加載多張較大圖片資源時,經常會報java.lang.OutOfMemoryError(OOM): bitmap size exceeds VM budget異常,這就表示圖片的大小超過了dalvik為程序分配的heap的空間容量。Dalvik虛擬機會為應用程序分配固定大小的heap ,如果使用超過了該heap的空間設置,又沒有可被回收對象,就會報OOM異常,而且多張較大圖片會迅速占用空間造成OOM。由此可知,出現該異常的原因是圖片資源過大,解決方法是可對圖片資源進行處理,常使用BitmapFactory.Options方法對圖片進行scale縮放處理。
6 結束語
在Android 手機應用程序開發中,特別是一些展示類的手機app應用,ListView動態遞增加載必不可少。本文以批量加載網絡數據為切入口,介紹了ListView控件和自定義Adapter,展現了ListView按頁動態遞增加載流程及其實現,最后論述了ListView按頁動態遞增加載注意事項及相關的優化方案。文中代碼能夠正常運行,且ListView動態遞增加載的效果流暢,圖形界面美觀大方,能夠運用到商業的手機app應用中,具有一定的實用性。
參考文獻:
[1] 范鋒. Android的架構與應用開發研究[J]. 信息與電腦,2012,(5): 55-56.
[2] 張秀香.基于Android的健康管理系統客戶端的設計與實現[D].大連理工大學,2012.
[3] 劉甫迎,劉焱.Android移動編程實用教程[M].電子工業出版社,2012.
[4] Jason Ostrander.Android UI Fundamentals[M].劉文斌譯.北京:人民郵電出版社,2012.
[5] 王曉禹,石麗. 基于 JSON 實現 Android 智能終端與Web 服務器“面向對象”的信息交換[J]. 數字技術與應用,2012(4) : 224-225.
[6] 李小群,趙慧斌,孫玉芳.進程間通信機制的分析與比較[J].計算機科學,2002,29(11): 18-19.
[7] 彭濤,孫連英.回調機制及其在 Android 應用開發中的應用[J].北京聯合大學學報,2013,27(2): 70-71.