編輯:關於Android編程
如何有效的加載一個bitmap,由於Bitmap的特殊性以及Android對單個應用所施加的內存限制,比如16MB,這就導致加載Bitmap的時候很容易出現內存溢出。
因此,如何高效的加載bitmap是一個很重要也很容易被開發者忽略的問題。
Bitmap的高效加載:
如何加載一張圖片呢?BitmapFactory類提供了四類方法:decodedFile,decodedResource,decodedStream和decodedByteArray,分別用於支持文件系統,資源,輸入流以及字節數組中加載出一個Bitmap對象,其中decodedFile和decodedResource又間接調用了decodedStream方法,這四類方法最終是在Android的底層實現的,對應著BitmapFactory類的幾個方法。
高效加載Bitmap的核心就是采用BitmapFactory.Options來加載所需尺寸的圖片。這裡加色通過ImageView來顯示圖片,很多時候ImageView並沒有圖片的原始尺寸那麼大,這個時候把整個圖片加載進來後再射給ImageView,這顯然是沒有必要的,因為ImageView並沒有辦法顯示原始的圖片。通過Options參數就可以按一定的采樣率來加載縮小後的圖片,將縮小後的圖片在ImageView中顯示,這樣就會降低內存占用從而在一定程度上避免OOM,提高加載時的性能。其縮放圖片主要是用到了Options的inSampleSize參數,即采樣率,當inSampleSize為1時,采樣後的圖片大小為圖片的原始大小,當inSampleSize大於1時,比如2,那麼采樣後的圖片其寬/高均為原圖大小的1/2,而像素數為原圖的1/4,其占有的內存大小也為原圖的1/4
拿一張1024*1024像素的圖片來說,嘉定采用ARGB8888格式存儲,那麼他占有的內存為1024*1024*4,即4MB,如果inSampleSize為2,那麼采樣後的圖片其內存占用只有512*512*4即1MB。可以發現采樣率inSampleSize必須是大於1的整數,圖片才會有縮小的效果,並且采樣率同時作用於寬/高,這將導致縮放後的圖片大小以采樣率的2次方形式遞減。即inSampleSize=4是,那麼縮放比例就是1/16。有一種 特殊情況,就是當inSampleSize小雨1時,其作用相當於1,即無縮放效果。
通過采樣率即可有效的加載圖片,那麼到底如何獲取采樣率呢?
(1)將BitmapFactory.Options的inJustDecodeBounds參數設為true並加載圖片(只會解析 圖片的原始寬/高信息,並不會去真正加載圖片)
(2)從BitmapFactory.Options中取出圖片的原始寬高信息。他們對英語outWitdh和outHeight參數
(3) 根據采樣率的規則並結合目標View的所需大小計算出采樣率inSampleSize
(4)將itmapFactory.Options的inJustDecodeBounds參數誰為false,然後重新加載圖片。
public static Bitmap decodeSanpledBitmapFromResource(Resources res,
int resId, int reqWidth, int reqHeight) {
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
options.inSampleSize = calculateInSampleSize(options, reqWidth,
reqHeight);
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
}
public static int calculateInSampleSize(BitmapFactory.Options options,
int reqWidth, int reqHeight) {
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
final int halfHieght = height / 2;
final int halfWidth = width / 2;
while ((halfHieght / inSampleSize) >= reqHeight
&& (halfWidth / inSampleSize >= reqWidth)) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
除了BitmapFactory的decodedResource方法,其他三個decode系列的方法也是支持采樣加載的,並且處理方式也是類似的,但是decodeStream方法稍微特殊,後面會講解。
Android中的緩存策略:
緩存策略在Android中有著廣泛的使用場景,尤其在圖片加載這個場景下,緩存策略變得非常重要。
目前常用的一種緩存算法是LRU,LRU是近期最少使用算法,他的核心思想是當緩存滿時,會優先淘汰那些近期最少使用的緩存對象。采用LRU算法的緩存由兩種:LruCache和DiskLruCache,前者用於實現內存緩存,而後者則充當了存儲設備緩存,通過二者的結合就可以很方便的實現一個很高使用價值的ImageLoader。
LruCache:
LruCache是3.1所提供的一個緩存類,通過v4兼容包可以兼容到早起的版本,為了能夠兼容至2.2版本,在使用LruCache的時候建議采用v4兼容包中提供的LruCache,而不要直接使用3.1提供的LruCache
LruCache是一個泛型類,他內部采用一個LinkedHashMap以強引用的方法存儲外界的緩存對象,其提供了get和put方法來完成緩存的獲取和添加操作。接下來簡答說一個幾個引用:
強引用: 直接的對象引用
軟引用: 當一個對象只有軟引用存在時,系統內存不足時此對象會被gc回收;
弱引用: 當一個對象只有弱引用存在時,此對象會隨時被gc回收。
另外LruCache是線程安全的,因為他使用了LinkedHashMap:
public class LruCache {
private final LinkedHashMap map;
功欲善其實,必先利器,接下來我們就看看LruCache的使用,首先典型的初始化:
int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
int cacheSize = maxMemory / 8;
mMemoryCache = new LruCache(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
// TODO Auto-generated method stub
return bitmap.getRowBytes() * bitmap.getHeight() / 1024;
}
};
只需要提供緩存的總容量大小並重寫sizeOf方法即可。sizeOf方法的作用是計算緩存對象的大小,這裡的大小的單位需要和總容量的單位保持一致。對於上面代碼,總容量的大小為當前進程可用內存的1/8,單位為KB,sizeOf方法則完成了Bitmap對象的大小計算。
一些特殊情況,還需要重寫LruCache的entryRemoved方法,LruCache移除舊緩存時會調用這個方法,因此可以在這個方法中完成一些資源回收(如果需要的話)
除了創建以外還有緩存的獲取和添加。
mMemoryCache.get(key);
mMemoryCache.put(key, value);
DiskLruCache:
DiskLruCache用於實現存儲設備緩存,即磁盤緩存。他通過對緩存對象寫入文件系統從而實現緩存效果。其源碼地址:
下載地址
DiskLruCache的創建:
DiskLruCache並不能通過構造方法來創建,他提供了open方法用於創建自身,如下所示:
public static DiskLruCache open(File directory,int appVersion,int valueCount,long manSize)
第一個參數表示磁盤緩存在文件系統中的存儲路徑,緩存路徑可以選擇SD卡上的緩存目錄,具體是指/sdcard/Android/data/package_name/cache目錄,其中package_name表示當前應用包名,當應用被卸載後,此目錄會一並被刪除。當然也可以選擇SD卡上的其他指定目錄,還可以選擇data下的當前應用目錄,具體可根據需要靈活設定。如果應用卸載後就希望刪除緩存目錄,那麼就選擇SD卡上的緩存目錄,否則就選擇SD卡上的其他目錄。
第二個參數表示應用的版本號,一般設為1即可。當版本號發生變化時DiskLruCache會清空之前所有的緩存文件,而這個特性在實際開發中作用並不大,很多情況下既是應用版本號發生了變化緩存文件仍然是有效的,因此這個參數設為1比較好。
第三個參數表示單個節點所對應的數據的個數,一般設為1即可,
第四個參數表示緩存的總大小,比如50MB,當緩存大小超出這個設定值後,DiskLruCache會清楚一些緩存從而保證總大小不大於這個設定值:
private static final long DISK_CACHE_SIZE=1024*1024*50;
File diskCacheDir=getDiskCacheDir(mContext,"bitmap");
if(!diskCacheDir.exists()){
diskCacheDir.mkdir();
}
mDiskLruCache=DiskLruCache.open(diskCacheDir,1,1,DISK_CACHE_SIZE);
DiskLruCache的緩存添加:
DiskLruCache的緩存添加的操作是通過Editor完成的。Editor表示一個緩存對象的編輯對象,這裡仍以圖片緩存舉例子,首先需要獲取圖片url所對應的key,然後根據key就可以通過edit()來獲取Editor對象,如果這個緩存正在被編輯,那麼會返回null,即DiskLruCache不允許同時編輯一個緩沖對象,之所以要把url轉換為key,是因為圖片的url中很可能有特殊字符,這將影響url在Android中直接使用,一般采用url的md5值作為key:
private String hashKeyFormUrl(String url) {
String cacheKey;
try {
final MessageDigest mDigest = MessageDigest.getInstance("MD5");
mDigest.update(url.getBytes());
cacheKey = bytesToHexString(mDigest.digest());
} catch (Exception e) {
// TODO: handle exception
cacheKey = String.valueOf(url.hashCode());
}
return cacheKey;
}
private String bytesToHexString(byte[] digest) {
// TODO Auto-generated method stub
StringBuilder sb = new StringBuilder();
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();
}
將圖片的url轉化為key以後,就可以獲取Editor對象了,對於這個key來說,如果當前不存在其他Editor對象,那麼edit()就會返回一個新的Editor對象,通過他就可以得到一個文件輸出流,需要注意的是,由於前面在DiskLruCache的open方法中設置了一個子節點只能有一個數據,因此下面的DISK_CACHE_INDEX常量直接設為0即可:
String key=hashKeyFormUrl(url);
DiskLruCache.Editor editor=mDiskLruCache.edit(key);
if(editor!=null){
OutputStream outputStream=editor.newOutputStream(DISK_CACHE_INDEX);
}
有了文件輸出流,接下來就是,從網絡下載圖片時,圖片就可以通過這個文件輸出流寫入到文件系統上:
public 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(outputStream, IO_BUFFER_SIZE);
int b;
while ((b = in.read()) != -1) {
out.write(b);
}
} catch (Exception e) {
// TODO: handle exception
} finally {
if (urlConnection != null) {
urlConnection.disconnect();
}
MyUtils.close(out);
MyUtils.close(in);
}
return false;
}
經過上面的步驟,其實並沒有真正的將圖片寫入文件系統,還必須通過Editor的commit()來提交寫入操作,如果圖片下載 過程發生了異常,那麼還可以通過Editor的abort()來回退整個操作:
OutputStream outputStream=editor.newOutputStream(DISK_CACHE_INDEX);
if(downloadUrlToStream(url,outputStream)){
editor.commit();
}else{
editor.abort();
}
mDiskLruCache.flush();
經過上面的步驟,圖片就已經被正確的寫入到文件系統中了,接下來圖片的獲取操作就不需要請求網絡了。
DiskLruCache的緩存查找:
和緩存添加過程類似,緩存找找過程也需要將url轉換為key,然後通過DiskLruCache的get方法得到一個Snapshot對象,接著再通過Snapshot對象即可得到緩存的文件輸入流,有了文件輸入流,自然就可以得到Bitmap對象了,為了避免加載圖片過程中導致的OOM問題,一般不建議直接加載原始圖片。但是BitmapFactory.Options的方式壓縮圖片對FileInputStream的縮放存在問題,原因是,FileInputStream是一種 有序的文件流,而兩次decodeStream調用印象了文件流的位置屬性,導致了第二次decodeStream時得到的是null。為了解決這個問題,可以通過文件流來得到他所對應的文件描述符,然後再通過BitmapFactory.decodeFileDescriptor方法來加載一張縮放後的圖片,代碼如下:
Bitmap bitmap = null;
String key = hashKeyFormUrl(url);
DiskLruCache.Snapshot snapShot = mDiskLruCache.get(key);
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(key,bitmap);
}
}
好了,就說到這裡,主要介紹了一些緩存策略和高效加載bitmap的方式,後續會繼續對於ImageLoader進行學習並解析。
Android 使用jarsigner給apk簽名的方法詳細介紹工作中APP功能完成以後往往需要往應用商店提交一些內容,如商店中存在本公司別的人員提交的APP,往往需要進
不得不說,當不了解一件事情的時候,就會像當然的認為,其很神秘。但是當真正的接觸到了這些神秘的item,就不會有這種感覺了。作為一個android開發新手的我,剛接觸到了V
1.簡介這是一個用於實現像微信朋友圈和微博的類似的九宮格圖片展示控件,通過自定義viewgroup實現,使用方便。 多圖根據屏幕適配,單張圖片時需要自己指定圖片的寬高;2
當我們進行Android開發,配置安裝環境Eclispe&SDK&ADT過程中,在SDK在線安裝後,重新登錄Eclispe,會出現警告框:This And