1.Bitmap的高效加載
1.1 通常如何加載Bitmap
Bitmap在Android指的是一張圖艇炎,可以是.png/.jpg等其他格式
BitmapFactory提供四類方法:
decodeFile窘茁、decodeResource尺棋、decodeStream谣殊、decodeByteArray
對應(yīng)從文件系統(tǒng)建椰、資源搪柑、輸入流世分、字節(jié)數(shù)組中加載出一個(gè)Bitmap對象
decodeFile编振、decodeResource又間接調(diào)用了decodeStream方法
1.2 如何高效加載Bitmap
核心思想是采用BitmapFactory.Options來加載所需尺寸的圖片
比如通過ImageView來顯示圖片,通常ImageView沒有圖片原始尺寸這么大,這時(shí)就通過BitmapFactory.Options按一定的采樣率來加載縮小后的圖片,降低內(nèi)存占用
1.3 BitmapFactory.Options
BitmapFactory.Options縮放圖片,主要是用到了 inSampleSize 參數(shù),即采樣率
inSampleSize為1時(shí),采樣為原始大小
inSampleSize 為2時(shí),圖片的寬高為原圖的1/2,像素?cái)?shù)為1/4,占用內(nèi)存為1/4
inSampleSize 為4,縮放比例就是1/16,即2的4次方
inSampleSize應(yīng)該為2的指數(shù)倍,2,4,8,16等
inSampleSize小于1,相當(dāng)于1
inSampleSize不為2 的指數(shù),向下取整為2的指數(shù)
1.4 如何獲取采樣率
●將BitmapFactory.Options的inJustDecodeBounds參數(shù)設(shè)為true并加載圖片
●設(shè)置為true后BitmapFactory只會(huì)解析圖片的原始寬高信息,并不會(huì)加載圖片
●根據(jù)結(jié)果設(shè)置合適的采樣率
● inJustDecodeBounds設(shè)回false然后重新加載圖片
舉例
public Bitmap decodeSampledBitmapFromFileDescriptor(FileDescriptor fd, int reqWidth, int reqHeight) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();//拿到
options.inJustDecodeBounds = true;//設(shè)置
BitmapFactory.decodeFileDescriptor(fd, null, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth,
reqHeight);//計(jì)算
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeFileDescriptor(fd, null, options);
}
public int calculateInSampleSize(BitmapFactory.Options options,
int reqWidth, int reqHeight) {
if (reqWidth == 0 || reqHeight == 0) {
return 1;
}
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
Log.d(TAG, "origin, w= " + width + " h=" + height);
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
// Calculate the largest inSampleSize value that is a power of 2 and
// keeps both
// height and width larger than the requested height and width.
while ((halfHeight / inSampleSize) >= reqHeight
&& (halfWidth / inSampleSize) >= reqWidth) {
inSampleSize *= 2;
}
}
Log.d(TAG, "sampleSize:" + inSampleSize);
return inSampleSize;
}
2 Android中的緩存策略
緩存可以避免過多的消耗流量
當(dāng)用戶第一次從網(wǎng)上加載圖片后,會(huì)把圖片緩存在內(nèi)存中,再緩存到本地儲(chǔ)存設(shè)備.這樣當(dāng)應(yīng)用打算從網(wǎng)絡(luò)請求一張圖片的時(shí)候會(huì)先訪問內(nèi)存,再訪問儲(chǔ)存設(shè)備,都沒有后才會(huì)去下載
目前常用的緩存算法是LRU,近期最少使用算法
2.1 LruCache
最近最少使用緩存,它用強(qiáng)引用保存需要緩存的對象罚攀,內(nèi)部維護(hù)一個(gè)隊(duì)列(實(shí)際是LinkedhashMap內(nèi)部的雙向鏈表党觅,LruCache對其進(jìn)行了封裝雌澄,添加了線程安全操作),當(dāng)其中的一個(gè)值被訪問時(shí)杯瞻,它被放到隊(duì)列的尾部镐牺,當(dāng)緩存滿了,頭部的值會(huì)被丟棄魁莉,之后可以被垃圾回收睬涧。
2.2 LruCache的實(shí)現(xiàn)
需要提供緩存的總?cè)萘看笮〔⒅貙憇izeOf方法(計(jì)算緩存對象的大小)
LruCache還支持刪除操作,通過remove方法即可刪除一個(gè)指定的緩存對象
//獲取最大可用的內(nèi)存空間
int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
int cacheSize = maxMemory / 8;//緩存大小為總?cè)萘康?/8旗唁,單位KB
mLruCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap value) {//sizeOf()用來計(jì)算緩存對象的大小
return value.getRowBytes() * value.getHeight() / 1024;//除1024為了將單位轉(zhuǎn)成KB
}
};
}
2.2.1 從Lrucache獲取一個(gè)緩存對象
mLruCache.get(key)
2.2.2 Lrucache添加一個(gè)緩存對象
mLruCache.put(key,bitmap)
2.2.3 刪除一個(gè)指定的緩存對象
mLruCache.remove(key);
2.3 DiskLruCache
● 用于實(shí)現(xiàn)存儲(chǔ)設(shè)備緩存畦浓,磁盤緩存
● 將緩存對象寫入文件系統(tǒng),從而實(shí)現(xiàn)緩存效果
2.3.1 DiskLruCache的創(chuàng)建
DiskLruCache并不能通過構(gòu)造方法來創(chuàng)建,它提供了open方法用于創(chuàng)建自身
public static DiskLruCache open(File directory,
int appVersion,
int valueCount,
long maxSize)
第一個(gè)參數(shù):表示磁盤緩存在文件系統(tǒng)中的存儲(chǔ)路徑,可以選擇SD卡.(如果希望應(yīng)用卸載后刪除緩存文件,那么就選擇SD卡上的緩存目錄,否則應(yīng)該選擇SD卡上的其他特定目錄)
第二個(gè)參數(shù):表示應(yīng)用的版本號,一般為1,版本號改變時(shí)會(huì)清空之前所有的緩存文件
第三個(gè)參數(shù):表示單個(gè)節(jié)點(diǎn)所對應(yīng)的數(shù)據(jù)個(gè)數(shù),一般為1
第四個(gè)參數(shù):表示緩存的總大小,比如50MB
2.3.2 DiskLruCache的緩存添加
● 此操作是通過Editor來完成的
● 緩存首先要獲取圖片的url所對應(yīng)的key(url的md5值)
● 緩存只允許編輯一個(gè)緩存對象
● 通過Editor對象得到一個(gè)文件輸出流,寫入到文件系統(tǒng)上
● 最后Editor.commit()
○ 第一步
private String hashKeyFormUrl(String url){
String cacheKey;
try {
MessageDigest mDigest = MessageDigest.getInstance("MD5");
mDigest.update(url.getBytes());
cacheKey = bytesToHexString(mDigest.digest());
} catch (NoSuchAlgorithmException e) {
cacheKey = String.valueOf(url.hashCode());
}
return cacheKey;//獲取URL對應(yīng)的key
}
private String bytesToHexString(byte[] digest) {
StringBuffer sb = new StringBuffer();
for (int i = 0; i < digest.length; i++) {
String hex = Integer.toHexString(0xFF&digest[i]);
if(hex.length() == 1){
sb.append('0');
}
sb.append(hex);
}
return sb.toString();
○ 網(wǎng)絡(luò)下載圖片检疫,通過文件輸出流寫入到文件系統(tǒng)上讶请。
private boolean downloadUrlToStream(String urlString,OutputStream outputStream){
int IO_BUFFER_SIZE = 0;
HttpURLConnection urlConnection = null;
BufferedOutputStream out = null;
BufferedInputStream in = null;
try {
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 (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
if(urlConnection != null){
urlConnection.disconnect();
}
}
return false;
}
○ 將圖片的url轉(zhuǎn)為key之后,就可以獲取Editor對象了屎媳,對于這個(gè)key來說夺溢,如果當(dāng)前不存在其他Editor對象,那么edit()就會(huì)返回一個(gè)新的Editor對象烛谊,通過它就可以得到一個(gè)文件輸入流风响,通過Editor的commit完成提交。
int DISK_CACHE_INDEX = 0;//由于前面的open設(shè)置了一個(gè)節(jié)點(diǎn)只能有一個(gè)數(shù)據(jù)丹禀,因此DISK_CACHE_SIZE = 0
String key = hashKeyFormUrl(url);
try {
DiskLruCache.Editor editor = mDiskLruCache.edit(key);
if(editor != null){
OutputStream outputStream = editor.newOutputStream(DISK_CACHE_INDEX);
//執(zhí)行下載
if(downloadUrlToStream(url,outputStream)){
//提交寫入操作
editor.commit();
}else {
//下載異常状勤,執(zhí)行回退操作
editor.abort();
}
//更新操作
mDiskCache.flush();
}
} catch (IOException e) {
e.printStackTrace();
}
2.3.3 DiskLruCache的緩存的查找
● 查找也需要將url轉(zhuǎn)換為key
● 然后通過DiskLruCache的get方法得到一個(gè)Snapshot對象
● 再通過Snapshot對象得到緩存的文件輸入流
● 記得通過BitmapFactory.Options加載一個(gè)縮放后的圖片
Bitmap bitmap = null;
String keys = hashKeyFormUrl(url);
try {
DiskLruCache.Snapshot snapshot = mDiskLruCache.get(keys);
if(snapshot != null){
FileInputStream fileInputStream = (FileInputStream) snapshot.getInputStream(DISK_CACHE_INDEX);
FileDescriptor fileDescriptor = fileInputStream.getFD();
bitmap=mImageResizer.decodeSampledBitmapFromFileDescriptor(fileDescriptor,reqWidth,reqHeight);
if(bitmap != null){
addBitmapToMemoryCache(keys,bitmap);
}
2.4 ImageLoader
2.4.1 ImageLoader的實(shí)現(xiàn)
一個(gè)優(yōu)秀的ImageLoader應(yīng)具備如下功能:
● 圖片的同步加載(從內(nèi)存緩存、磁盤緩存双泪、網(wǎng)絡(luò)中獲取的)
● 圖片的異步加載(ImageLoader內(nèi)部需要自己在線程中加載圖片并將圖片設(shè)置給所需的ImageView)
● 圖片壓縮
● 內(nèi)存緩存
● 磁盤緩存
● 網(wǎng)絡(luò)拉取
ImageLoader還需要處理在ListView或者GridView中,快速下拉時(shí)圖片在item錯(cuò)位的情況
內(nèi)存緩存和磁盤緩存是ImageLoader的核心持搜,通過這兩級緩存極大的提高了程序的效率并降低了流量消耗,只有這兩級緩存都不可用時(shí)才需要從網(wǎng)絡(luò)中拉去圖片攒读。
2.4.2 優(yōu)化卡頓現(xiàn)象
● 關(guān)鍵是不要在主線程中做太多耗時(shí)的操作
● 不要在getView中執(zhí)行耗時(shí)操作(如加載圖片)
● 控制異步任務(wù)的執(zhí)行頻率,比如頻繁的上下滑動(dòng)會(huì)產(chǎn)生N個(gè)異步任務(wù).這時(shí)可以·考慮在列表滑動(dòng)的時(shí)候停止加載圖片,停下來再加載
● 開啟硬件加速:android:hardwareAccelerated=“true”