title: 第12章 Bitmap的加載和Cache
tags: []
notebook: Android開發(fā)藝術(shù)探索
第12章 Bitmap的加載和Cache
[TOC]
本章主要介紹了三個方面的知識:
- 圖片加載:如何有效的加載一個Bitmap
- 緩存策略:LruCache和DiskLruCache
- 列表的滑動流暢性:如何優(yōu)化列表的卡頓現(xiàn)象
12.1 Bitmap的高效加載
首先有4種Bitmap的加載方法,都是有BitmapFactory提供的
加載方法 | 加載來源 |
---|---|
decodeFile | 文件 |
decodeResource | 資源 |
decodeStream | 輸入流 |
decodeByteArray | 字節(jié)數(shù)組 |
*其中decodeFile和decodeResource是間接調(diào)用了decodeStream方法
1.核心思想
加載所需尺寸的圖片。即原本圖片很大岸啡,但是實(shí)際上需要的尺寸很小淤翔,比如頭像只需要縮略圖线得,此時可以計(jì)算出采樣率爆价,然后根據(jù)采樣率來加載圖片
2.壓縮方法
通過設(shè)置BitmapFactory.Options對象的inSampleSize來進(jìn)行圖片的采樣計(jì)算和縮放奥喻,這里需要了解的是整個圖片縮放比例是1/(inSampleSize的2次方)光绕,而寬高為原來的1/inSampleSize铭段,即inSampleSize為1時不縮放,為2時縮放為原來的1/4(寬高都為原來的1/2)隅很,為4時縮放為原來的1/16(寬高都為原來的1/4)
采樣原則:縮放比例一定要大于等于需求寬高。比如ImageView的大小為100*100像素率碾,原始圖片為200*300叔营,那么inSampleSize應(yīng)該為2,此時壓縮后的圖片為100*150 >= 100*100
3.采樣縮放流程
總體流程應(yīng)該分為:獲取圖片的寬高->獲取所需的寬高->計(jì)算采樣率->壓縮圖片
具體流程如下:
- 將BitmapFactory.Options的inJustDecodeBounds參數(shù)設(shè)為true所宰,并通過BitmapFactory和Options加載圖片(此時加載的只是原始圖片的寬高)
- 從BitmapFactory.Options中獲取到原始圖片的寬高绒尊,對應(yīng)outWidth和outHeight
- 通過實(shí)際所需的寬高reqWidth和reqHeight以及outWidth和outHeight計(jì)算出采樣率,并設(shè)置到Options的inSampleSize中
- 將BitmapFactory.Options的inJustDecodeBounds參數(shù)設(shè)為false仔粥,然后重新通過BitmapFactory和Options加載圖片婴谱,此時圖片就是根據(jù)實(shí)際需要壓縮過的圖片
*其中inJustDecodeBounds參數(shù)的作用是使BitmapFactory只加載圖片的寬高,因?yàn)椴蓸勇实挠?jì)算并不需要加載整個圖片躯泰,提高了效率
上面流程的代碼實(shí)現(xiàn)如下
public class ImageResizer {
public ImageResizer(){}
/**
* 從資源文件中獲取相應(yīng)的圖片
* @param res
* @param resId
* @param reqWidth
* @param reqHeight
* @return
*/
public Bitmap decodeSampleBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight)
{
Options options = new Options();
//設(shè)置只加載寬高標(biāo)志位
options.inJustDecodeBounds = true;
//加載原始圖片寬高到Options中
BitmapFactory.decodeResource(res, resId, options);
//計(jì)算采樣率谭羔,通過所需寬高和原始圖片寬高
options.inSampleSize = calculateSampleSize(reqWidth, reqHeight, options);
//還原并再次加載圖片
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
}
/**
* 從文件描述符中獲取相應(yīng)的圖片
* @param reqWidth
* @param reqHeight
* @param options
* @return
*/
public Bitmap decodeSampleBitmapFromFileDescriptor(FileDescriptor fd, int reqWidth, int reqHeight)
{
Options options = new Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFileDescriptor(fd, null, options);
options.inSampleSize = calculateSampleSize(reqWidth, reqHeight, options);
options.inJustDecodeBounds = false;
return BitmapFactory.decodeFileDescriptor(fd, null, options);
}
//計(jì)算采樣率
public static int calculateSampleSize(int reqWidth, int reqHeight, Options options)
{
//如果傳入0參數(shù),則將采樣率設(shè)成1斟冕,即不壓縮
if (reqWidth == 0 || reqHeight == 0) {
return 1;
}
int inSampleSize = 1;
int width = options.outWidth;
int height = options.outHeight;
//當(dāng)所需寬高比實(shí)際寬高小時才進(jìn)行壓縮
if(reqWidth < width && reqHeight < height)
{
int halfWidth = width >>= 1;
int halfHeight = height >>= 1;
//保證壓縮后的寬高不能小于所需寬高
while(reqWidth <= halfWidth && reqHeight <= halfHeight)
{
inSampleSize <<= 1;
halfWidth /= inSampleSize;
halfHeight /= inSampleSize;
}
}
return inSampleSize;
}
}
實(shí)際使用的時候根據(jù)需要壓縮圖片口糕,比如ImageView所期望的圖片大小為100*100,則可以這樣實(shí)現(xiàn)
iv.setImageBitmap(mImageResizer.decodeSampleBitmapFromResource(getResources(), R.drawable.lizhuo, 100, 100));
12.2 Android中的緩存策略
Android中三級緩存策略:內(nèi)存-磁盤-網(wǎng)絡(luò)磕蛇。即在獲取資源時比如圖片景描,先從內(nèi)存緩存中讀取,如果沒有則從磁盤緩存中讀取秀撇,最后還沒有再從網(wǎng)絡(luò)中拉取圖片超棺。
Android中通過LruCache實(shí)現(xiàn)內(nèi)存緩存,通過DiskLruCache實(shí)現(xiàn)磁盤緩存呵燕,它們采用的都是LRU(Least Recently Used)最近最少使用算法來移除緩存
12.2.1 LruCache
使用LruCache類時建議使用v4包中的以兼容Android2.2版本棠绘,它是在Android3.1開始默認(rèn)所提供的一個緩存類
1.LruCache實(shí)現(xiàn)原理
LruCache底層是使用LinkedHashMap來實(shí)現(xiàn)的,所以LruCache也是一個泛型類,利用LinkedHashMap的accessOrder屬性可以實(shí)現(xiàn)LRU算法氧苍。
因?yàn)長inkedHashMap利用一個雙重鏈接鏈表來維護(hù)所有條目夜矗,accessOrder屬性決定了LinkedHashMap的鏈表順序
* 為true則以訪問順序維護(hù)鏈表,即被訪問過的元素會安排到鏈表的尾部让虐;
* 為false則以插入的順序維護(hù)鏈表紊撕。
而LruCache利用的正是accessOrder為true的LinkedHashMap來實(shí)現(xiàn)LRU算法的。
- put:通過LinkedHashMap的put來實(shí)現(xiàn)元素的插入赡突,在插入后調(diào)用trimToSize來調(diào)整緩存的大小对扶,如果大于設(shè)定的最大緩存大小,則將LinkedHashMap頭部的節(jié)點(diǎn)刪除惭缰,直到size小于maxSize浪南。注:插入的過程還是要先尋找有沒有相同的key的數(shù)據(jù),如果有則替換掉舊值漱受,并且將該節(jié)點(diǎn)移到鏈表的尾部
- get:通過LinkedHashMap的get來實(shí)現(xiàn)络凿,由于accessOrder為true,因此被訪問到的元素會被調(diào)整到鏈表的尾部拜效,因此不常被訪問的元素就會留到鏈表的頭部喷众,當(dāng)觸發(fā)清理緩存時不常被訪問的元素就會被刪除,這里是實(shí)現(xiàn)LRU最關(guān)鍵的地方
- remove:通過LinkedHashMap的remove來實(shí)現(xiàn)
- size:LruCache中很重要的兩個成員size和maxSize紧憾,因?yàn)榍謇砭彺娴氖窃趕ize>maxSize時觸發(fā)的到千,因此在初始化的時候要傳入maxSize定義緩存的大小,然后重寫sizeOf方法赴穗,因?yàn)長ruCache是通過sizeOf方法來計(jì)算每次添加一個元素或者刪除一個元素而改變的size的大小
引用的分類
- 強(qiáng)引用:直接的對象引用憔四,不會被gc回收
- 軟引用:系統(tǒng)內(nèi)存不足時,對象會被gc回收
- 弱引用:對象隨時被gc回收
LruCache里的LinkedHashMap以強(qiáng)引用的方式存儲外界的緩存對象
2.LruCache的使用
- 設(shè)計(jì)LruCache的最大緩存大邪忝肌:一般是通過計(jì)算當(dāng)前可用的內(nèi)存大小繼而來獲取到應(yīng)該設(shè)置的緩存大小
- 創(chuàng)建LruCache對象:傳入最大緩存大小的參數(shù)了赵,同時重寫sizeOf方法來設(shè)置存在LruCache里的每個對象的大小
- 通過put、get和remove方法來實(shí)現(xiàn)數(shù)據(jù)的添加甸赃、獲取和刪除
//初始化LruCache對象
public void initLruCache()
{
//獲取當(dāng)前進(jìn)程的可用內(nèi)存柿汛,轉(zhuǎn)換成KB單位
int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
//分配緩存的大小
int maxSize = maxMemory / 8;
//創(chuàng)建LruCache對象并重寫sizeOf方法
lruCache = new LruCache<String, Bitmap>(maxSize)
{
@Override
protected int sizeOf(String key, Bitmap value) {
// TODO Auto-generated method stub
return value.getWidth() * value.getHeight() / 1024;
}
};
}
//LruCache對數(shù)據(jù)的操作
public void fun()
{
//添加數(shù)據(jù)
lruCache.put("lizhuo", bm1);
lruCache.put("sushe", bm2);
lruCache.put("jiqian", bm3);
//獲取數(shù)據(jù)
Bitmap b1 = (lruCache.get("lizhuo"));
Bitmap b2 = (lruCache.get("sushe"));
Bitmap b3 = (lruCache.get("jiqian"));
//刪除數(shù)據(jù)
lruCache.remove("sushe");
}
一般會對將數(shù)據(jù)添加進(jìn)內(nèi)存和獲取數(shù)據(jù)做一個封裝
/**
* 將圖片存入緩存
* @param key 圖片的url轉(zhuǎn)化成的key
* @param bitmap
*/
private void addBitmapToMemoryCache(String key, Bitmap bitmap)
{
if(getBitmapFromMemoryCache(key) == null)
{
mLruCache.put(key, bitmap);
}
}
private Bitmap getBitmapFromMemoryCache(String key)
{
return mLruCache.get(key);
}
/**
* 因?yàn)橥饨缫话惬@取到的是url而不是key,因此再做一層封裝
* @param url http url
* @return bitmap
*/
private Bitmap loadBitmapFromMemoryCache(String url)
{
final String key = hashKeyFromUrl(url);
return getBitmapFromMemoryCache(key);
}
12.2.2 DiskLruCache
DiskLruCache用于實(shí)現(xiàn)磁盤緩存埠对,通過將緩存對象寫入文件系統(tǒng)從而實(shí)現(xiàn)緩存的效果络断。DiskLruCache并不屬于Android SDK的一部分,需要另行添加
注:由于DiskLruCache是采用文件存儲的项玛,存儲和讀取都是通過IOStream來處理的貌笨,所以并不存在什么類型,所以DiskLruCache并不是泛型類襟沮,不能像LurCache一樣添加泛型
1.DiskLruCache實(shí)現(xiàn)原理
DiskLruCache是通過文件的形式將數(shù)據(jù)存在磁盤上的锥惋,和LruCache一樣昌腰,是通過LinkedHashMap來實(shí)現(xiàn)LRU算法的,不同的是膀跌,LruCache是直接將數(shù)據(jù)存在LinkedHashMap當(dāng)中遭商,即key對應(yīng)的value;而DiskLruCache在LinkedHashMap中存儲的值是Entry對象捅伤,可以認(rèn)為它指向磁盤中的文件株婴,因此DiskLruCache對文件數(shù)據(jù)的操作(寫入、讀仁钊稀)是通過Entry對象間接進(jìn)行的。以下是各數(shù)據(jù)操作的原理
- 添加數(shù)據(jù):Editor->OutputStream->Entry->File
先是獲取到對應(yīng)Key的Editor大审,然后Editor里有LinkekHashMap中對應(yīng)的Entry蘸际,Entry對應(yīng)的就是緩存文件,因此通過Editor的OutputStream可以將數(shù)據(jù)直接寫到對應(yīng)的緩存文件當(dāng)中徒扶。當(dāng)然中間的過程還是要先查找粮彤,如果查找到了直接換舊值,并且把節(jié)點(diǎn)移到鏈表的尾部姜骡,這跟LruCache一樣导坟,只不過是通過Editor的OutputStream來添加數(shù)據(jù)
其實(shí)仔細(xì)想想也不難理解啊,因?yàn)榫彺娴氖俏募Τ海募膶懭肟隙ㄊ荗utputStream來達(dá)到的惫周,而Edittor就是將OutputStream和Entry連接起來的橋梁,這么想就理解了為什么DiskLruCache的添加數(shù)據(jù)比LruCache麻煩 - 獲取數(shù)據(jù):Snapshot->InputStream->Entry->File
上面添加數(shù)據(jù)過程理解之后獲取數(shù)據(jù)就簡單多了康栈,同樣要讀取文件是需要InputStream的递递,而Snapshot就是將LinkedHashMap中對應(yīng)key的Entry和InputStream連接的對象,然后從Snapshot對象中可以獲取到文件的輸入流從而達(dá)到讀取緩存文件的效果 - 刪除數(shù)據(jù):通過LinkedHashMap直接刪除對應(yīng)key的Entry
- 日志文件:DiskLruCache中的特別的地方還有一個日志文件journal
它用于記錄DiskLruCache進(jìn)行過的數(shù)據(jù)操作啥么,DiskLruCache能夠正常工作的前提就是依賴journal文件中的內(nèi)容登舞,它一共有DIRTY,CLEAN,REMOVE和READ四種前綴,分別記錄了正在修改的數(shù)據(jù)悬荣,干凈的菠秒,被移除的和被讀取的數(shù)據(jù)。
這里強(qiáng)調(diào)一下Editor和Snapshot的使用氯迂,以免對它們的使用不夠清楚了解践叠。它們可以理解成在DiskLruCache中,每個key對應(yīng)一個Editor和一個Snapshot囚戚,每個Editor和Snapshot又對應(yīng)著一個Entry繼而對應(yīng)著一個文件FIle酵熙,因此在獲取Editor或者Snapshot對象時需要相應(yīng)的key,如下:
Editor editor = mDiskLruCache.edit(key);
OutputStream = editor.newOutputStream(0);
Snapshot snapshot = mDiskLruCache.get(key);
FileInputStream fileInputStream = snapshot.getInputStream(0);
2.DiskLruCache的使用
使用過程和LruCache大同小異驰坊,還是分配空間匾二,創(chuàng)建對象和數(shù)據(jù)操作,由于DiskLruCache是將數(shù)據(jù)存在磁盤上的,因此比LruCache多了一步設(shè)置緩存目錄
- 計(jì)算分配DiskLruCache分配空間大胁烀辍:設(shè)成50MB的寫法為1024*1024*50
- 設(shè)置緩存目錄:通常都會存放在 /sdcard/Android/data/ "application package"/cache 這個路徑下面皮璧,但同時我們又需要考慮如果這個手機(jī)沒有SD卡,或者SD正好被移除了的情況分飞,因此比較優(yōu)秀的程序都會專門寫一個方法來獲取緩存地址(后面說明)
- 創(chuàng)建DiskLruCache對象:通過靜態(tài)方法open(File directory, int appVersion, int valueCount, long maxSize) 來創(chuàng)建對象悴务,因?yàn)閯?chuàng)建對象時還要對journal文件進(jìn)行操作,因此不能直接通過構(gòu)造方法來創(chuàng)建譬猫。其中第一個參數(shù)指定的是數(shù)據(jù)的緩存地址讯檐,第二個參數(shù)指定當(dāng)前應(yīng)用程序的版本號,第三個參數(shù)指定同一個key可以對應(yīng)多少個緩存文件染服,基本都是傳1别洪,這里的1導(dǎo)致在Editor獲取OutputStream和Snapshot獲取InputStream時需要傳入的參數(shù)為0,表示數(shù)組的第一個柳刮,第四個參數(shù)指定最多可以緩存多少字節(jié)的數(shù)據(jù)挖垛。
- 數(shù)據(jù)操作:通過Editor進(jìn)行數(shù)據(jù)的添加,通過Snapshot進(jìn)行數(shù)據(jù)的讀取秉颗,通過remove進(jìn)行數(shù)據(jù)的刪除痢毒,注意在添加數(shù)據(jù)的時候最后要調(diào)用editor.commit()保證被其它的Reader看到,并且commit方法可以保證緩存大小不超過閾值
- flush():最后記得調(diào)用flush將數(shù)據(jù)寫入磁盤
注:由于磁盤緩存和內(nèi)存緩存一般是同時工作的蚕甥,因此這里在使用DiskLruCache的時候會順便使用LruCache以更好的觀察它們之間如何相互作用哪替。主要的一個關(guān)系就是,在從磁盤讀出數(shù)據(jù)的同時梢灭,通常會將該數(shù)據(jù)加載到內(nèi)存當(dāng)中
DiskLruCache的初始化
以下是DiskLruCache的初始化夷家,包括了分配參數(shù),設(shè)置目錄和對象的創(chuàng)建
//初始化DiskLruCache敏释,包括一些參數(shù)的設(shè)置
public void initDiskLruCache
{
//配置固定參數(shù)
// 緩存空間大小
private static final long DISK_CACHE_SIZE = 1024 * 1024 * 50;
//下載圖片時的緩存大小
private static final long IO_BUFFER_SIZE = 1024 * 8;
// 緩存空間索引库快,用于Editor和Snapshot,設(shè)置成0表示Entry下面的第一個文件
private static final int DISK_CACHE_INDEX = 0;
//設(shè)置緩存目錄
File diskLruCache = getDiskCacheDir(mContext, "bitmap");
if(!diskLruCache.exists())
diskLruCache.mkdirs();
//創(chuàng)建DiskLruCache對象钥顽,當(dāng)然是在空間足夠的情況下
if(getUsableSpace(diskLruCache) > DISK_CACHE_SIZE)
{
try
{
mDiskLruCache = DiskLruCache.open(diskLruCache,
getAppVersion(mContext), 1, DISK_CACHE_SIZE);
mIsDiskLruCache = true;
}catch(IOException e)
{
e.printStackTrace();
}
}
}
//上面的初始化過程總共用了3個方法
//設(shè)置緩存目錄
public File getDiskCacheDir(Context context, String uniqueName) {
String cachePath;
if (Environment.MEDIA_MOUNTED.equals(Environment
.getExternalStorageState())
|| !Environment.isExternalStorageRemovable()) {
cachePath = context.getExternalCacheDir().getPath();
} else {
cachePath = context.getCacheDir().getPath();
}
return new File(cachePath + File.separator + uniqueName);
}
// 獲取可用的存儲大小
@TargetApi(VERSION_CODES.GINGERBREAD)
private long getUsableSpace(File path) {
if (Build.VERSION.SDK_INT >= VERSION_CODES.GINGERBREAD)
return path.getUsableSpace();
final StatFs stats = new StatFs(path.getPath());
return (long) stats.getBlockSize() * (long) stats.getAvailableBlocks();
}
//獲取應(yīng)用版本號义屏,注意不同的版本號會清空緩存
public int getAppVersion(Context context) {
try {
PackageInfo info = context.getPackageManager().getPackageInfo(
context.getPackageName(), 0);
return info.versionCode;
} catch (NameNotFoundException e) {
e.printStackTrace();
}
return 1;
}
DiskLruCache寫入數(shù)據(jù)
下面是DiskLruCache的寫入數(shù)據(jù)的方法,即從網(wǎng)上拉取圖片到磁盤中蜂大,考慮到實(shí)用性闽铐,這里直接給出從網(wǎng)絡(luò)獲取圖片的方法,里面其實(shí)包含了幾個過程:從網(wǎng)上獲取圖片的輸入流InputStream奶浦,通過InputStream和OutputStream將該資源寫入到磁盤緩存中兄墅,最后從磁盤中獲取所需大小的圖片。
其中第二個過程正是DiskLruCache的寫入方法澳叉,配合其他過程能夠更好的理解DiskLruCache的工作
/**
* 從網(wǎng)絡(luò)中下載圖片到磁盤中并獲取到按需壓縮后的圖片
* 1.由于涉及到網(wǎng)絡(luò)通信隙咸,因此該方法應(yīng)該運(yùn)行在子線程當(dāng)中
* 2.圖片是完整下載下來的沐悦,reqWidth和reqHeight只是在從磁盤讀取的時候進(jìn)行
* 壓縮用的
* 3.在存到磁盤的時候url是轉(zhuǎn)換過的編碼的
* 4.要通過editor的commit保證被其它的Reader看到,而且在commit中會保證緩存大小不超過閾值
* 5.最后記得調(diào)用flush()將數(shù)據(jù)確實(shí)寫入文件系統(tǒng)
*
* @param url 圖片網(wǎng)址
* @param reqWidth 所需寬度
* @param reqHeight 所需高度
* @return 按需壓縮后的圖片
* @throws IOException
*/
private Bitmap loadBitmapFromHttp(String url, int reqWidth, int reqHeight) throws IOException
{
if(Looper.myLooper() == Looper.getMainLooper())
throw new RuntimeException("can not visit network from UI Thread.");
if(mDiskLruCache == null)
return null;
String key = hashKeyForDisk(url);
DiskLruCache.Editor editor = mDiskLruCache.edit(key);
if(editor != null)
{
OutputStream outputStream = editor.newOutputStream(DISK_CACHE_INDEX);
//寫入完成后絕不能忘了commit和flush
if(downloadUrlToStream(url, outputStream))
{
editor.commit();
}else
{
editor.abort();
}
//flush確保數(shù)據(jù)寫入文件系統(tǒng)
mDiskLruCache.flush();
}
return loadBitmapFromDiskCache(url, reqWidth, reqHeight);
}
上面是這個流程五督,其中使用了3個方法
- hashKeyForDisk是將url編碼成key方便存儲和查找
- downloadUrlToStream是將資源寫到outputStream中也就是寫到文件系統(tǒng)中
- loadBitmapFromDiskCache(url, reqWidth, reqHeight)是從磁盤中讀取剛剛寫入的資源
注意這里傳入的參數(shù)是url藏否,不傳key是因?yàn)樵谧x取這個方法中還會將url過同樣的處理變成key.
至于為什么不拿key做參數(shù),那是因?yàn)樵谄渌胤綇拇疟P中獲取資源的時候只需要傳入url就行了充包,不用自行轉(zhuǎn)化成key
下面是其中兩個方法副签,最后一個讀取磁盤另外講
/**
* 考慮到直接使用URL作為DiskLruCache中LinkedHashMap的Key不太適合,
* 因?yàn)閳D片URL中可能包含一些特殊字符基矮,這些字符有可能在命名文件時是不合法的淆储。
* 其實(shí)最簡單的做法就是將圖片的URL進(jìn)行MD5編碼,編碼后的字符串肯定是唯一的家浇,
* 并且只會包含0-F這樣的字符遏考,完全符合文件的命名規(guī)則。
*
* 該方法在寫入磁盤和從磁盤讀取時都需要用到蓝谨,因?yàn)樗怯?jì)算出索引的方法
*
* @param key 圖片的url
* @return MD5編碼之后的key
*/
public String hashKeyForDisk(String key) {
String cacheKey;
try {
final MessageDigest mDigest = MessageDigest.getInstance("MD5");
mDigest.update(key.getBytes());
cacheKey = bytesToHexString(mDigest.digest());
} catch (NoSuchAlgorithmException e) {
cacheKey = String.valueOf(key.hashCode());
}
return cacheKey;
}
private String bytesToHexString(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < bytes.length; i**) {
String hex = Integer.toHexString(0xFF & bytes[i]);
if (hex.length() == 1) {
sb.append('0');
}
sb.append(hex);
}
return sb.toString();
}
/**
* 將圖片資源寫到文件系統(tǒng)上
* @param urlString 資源
* @param outputStream Editor的對應(yīng)Entry下的文件的OutputStream
* @return
*/
private boolean downloadUrlToStream(String urlString, OutputStream outputStream) {
HttpURLConnection urlConnection = null;
BufferedOutputStream out = null;
BufferedInputStream in = null;
try {
final URL url = new URL(urlString);
urlConnection = (HttpURLConnection) url.openConnection();
in = new BufferedInputStream(urlConnection.getInputStream(),
IO_BUFFER_SIZE);
out = new BufferedOutputStream(outputStream, IO_BUFFER_SIZE);
int b;
while ((b = in.read()) != -1) {
out.write(b);
}
return true;
} catch (final IOException e) {
e.printStackTrace();
} finally {
if (urlConnection != null) {
urlConnection.disconnect();
}
try {
if (out != null) {
out.close();
}
if (in != null) {
in.close();
}
} catch (final IOException e) {
e.printStackTrace();
}
}
return false;
}
以上就是DiskLruCache的寫入數(shù)據(jù)的過程,其實(shí)核心只是3步青团,即
1.獲取mDiskLruCache中對應(yīng)key的Editor
2.獲取Editor的OutputStream
3.通過OutputStream寫入數(shù)據(jù)
而上面寫那么多是為了能夠更好地理解DiskLruCache的工作流程和如何使用才是最好的譬巫,比如講url轉(zhuǎn)換成key這個方法可以我們意識到這樣才能使DiskLruCache工作得更好
Editor editor = mDiskLruCache.edit(key);
OutputStream outputStream = editor.newOutputStream(DISK_CACHE_INDEX);//DISK_CACHE_INDEX == 0
outputStream.wirte(data);
DiskLruCache讀取數(shù)據(jù)
下面介紹的是根據(jù)需要的寬高加載磁盤緩存中的圖片,先是計(jì)算url對應(yīng)的key督笆,然后從mDiskLruCache中獲取到相應(yīng)的數(shù)據(jù)芦昔,最后在返回之前加載到LruCache當(dāng)中
/**
* 磁盤緩存的讀取
* @param url
* @param reqWidth
* @param reqHeight
* @return
*/
private Bitmap loadBitmapFromDiskCache(String url, int reqWidth, int reqHeight) throws IOException
{
if(Looper.myLooper() == Looper.getMainLooper())
Log.w(TAG, "it's not recommented load bitmap from UI Thread");
if(mDiskLruCache == null)
return null;
Bitmap bitmap = null;
String key = hashKeyForDisk(url);
Snapshot snapshot = mDiskLruCache.get(key);
if(snapshot != null)
{
FileInputStream fileInputStream = (FileInputStream) snapshot.getInputStream(DISK_CACHE_INDEX);
FileDescriptor fd = fileInputStream.getFD();
bitmap = mImageResizer.decodeSampleBitmapFromFileDescriptor(fd, reqWidth, reqHeight);
if(bitmap != null)
addBitmapToMemoryCache(key, bitmap);
}
return bitmap;
}
跟Editor寫入數(shù)據(jù)只需要3步一樣,讀取數(shù)據(jù)的核心也只需要3步
1.根據(jù)key獲取mDiskLruCache中對應(yīng)的Snapshot
2.通過Snapshot獲取到文件數(shù)據(jù)的InputStream(可以向下轉(zhuǎn)型為FileInputStream)
3.通過InputStream讀取數(shù)據(jù)
Snapshot snapshot = mDiskLruCache.get(key);
InputStream inputStream = snapshot.getInputStream(DISK_CACHE_INDEX);//DISK_CACHE_INDEX == 0
int data = inputStream.read();
12.2.3 ListView的列表錯位問題
1.問題
在Adapter中娃肿,通常會復(fù)用View來優(yōu)化ListView或者GridView的加載咕缎,而復(fù)用View帶來的問題就是,圖片在ListView列表中顯示的位置錯亂料扰,即本應(yīng)該顯示B圖片的位置顯示了A圖片
2.原因
假設(shè)在ListView中凭豪,每個位置對應(yīng)著一個ImageView控件用于顯示一張圖片,而圖片是從網(wǎng)絡(luò)中拉壬硅尽(沒有緩存的情況下)嫂伞,對應(yīng)如下表所示。
列表位置 | 對應(yīng)控件 | 對應(yīng)圖片 |
---|---|---|
itemA | ImageView | 圖片A |
itemX | ImageView | 圖片X |
itemY | ImageView | 圖片Y |
itemZ | ImageView | 圖片Z |
itemB | ImageView | 圖片B |
問題產(chǎn)生的原因在于:
- ListView在Adapter中復(fù)用每個item的布局包括里面的控件
即在屏幕中顯示的每個ListView的item對應(yīng)的布局只有在第一次的時候被加載拯钻,然后緩存在convertView里面帖努,之后滑動改變ListView時調(diào)用的getView就會復(fù)用緩存在converView中的布局和控件,所以可以使得ListView變得流暢(因?yàn)椴挥弥貜?fù)加載布局) - 異步加載圖片需要時間
假設(shè)每個ListView中的item都有一個ImageView顯示圖片粪般,比如itemA需要ImageView顯示圖片A拼余,itemB需要顯示圖片B。而圖片都是需要異步從網(wǎng)絡(luò)加載亩歹,所以需要時間 - 加載過程中ListView發(fā)生滑動
itemA在使用ImageView加載圖片A時匙监,ListView發(fā)生了滑動凡橱,導(dǎo)致itemB滑動到了itemA的位置 - itemB復(fù)用itemA的布局和控件
由于Adapter復(fù)用convertView的原因,itemB的布局直接復(fù)用原來該位置的ImageView舅柜,并同時利用該ImageView加載圖片B - itemA異步加載圖片A的方法還持有被復(fù)用的ImgaView
由于是異步加載圖片梭纹,所以itemA在加載圖片的過程中會持有剛才的ImageView,導(dǎo)致圖片A在下載完成之后會被加載到該ImageView控件當(dāng)中致份,因此造成了itemB對應(yīng)顯示的是圖片A变抽,這就是列表錯亂問題
3.解決方法
從上面可以看出,問題的根源在于圖片A在被加載ImageView之前氮块,ListView發(fā)生滑動導(dǎo)致ImageView被itemB復(fù)用绍载,此時該ImageView就不能顯示圖片A了。
那么就從根源入手滔蝉,在圖片A被加載到ImageView之前做一個判斷击儡,判斷該ImageView是否還是對應(yīng)的是itemA,如果是則將圖片加載到ImageView當(dāng)中蝠引,如果不是則放棄加載(因?yàn)閕temB已經(jīng)啟動了圖片B的加載阳谍,所以不用擔(dān)心控件出現(xiàn)空白的情況)
所以問題就變成了,如何判斷ImageView對應(yīng)的item已經(jīng)改變了
方法:
- 在每次getView的復(fù)用布局控件時螃概,對會被復(fù)用的控件設(shè)置一個標(biāo)簽(在這里就是對ImageView設(shè)置標(biāo)簽)矫夯,這里使用圖片的url作為標(biāo)簽內(nèi)容,然后再異步加載圖片
- 在圖片下載完成后要加載到ImageView之前做判斷吊洼,判斷該ImageView的標(biāo)簽內(nèi)容是否和圖片的url一樣
如果一樣說明ImageView沒有被復(fù)用训貌,可以將圖片加載到ImageView當(dāng)中;
如果不一樣冒窍,說明ListView發(fā)生了滑動递沪,導(dǎo)致其他item調(diào)用了getView從而將該ImageView的標(biāo)簽改變,此時放棄圖片的加載(盡管圖片已經(jīng)被下載成功了)
注:如果是直接從內(nèi)存里讀取的數(shù)據(jù)综液,則不需要對比tag款慨,因?yàn)檫@幾乎不需要時間
4.具體實(shí)現(xiàn)
1)在getView中給ImageView設(shè)置標(biāo)簽內(nèi)容
imageView.setTag(uri);//對應(yīng)imageView.getTag()
//or
imageView.setTag(TAG_KEY_URI, uri);//對應(yīng)imageView.getTag(TAG_KEY_URI) TAG_KEY_URL是常量
2)在給ImageView設(shè)置圖片前判斷圖片的uri是否和ImageView的標(biāo)簽內(nèi)容
if(uri.equals(imageView.getTag(TAG_KEY_URI)))
{
//如果相等才設(shè)置圖片
imageView.setImageBitmap(bitmap);
}else
{
Log.w(TAG,"set image bitmap, but url has changed, ignored!");
}
12.2.4 ImageLoader的實(shí)現(xiàn)
ImageLoader封裝了Bitmap的高效加載、LruCache和DiskLruCache谬莹,學(xué)習(xí)ImageLoader的實(shí)現(xiàn)有利于對Android的Bitmap的加載和緩存機(jī)制有更深刻的理解
ImageLoader應(yīng)該具備以下功能
- 圖片的同步加載
- 圖片的異步加載
- 圖片壓縮
- 內(nèi)存緩存
- 磁盤緩存
- 網(wǎng)絡(luò)拉取
圖片的同步加載
同步:先復(fù)習(xí)一下同步的意思樱调,就是整個流程會按順序執(zhí)行,如果有阻塞那就一直等待阻塞返回届良。
ImageLoader采取的圖片的同步加載的方法是loadBitmap笆凌,其中的流程是
1.loadBitmapFromMemoryCache:從內(nèi)存緩存提取圖片
2.loadBitmapFromDisk:從磁盤緩存提取圖片
3.loadBitmapFromHttp:從網(wǎng)絡(luò)拉取圖片(要經(jīng)過磁盤的存儲)
*4.downloadBitmapFromUrl:直接從網(wǎng)絡(luò)提取圖片(不經(jīng)過磁盤存儲)
注:
* 只有前一步提取不成功時才會執(zhí)行下一步;
* 第4步是為了防止因?yàn)榇疟P不夠而第3步執(zhí)行不成功的情況士葫,本質(zhì)都是通過網(wǎng)絡(luò)獲取圖片
從ImageLoader這個名稱上來看就知道圖片加載時ImageLoader的最主要的工作乞而,因此要深刻理解ImageLoader中圖片加載的步驟。ImageLoader封裝了整個加載流程慢显,只向外提供了統(tǒng)一的接口loadBitmap爪模,這方便了調(diào)用者不用再去關(guān)心加載的流程欠啤,以下是loadBitmap的程序:
/**
* load bitmap from memory cache or disk or network
* NOTE that should run in a new Thread
* @param url http url
* @param reqWidth the width that imageView desire
* @param reqHeight the height that imageView desire
* @return bitmap maybe null
*/
public Bitmap loadBitmap(String url, int reqWidth, int reqHeight)
{
Bitmap bitmap = loadBitmapFromMemoryCache(url);
if(bitmap != null)
{
Log.d(TAG, "loadBitmapFromMemoryCache, url:"+url);
return bitmap;
}
try
{
bitmap = loadBitmapFromDiskCache(url, reqWidth, reqHeight);
if(bitmap != null)
{
Log.d(TAG, "loadBitmapFromDiskCache, url:"+url);
return bitmap;
}
bitmap = loadBitmapFromHttp(url, reqWidth, reqHeight);
Log.d(TAG, "loadBitmapFromHttp, url:"+url);
}catch(Exception e)
{
e.printStackTrace();
}
if(bitmap == null && !mIsDiskLruCacheCreated)
{
Log.w(TAG, "encounter error, DiskLruCache is not created.");
bitmap = downloadBitmapFromUrl(url);
}
return bitmap;
}
- loadBitmapFromMemoryCache:從內(nèi)存緩存提取圖片
private Bitmap loadBitmapFromMemoryCache(String url)
{
final String key = hashKeyForDisk(url);
return getBitmapFromMemoryCache(key);
}
private Bitmap getBitmapFromMemoryCache(String key)
{
return mLruCache.get(key);
}
private void addBitmapToMemoryCache(String key, Bitmap bitmap)
{
if(getBitmapFromMemoryCache(key) == null)
{
mLruCache.put(key, bitmap);
}
}
2.loadBitmapFromDisk:從磁盤緩存提取圖片
private Bitmap loadBitmapFromDiskCache(String url, int reqWidth, int reqHeight)
{
if(Looper.myLooper() == Looper.getMainLooper())
{
Log.w(TAG, "load bitmap from UI Thread is not recomment.");
}
if(mDiskLruCache == null)
{
return null;
}
Bitmap bitmap = null;
final String key = hashKeyForDisk(url);
Snapshot snapshot = mDiskLruCache.get(key);
if(snapshot == null)
{
return null;
}
FileInputStream fileInputStream = (FileInputStream) snapshot.getInputStream(DISK_CACHE_INDEX);
FileDescriptor fd = fileInputStream.getFD();
bitmap = mImageResizer.decodeSampleBitmapFromFileDescriptor(fd, reqWidth, reqHeight);
if(bitmap != null)
{
addBitmapToMemoryCache(key, bitmap);
}
return bitmap;
}
3.loadBitmapFromHttp:從網(wǎng)絡(luò)拉取圖片(要經(jīng)過磁盤的存儲) ,其實(shí)是先存到磁盤到從磁盤中讀取
private Bitmap loadBitmapFromHttp (String url, int reqWidth, int reqHeight)
throws IOException
{
if(Looper.myLooper() == Looper.getMainLooper())
{
throw new RuntimeException("can not visit network in from UI Thread");
}
if(mDiskLruCache == null)
{
return null;
}
final String key = hashKeyForDisk(url);
Editor editor = mDiskLruCache.edit(key);
if(editor != null)
{
OutputStream outputStream = editor.newOutputStream(DISK_CACHE_INDEX);
if(downloadUrlToStream(url, outputStream))
{
editor.commit();
}else
{
editor.abort();
}
}
mDiskLruCache.flush();
return loadBitmapFromDiskCache(url, reqWidth, reqHeight);
}
/**
* 將圖片資源寫到文件系統(tǒng)上
* @param urlString 資源
* @param outputStream Editor的對應(yīng)Entry下的文件的OutputStream
* @return
*/
private boolean downloadUrlToStream(String urlString, OutputStream outputStream) {
HttpURLConnection urlConnection = null;
BufferedOutputStream out = null;
BufferedInputStream in = null;
try {
final URL url = new URL(urlString);
urlConnection = (HttpURLConnection) url.openConnection();
in = new BufferedInputStream(urlConnection.getInputStream(),
IO_BUFFER_SIZE);
out = new BufferedOutputStream(outputStream, IO_BUFFER_SIZE);
int b;
while ((b = in.read()) != -1) {
out.write(b);
}
return true;
} catch (final IOException e) {
e.printStackTrace();
} finally {
if (urlConnection != null) {
urlConnection.disconnect();
}
try {
if (out != null) {
out.close();
}
if (in != null) {
in.close();
}
} catch (final IOException e) {
e.printStackTrace();
}
}
return false;
}
*4.downloadBitmapFromUrl:直接從網(wǎng)絡(luò)提取圖片(不經(jīng)過磁盤存儲)
private Bitmap downloadBitmapFromUrl(String urlString)
{
Bitmap bitmap = null;
HttpURLConnection connection = null;
BufferedInputStream bis = null;
try
{
URL url = new URL(urlString);
connection = (HttpURLConnection) url.openConnection();
bis = new BufferedInputStream(connection.getInputStream(),
IO_BUFFER_SIZE);
bitmap = BitmapFactory.decodeStream(bis);
}catch(IOException e)
{
Log.e(TAG,"Error in downloadBitmap: " + e);
}finally
{
if(connection != null)
{
connection.disconnect();
}
if(bis != null)
{
try {
bis.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
return bitmap;
}
圖片的異步加載
異步:指在執(zhí)行整個流程的時候屋灌,如果發(fā)生阻塞洁段,那么此時會去做其他跟阻塞返回?zé)o關(guān)事情,如果阻塞返回了再去執(zhí)行跟阻塞相關(guān)的事情共郭。
簡單來說異步就是在主線程里開另一個線程里做阻塞的事情祠丝,而主線程完成其他的工作,然后在子線程阻塞返回后通過回調(diào)(在Android中一般是通過Handler)將結(jié)果返回給主線程除嘹,此時主線程會按照預(yù)定好的規(guī)則(handleMessage)處理相關(guān)的結(jié)果
在ImageLoader當(dāng)中需要圖片的異步加載的原因:因?yàn)閳D片的下載是需要時間的写半,也就是需要在子線程中下載,而圖片下載完成之后需要加載到外界向ImageLoader提供的ImageView當(dāng)中尉咕,也就是需要在主線程中加載圖片叠蝇,還需要線程間通信。
所以ImageLoader不僅要同步加載圖片年缎,還應(yīng)該向外提供異步加載的接口悔捶,使得外界提供url和ImageView之后就不用再管圖片在子線程中的下載和在主線程中的加載了
實(shí)現(xiàn)異步加載的條件
1.Excutor:線程池,用于開啟新線程下載圖片
2.Handler:主線程的Handler单芜,用于將下載完成后的圖片在主線程中加載到ImageView當(dāng)中
3.bindBitmap:異步加載圖片炎功,包括了在子線程中同步加載圖片和在主線程中設(shè)置圖片
*4.列表錯亂預(yù)防:利用ImageView的標(biāo)簽防止異步加載圖片時發(fā)生列表錯亂
注:第4步是跟ListView或者GridView列表錯亂有關(guān),實(shí)際意義上的實(shí)現(xiàn)異步只需要前面三個條件
1.線程池的創(chuàng)建
這里使用線程池而不使用AsyncTask的原因缓溅,AsyncTask在3.0之后無法實(shí)現(xiàn)并發(fā)的效果(雖然AsyncTask里也使用了線程池,但是自帶的SerialExecutor導(dǎo)致任務(wù)只能一個個被執(zhí)行)赁温。而ImageLoader需要并發(fā)的效果坛怪,所以使用了線程池
//線程池的配置
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
private static final long KEEP_ALIVE = 10L;
private static final ThreadFactory sThreadFactory = new ThreadFactory() {
private final AtomicInteger mCount = new AtomicInteger(1);
@Override
public Thread newThread(Runnable r) {
// TODO Auto-generated method stub
return new Thread(r,"ImageLoader#" + mCount.getAndIncrement());
}
};
//作為靜態(tài)成員的線程池
//線程池的創(chuàng)建
public static final Executor THREAD_POOL_EXEUTOR =
new ThreadPoolExecutor(
CORE_POOL_SIZE, MAXIMUM_POOL_SIZE,
KEEP_ALIVE, TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(),
sThreadFactory);
2.Handler的創(chuàng)建
由于該Handler是向主線程發(fā)送消息的,因此Handler需要在主線程創(chuàng)建股囊。由于ImageLoader很可能在子線程中創(chuàng)建而導(dǎo)致成員都在子線程中被創(chuàng)建袜匿,因此該Handler的創(chuàng)建需要借助主線程的Looper對象,即Looper.getMainLooper()
其中在給ImageView設(shè)置圖片前先判斷列表項(xiàng)是否已經(jīng)改變
//主線程Handler創(chuàng)建的方法
private Handler mMainHandler = new Handler(Looper.getMainLooper())
{
@Override
public void handleMessage(android.os.Message msg) {
LoaderResult result = (LoaderResult) msg.obj;
ImageView imageView = result.imageView;
String uri = result.uri;
if(uri.equals(imageView.getTag(TAG_KEY_URI)))
{
imageView.setImageBitmap(result.bitmap);
}else
{
Log.w(TAG, "set image bitmap, but url has changed, ignored!");
}
};
};
/**
* 用于線程間通信的實(shí)體類稚疹,防止列表錯亂的關(guān)鍵
* 因?yàn)樗鼣y有原圖的url和要被加載的ImageView
*/
private static class LoaderResult
{
public String uri;
public ImageView imageView;
public Bitmap bitmap;
public LoaderResult(String uri, ImageView imageView, Bitmap bitmap)
{
this.uri = uri;
this.imageView = imageView;
this.bitmap = bitmap;
}
}
3.異步加載圖片bindBitmap
包括了在子線程中同步加載圖片(從內(nèi)存居灯、磁盤和網(wǎng)絡(luò)加載)以及通過Handler將結(jié)果送給主線程
它是一個向外提供的接口,因此外界可以直接使用它
/**
* load bitmap from memory cache or disk or http,
* then bind bitmap and ImageView
* @param uri
* @param imageView
*/
public void bindBitmap(final String uri, final ImageView imageView)
{
bindBitmap(uri, imageView, 0, 0);
}
/**
* 封裝了從url獲取圖片到加載到特定的ImageView當(dāng)中的方法
* 其中先是從內(nèi)存中獲取圖片内狗,如果獲取不到在調(diào)用loadBitmap獲取圖片
*
* 如果是從網(wǎng)絡(luò)中獲取圖片怪嫌,則
* 在將圖片加載到ImageView前,會將圖片的url和ImageView的tag進(jìn)行對比
* 如果相同則說明ImageView沒有被復(fù)用柳沙,因此可以加載圖片
* 如果不同則說名ImageView被復(fù)用隔了岩灭,放棄加載下載完成的圖片
*
* @param url
* @param imageView
* @param reqWidth
* @param reqHeight
*/
public void bindBitmap(final String url, final ImageView imageView,
final int reqWidth, final int reqHeight)
{
imageView.setTag(TAG_KEY_URI);
Bitmap bitmap = loadBitmapFromMemoryCache(url);
if(bitmap != null)
{
imageView.setImageBitmap(bitmap);
return;
}
Runnable loadBitmapTask = new Runnable()
{
@Override
public void run() {
Bitmap bitmap = loadBitmap(url, reqWidth, reqHeight);
if(bitmap != null)
{
LoaderResult result = new LoaderResult(url, imageView, bitmap);
mMainHandler.obtainMessage(MESSAGE_POST_RESULT, result)
.sendToTarget();
}
};
};
THREAD_POOL_EXEUTOR.execute(loadBitmapTask);
}
*4.防止列表錯亂
前面一節(jié)已經(jīng)提到了如何防止列表錯亂的方法,這里強(qiáng)調(diào)一下ImageLoader中設(shè)置標(biāo)簽和對比標(biāo)簽的位置
1.給ImageView設(shè)置標(biāo)簽
在bindBitmap中赂鲤,在利用子線程同步加載圖片前將ImageView設(shè)置標(biāo)簽噪径,然后該imageView會在圖片下載完成后和圖片一起通過Handler傳送到主線程
imageView.setTag(TAG_KEY_URI);
2.取出ImageView的標(biāo)簽與圖片url對比
在Handler的handleMessage中柱恤,在用imageView設(shè)置圖片之前進(jìn)行對比
LoaderResult result = (LoaderResult) msg.obj;
ImageView imageView = result.imageView;
String uri = result.uri;
if(uri.equals(imageView.getTag(TAG_KEY_URI)))
{
imageView.setImageBitmap(result.bitmap);
}else
{
Log.w(TAG, "set image bitmap, but url has changed, ignored!");
}
圖片壓縮
參照12.1節(jié)的ImageResizer.java
內(nèi)存緩存
參照12.2.1節(jié)的LruCache
loadBitmapFromMemoryCache(String url)
磁盤緩存
參照12.2.2節(jié)的DiskLruCache
loadBitmapFromDiskCache(String url, int reqWidth, int reqHeight)
網(wǎng)絡(luò)拉取
參照12.2.2節(jié)的DiskLruCache
loadBitmapFromHttp(String url, int reqWidth, int reqHeight)
12.3 ListView或GridView的優(yōu)化
ListView是開發(fā)時經(jīng)常用到的控件,如果使用的不夠好找爱,會造成卡頓等體驗(yàn)不好的現(xiàn)象梗顺,下面就介紹可用且好用的優(yōu)化方法
核心思想
不要在主線程中做太耗時的操作
方法
1.不要再Adapter的getView中執(zhí)行耗時操作
列表的滑動觸發(fā)最多的方法就是Adapter的getView方法,如果getView方法里面執(zhí)行的操作太耗時车摄,就會造成卡頓現(xiàn)象寺谤。如果有耗時的操作比如像上面的從網(wǎng)絡(luò)加載圖片,那么就應(yīng)該開啟新線程異步加載圖片
2.控制異步任務(wù)的執(zhí)行頻率
既然上面使用了異步任務(wù)练般,那么要考慮到用戶頻繁滑動的情景矗漾,這個是百分百發(fā)生的場景,所以一定要注意
方法是薄料,ListView或GridView設(shè)置OnScollListener敞贡,在Adapter中設(shè)置一個布爾成員mIsListViewIdle用于記錄當(dāng)前列表是否在滑動從而在getView里面限制列表的加載,程序如下
/*
為列表控件設(shè)置監(jiān)聽器摄职,并在滑動的時候設(shè)置Adapter的mIsListViewIdle成員
*/
mImageGridView.setOnScrollListener(new OnScollListener()
{
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
// TODO Auto-generated method stub
if(scrollState == OnScrollListener.SCROLL_STATE_IDLE)
{
mImageAdapter.setIsGridViewIdle(true);
mImageAdapter.notifyDataSetChanged();
}else
{
mImageAdapter.setIsGridViewIdle(false);
}
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
// TODO Auto-generated method stub
}
});
下面是ImageAdapter.java中的部分代碼
//ImageAdapter.java中的getView誊役,只有在mIsGridViewIdle為true即列表停止滑動的時候才加載數(shù)據(jù)
private boolean mIsGridViewIdle = true;
public void setIsGridViewIdle(boolean mIsGridViewIdle) {
this.mIsGridViewIdle = mIsGridViewIdle;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
ViewHolder viewHolder = null;
if(convertView == null)
{
convertView = LayoutInflater.from(mContext).inflate(R.layout.list_item_image, parent, false);
viewHolder = new ViewHolder();
viewHolder.imageView = (ImageView) convertView.findViewById(R.id.iv_list_item);
convertView.setTag(viewHolder);
}else
{
viewHolder = (ViewHolder)convertView.getTag();
}
ImageView imageView = viewHolder.imageView;
String url = getItem(position);
if(!url.equals(imageView.getTag()+""))
{
imageView.setImageDrawable(mDefaultBitmapDrawable);
}
if(mIsGridViewIdle)
{
imageView.setTag(url);
mImageLoader.bindBitmap(url, imageView, mImageWidth,mImageWidth);
}
return convertView;
}
3.為Activity開啟硬件加速
android:hardwareAccelerated="true";