異步加載的練習demo
主要涉及知識點:
1.解析json格式資料,主要包括圖檔,文本
2.使用asyntask異步方式從網絡下載下傳圖檔
3.baseadapter的“優雅”使用
4.使用lru緩存算法
5.改進加載:僅在listview滑動停止後才加載可見項,滑動中不加載
涉及到的知識點如上,這裡做一個小結,僅對一些代碼片段分析
1、異步加載
主要有倆個原因
【2】耗時操作阻塞ui線程(網絡下載下傳等 )
常用的倆種方式
【1】多線程/線程池
【2】asyntask (其實它底層也是線程池 核心線程數5,最大線程數128)
2、json數組解析
采用的是某網站的提供的接口(http://www.imooc.com/api/teacher?type=4&num=30)傳回的json資料如下
<span style="font-family:microsoft yahei;">/*
* json資料bean,這裡擷取三個屬性
* 圖檔
* 标題
* 内容
*/
public class newsbean {
public string newsiconurl;// 圖檔的網址
public string newstitle;
public string newscontent;
}</span>
下面就開始解析json,并将解析的資料放到list<newsbean>,代碼片段在mainactivity.java中
* 将url對應的json格式資料轉化為所封裝的newsbean
*/
// 擷取json傳回格式資料
private list<newsbean> getjsondata(string url) {
list<newsbean> newsbeanlist = new arraylist<newsbean>();
try {
string jsonstring = readstream(new url(url).openstream());// 直接根據url擷取網絡資料傳回inputstream類型
jsonobject jsonobject;
newsbean newsbean;
// log.d("xsf", jsonstring); //列印測試
// 将json資料放入jsonobject中,然後通過jsonarray擷取所需要的資料集合
try {
jsonobject = new jsonobject(jsonstring);
jsonarray jsonarray = jsonobject.getjsonarray("data");
// 通過for循環取出jsonarray每個值,放到newsbean的集合中去
for (int i = 0; i < jsonarray.length(); i++) {
jsonobject = jsonarray.getjsonobject(i);
newsbean = new newsbean();
newsbean.newsiconurl = jsonobject.getstring("picsmall");// 擷取小圖檔
newsbean.newstitle = jsonobject.getstring("name");// 擷取title
newsbean.newscontent = jsonobject.getstring("description");// 擷取内容
newsbeanlist.add(newsbean);
}
} catch (jsonexception e) {
e.printstacktrace();
}
} catch (ioexception e) {
e.printstacktrace();
}
return newsbeanlist;
}
</span>
整個邏輯在 代碼注釋中很清楚,通過url下載下傳資料位string,先擷取最外一級jsonobj,然後擷取内部jsonarray數組,在for循環中子jsonobj,通過getstring擷取每個子jsonobj中的标簽對應的内容。(結合前面的json圖來看)最後統一放到newslist中,作為後面listview擴充卡apapter的資料源
這裡還涉及到java io流的操string jsonstring = readstream(new url(url).openstream());,主要是吧new url(url).openstream()得到的位元組流蹭蹭封裝成buffer(核心依然是裝飾者模式),然後拼接成string形式傳回,代碼片段在mainactivity.java中
<span style="font-family:microsoft yahei;">// 位元組流轉字元流,讀取函數,解析網頁傳回的數組
private string readstream(inputstream is) {
inputstreamreader isr;
string result = "";
string line = "";
isr = new inputstreamreader(is, "utf-8");// 位元組流封裝成字元流并且指定為utf-8格式
bufferedreader br = new bufferedreader(isr);// 将字元流通過buffer形式讀取出來
while ((line = br.readline()) != null) {
result += line;
} catch (unsupportedencodingexception e) {
return result;
}</span>
3、asyntask異步加載
先來看看asynctask的定義:
public abstract class asynctask<params, progress, result> { }三種泛型類型分别代表“啟動任務執行的輸入參數”、“背景任務執行的進度”、“背景計算結果的類型”。
在特定場合下,并不是所有類型都被使用,如果沒有被使用,可以用java.lang.void類型代替。一個異步任務的執行一般包括以下幾個步驟:
【1】.execute(params... params),執行一個異步任務,需要我們在代碼中調用此方法,觸發異步任務的執行。(在ui線程中執行,不可混淆)
以下幾個是asyntask繼承時可以重寫的函數
【2】.onpreexecute(),在execute(params... params)被調用後立即執行,一般用來在執行背景任務前對ui做一些标記。
【3】.doinbackground(params... params),在onpreexecute()完成後立即執行,用于執行較為費時的操作,此方法将接收輸入參數和傳回計算結果。在執行過程中可以調用publishprogress(progress... values)來更新進度資訊。
【4】.onprogressupdate(progress... values),在調用publishprogress(progress... values)時,此方法被執行,直接将進度資訊更新到ui元件上。
【5】.onpostexecute(result result),當背景操作結束時,此方法将會被調用,計算結果将做為參數傳遞到此方法中,直接将結果顯示到ui元件上。
在使用的時候,有幾點需要格外注意:
【1】.異步任務的執行個體必須在ui線程中建立。
【2】.execute(params... params)方法必須在ui線程中調用。
【3】.不要手動調用onpreexecute(),doinbackground(params... params),onprogressupdate(progress... values),onpostexecute(result result)這幾個方法。
【4】.不能在doinbackground(params... params)中更改ui元件的資訊。
【5】.一個任務執行個體隻能執行一次,如果執行第二次将會抛出異常。
在mainactivity.java中,通過該方式進行異步加載json資料
詳細見代碼注釋,邏輯比較簡單
在主線程中通過execute啟動asyntask
<code><code></code></code>
@override
protected void oncreate(bundle savedinstancestate) {
super.oncreate(savedinstancestate);
setcontentview(r.layout.activity_main);
// 擷取listview控件
mlistview = (listview) findviewbyid(r.id.tv_main);
// 在主線程中啟動asyntask
new newsasyntask().execute(url);
// 實作網絡的異步通路
/*
* asyntask 輸入三個參數 params,這裡需要傳入url網址,是以為string類型 progress 這裡不需要傳回進度顯示
* 是以為void result 為 json解析之後的資料bean的集合
class newsasyntask extends asynctask<string, void, list<newsbean>> {
// 通過擷取newsbean集合得到資料傳遞到adapter中,這樣可以顯示出每個json資料
@override
protected list<newsbean> doinbackground(string... params) {
return getjsondata(params[0]);// 這裡的參數隻有一個url網址
// 将生成的nesbean設定給listview
protected void onpostexecute(list<newsbean> newsbeans) {
super.onpostexecute(newsbeans);
newsadapter adapter = new newsadapter(mainactivity.this, newsbeans,
mlistview);
mlistview.setadapter(adapter);
4 baseadapter的“優雅”使用
主要包含以下幾點
1、自定義adpter繼承baseadpter;
2、定義變量:list<newsbean>;layoutinflater;
3、重寫構造函數newsadpter(context context, list<newsbean> data)。
4、文藝方式重寫getview()方法。
5、自定義類viewholder,映射相關的view對象
(擴充卡是架起資料到界面顯示的一座橋梁,普通listview的核心就是在擴充卡上下功夫)
在getview中通過viewholder儲存資料,結合settag來給viewholder打标簽,來解決listview滑動圖檔加載錯位的問題,代碼片段在newsadapter.java中
public view getview(int position, view convertview, viewgroup parent) {
// viewholder方式
viewholder viewholder = null;
if (convertview == null) {
viewholder = new viewholder();
convertview = minflater.inflate(r.layout.item_layout, null);
// 對viewholder元素進行初始化
viewholder.ivicon = (imageview) convertview
.findviewbyid(r.id.tv_icon);
viewholder.tvtitle = (textview) convertview
.findviewbyid(r.id.tv_title);
viewholder.tvcontent = (textview) convertview
.findviewbyid(r.id.tv_content);
convertview.settag(viewholder);
} else {
viewholder = (viewholder) convertview.gettag();
viewholder.ivicon.setimageresource(r.drawable.ic_launcher);
/*
* 防止listview加載圖檔出現錯位,非常重要 以下倆行代碼很重要,是以在imageloader擷取圖檔需要進行判斷
*/
string url = mlist.get(position).newsiconurl;
viewholder.ivicon.settag(url);// 将圖檔和對應的url進行綁定
// 使用多線程方式加載實際圖檔
* new imageloadr().showimagebythread(viewholder.ivicon, url);
// 使用asynctask加載實際圖檔
// new imageloadr().showimagebyasynctask(viewholder.ivicon, url);
mimageloader.showimagebyasynctask(viewholder.ivicon, url);
viewholder.tvtitle.settext(mlist.get(position).newstitle);
viewholder.tvcontent.settext(mlist.get(position).newscontent);
return convertview;
class viewholder {
public textview tvtitle, tvcontent;
public imageview ivicon;
這裡通過
string url = mlist.get(position).newsiconurl;
con.settag(url);// 将圖檔和對應的url進行綁定
然後調用asyntask異步方式,開始加載圖檔,關于加載圖檔這裡采用了lru算法,下面會分析。
5、優化的listview圖檔加載
通常在listview網絡加載圖檔時,我們通常會做這樣的處理: 僅在listview滑動停止後加載圖檔或者文字,這樣可以減少卡頓
實作邏輯:在listview的adapetr中實作onscrolllistener接口,需要重寫倆個函數
public void onscrollstatechanged(abslistview view, int scrollstate) 滾動狀态改變觸發,在這裡可以判斷滾動狀态進而确定是否需要加載
public void onscroll(abslistview view, int firstvisibleitem, int visibleitemcount, int totalitemcount) 一直都會觸發,可以記錄此時滾動的在螢幕中起始位置,便于加載處理,代碼片段在newsadapter.java中
public void onscrollstatechanged(abslistview view, int scrollstate) {
// listview滑動狀态切換的時候才調用
// 判斷listview滾動狀态
if (scrollstate == scroll_state_idle) {
// 滾動停止狀态,加載可見項
mimageloader.loadimages(mstart, mend);
// 停止任務
mimageloader.cancelalltask();
@override
public void onscroll(abslistview view, int firstvisibleitem,
int visibleitemcount, int totalitemcount) {
// 整個滑動過程都會調用,不斷擷取目前可見項目和最後一個可見項目
mstart = firstvisibleitem;
mend = firstvisibleitem + visibleitemcount;
if (mfirrstin && visibleitemcount > 0) {
// 手動加載第一屏
mfirrstin = false;
6、 圖檔下載下傳 核心代碼 imageloader.java
有以下幾個看點
對于從網絡上擷取圖檔這種需求,我們都要使用cache來将我們的圖檔緩存起來,尤其是對于listview這種,不能每次我們滑動listview就重新從網上下載下傳圖檔,這樣會很浪費資源而且浪費手機的流量。在android中,已經為我們提供了一個用于緩存的類lrucache。我們可以使用這個類來實作我們對于圖檔資源的緩存。(lrucache是将圖檔緩存在記憶體中,而還有個第三方的類disklrucache來将圖檔緩存到手機的disk上,而我們大型的app,一般都是将lrucache和disklrucache結合起來使用,形成一個memory hierarchy。)
【1】需要預設緩存占sd卡的大小 代碼片段在imageloader.java中
【2】添加到緩存 (可以通過url和bitmap的鍵值對方式關聯)
【3】從緩存擷取圖檔
lru本質就是linkhashmap,是以具備put get操作,lru這裡就不擴充開了,代碼片段如下
public imageloadr(listview listview) {
mlistview = listview;
mtask = new hashset<imageloadr.imageloaderasyntask>();
// 擷取最大可使用記憶體
int maxmemory = (int) runtime.getruntime().maxmemory();
// 設定所需緩存大小
int cachesize = maxmemory / 4;
mcaches = new lrucache<string, bitmap>(cachesize) {
@override
protected int sizeof(string key, bitmap value) {
// 在每次存入緩存時候調用,告訴系統傳入對象的大小
return value.getbytecount();
};
// 增加到緩存
public void addbitmaptocache(string url, bitmap bitmap) {
if (getbitmapfromcache(url) == null) {
// 判斷目前是否存在url所指定的圖檔
mcaches.put(url, bitmap);
// 從緩存中擷取資料
public bitmap getbitmapfromcache(string url) {
return mcaches.get(url);
6.2、加載圖檔
使用了loadimages(start,end)該函數主要用來加載目前顯示listview從start到end的圖檔用來配合listview僅在活動停止後加載,假設此時滑動停止螢幕listview在12-20行之間則隻加載該區間的圖檔文本
原理:
【1】将start,end作為for循環,由于在newsadapter.java中已經記錄了所有的urls,因而string url = newsadapter.urls[i] 并且i在[start,end]之間,這樣就将url和start,end對應起來
【2】這樣同樣使用asyntask來加載圖檔,這裡使用一個asyntask集合來管理,當開始下載下傳時加入集合,下載下傳完成回調時在onpostexecute中将該asyntask從中remove掉
// 用來加載從start到end的所有圖檔
public void loadimages(int start, int end) {
for (int i = start; i < end; i++) {
string url = newsadapter.urls[i];// 擷取到了從start開始到end所有rul
bitmap bitmap = getbitmapfromcache(url);
if (bitmap == null) {
// 緩存中沒有該圖檔則直接加載
// new imageloaderasyntask(, url).execute(url);
imageloaderasyntask task = new imageloaderasyntask(url);
task.execute(url);
mtask.add(task);// 将該task儲存到目前活動task集合中
/*
* // 緩存中有該圖檔直接加載 // imageview.setimagebitmap(bitmap);
*/
// 通過tag找到imageview
imageview imageview = (imageview) mlistview
.findviewwithtag(url);
imageview.setimagebitmap(bitmap);
}
private class imageloaderasyntask extends asynctask<string, void, bitmap> {
// private imageview
// mimageview;不再需要,可以通過listview的findviewwithtag(url)找到imageview
// 防止listview圖檔加載錯位做的處理
private string murl;
* public imageloaderasyntask(imageview imageview, string url) {
* mimageview = imageview; murl = url; }
public imageloaderasyntask(string url) {
murl = url;
protected bitmap doinbackground(string... params) {
string url = params[0];
// 從網絡中擷取圖檔
bitmap bitmap = getbitmapfromurl(url);
if (bitmap != null) {
addbitmaptocache(url, bitmap);
return bitmap;
protected void onpostexecute(bitmap bitmap) {
super.onpostexecute(bitmap);
// 設定圖檔時增加判斷防止listview加載圖檔錯位
* if (mimageview.gettag().equals(murl)) {
* mimageview.setimagebitmap(bitmap); }
imageview imageview = (imageview) mlistview.findviewwithtag(murl);
if (imageview != null && bitmap != null) {
// 設定完bitmap之後表明該task已經失去作用,需要從集合中移除
mtask.remove(this);
}
轉載:http://blog.csdn.net/xsf50717/article/details/49024075