獲取方式
1.內存
內存緩存主要使用LRU緩存算法,引用support-v4中的LruCache,
通過鍵值對的形式獲取到相應的bitmap,配置如下:
//初始化緩存策略
int maxMem = (int) (Runtime.getRuntime().maxMemory() / 1024);
int cacheSize = maxMem / 8;
mMemCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap value) {
//計算緩存對象大小
return value.getRowBytes() * value.getHeight() / 1024;
}
};
2.磁盤緩存
磁盤緩存也是採用LRU緩存算法,一般從網上直接獲取DiskLruCache源碼,配置好,通過鍵值對的形式獲取。
DiskLruCache的入口函數如下:
/*
* 執行初始化磁盤緩存操作,
* 注意directory要先mkdir
*/
public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize);
/**
* 獲取磁盤緩存路徑
* @param context
* @param uniqueName
* @return
*/
public static File getDiskCacheDir(Context context, String uniqueName) {
// Check if media is mounted or storage is built-in, if so, try and use external cache dir
// otherwise use internal cache dir
final String cachePath =
Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) ||
!isExternalStorageRemovable() ? getExternalCacheDir(context).getPath() :
context.getCacheDir().getPath();
return new File(cachePath + File.separator + uniqueName);
}
directory:爲緩存存儲路徑,
appVersion: 版本號,改變後緩存會改變,一般設爲1
valueCount: 單節點對應數據個數,一般爲1
maxSize: 最大緩存容量,快滿會自動清理,單位爲Byte
注意的是這裏的key值不能直接存圖片url,而是需要將其轉化爲md5值,避免特殊字符干擾。
3.磁盤I/O
通過ContentResolver直接遍歷獲取磁盤上的圖片即可。
//獲取圖片存儲路徑
Uri mImageUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
ContentResolver mContentResolver = getContentResolver();
//獲得遊標並執行相關數據庫表的查詢
Cursor mCursor = mContentResolver.query(mImageUri,null, MediaStore.Images.Media.MIME_TYPE + "=? or "+ MediaStore.Images.Media.MIME_TYPE +"=?",new String[]{ "image/jpeg","image/png"}, MediaStore.Images.Media.DATE_MODIFIED);
if(mCursor==null){
return;
}
while(mCursor.moveToNext()){
//獲取圖片的路徑
String path = mCursor.getString(mCursor.getColumnIndex(MediaStore.Images.Media.DATA));
//TODO 儲存path
}
4.網絡
直接通過HttpURLConnection獲取到IO流便可
//網絡直接獲取到stream,注意getInputStream操作才執行網絡請求
HttpURLConnection connect = null;
InputStream in = null;
try {
final URL urlString = new URL(url);
connect = (HttpURLConnection) urlString.openConnection();
connect.setDoInput(true);
connect.connect();
in = connect.getInputStream();
}...
使用事項
1.bitmap佔用內存過大問題
android裏bitmap是個大胖子,加載大量圖片時需要對其進行縮放處理,而加載單張全圖時,則注意將對應的imageView設置爲弱引用,這樣gc會更快回收。
bitmap縮放使用以下方法:
/**
* 根據文件描述符來傳入圖片流並壓縮成制定的bitmap
* @param fd
* @param reqWidth
* @param reqHeight
* @return
*/
public static Bitmap decodeByFD(FileDescriptor fd, int reqWidth, int reqHeight) {
BitmapFactory.Options options = new BitmapFactory.Options();
//爲true時是隻取樣,幾乎不佔內存
options.inJustDecodeBounds = true;
BitmapFactory.decodeFileDescriptor(fd,null,options);
options.inSampleSize = calculateInSampleSize(options,reqWidth,reqHeight);
options.inJustDecodeBounds = false;
return BitmapFactory.decodeFileDescriptor(fd,null,options);
}
/**
* 計算壓縮比,一般是2的倍數,爲1時保留原來的格式
* @param options
* @param reqWidth
* @param reqHeight
* @return
*/
private static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
if (reqWidth == 0 && reqHeight == 0) {
return 1;
}
int realWidth = options.outWidth;
int realHeight = options.outHeight;
int inSampleSize = 1;
if (reqWidth < realWidth || reqHeight < realHeight) {
realWidth /= 2;
realHeight /= 2;
while((realWidth/inSampleSize)>=reqHeight&&(realHeight/inSampleSize)>=reqWidth){
inSampleSize*=2;
}
//這裏是考慮到極端情況,寬度比高度大很多的圖片需要繼續縮放
//總的圖片大小
long totalPixels = realWidth * realHeight / inSampleSize;
//imageView提供的大小
final long totalReqPixelsCap = reqWidth * reqHeight * 2;
while (totalPixels > totalReqPixelsCap) {
inSampleSize *= 2;
totalPixels /= 2;
}
}
return inSampleSize;
}
不一定是decodeByFile,可以利用BitmapFactory提供的其他decode方法,但注意的是,使用decodeStream時,兩次操作會改變fileInputStream的位置信息,使得第二次decode獲取到的爲null,根本原因是fileInputStream爲有序的。
另外MedioStore.Images.Thumbnails中提供了本地圖片縮略圖的獲取方式,如下:
String[] projection = new String[]{MediaStore.Images.Media._ID, MediaStore.Images.Media.DATA};
String[] condition = new String[]{url};
Cursor cursor = mContext.getContentResolver().query(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI, projection, MediaStore.Images.Media.DATA + " =? ",
condition, null);
if (cursor == null) {
return;
}
if (cursor.moveToNext()) {
long imageId = cursor.getLong(cursor.getColumnIndex(MediaStore.Images.Media._ID));
String data = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA));
bitmap = MediaStore.Images.Thumbnails.getThumbnail(mContext.getContentResolver(), imageId,
MediaStore.Images.Thumbnails.MICRO_KIND, null);
}
getThumbnail可以獲取到本地圖片的縮略圖,且縮略圖有兩種,分別是MINI_KIND 512 x 384和MICRO_KIND 96 x 96 此方法與bitmapFactory的decode的效率還有待深入比較。
另外,這裏有個小bug,就是使用getContentResolver查找不到MICRO_KIND類型圖片的路徑,MINI_KIND是可以獲取的,歡迎有興趣的朋友一起討論。
2線程安全問題
大量的圖片操作伴隨高耗時,所以除了內存緩存中的獲取,一般採用線程池來異步加載圖片,保證不阻礙UI主線程。
而ImageView的setBitmap操作要在主線程中操作,所以完成圖片處理需要及時把圖片信息通過主線程的handler傳遞message出去。
通過AsyncTask的executeOnExecutor方法可以自己配置併發使用的線程池,AsyncTask自帶的線程池是串行的。
3界面卡頓問題
1)listview和gridview滑動過快,導致頻繁的圖片加載
2)listview和gridview的imageView控件複用,導致bitmap在未被加載完全時,顯示被複用的bitmap
解決方案:
1 ) 監聽滑動狀態,通過OnScrollStateChanged監聽滑動操作,如果使快速滑動時,不執行i/o和網絡請求,代碼如下:
mGridView.setOnScrollListener(new AbsListView.OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
if (scrollState == AbsListView.OnScrollListener.SCROLL_STATE_IDLE) {
mPhotoAdapter.setmIsScroll(false);
mPhotoAdapter.notifyDataSetChanged();
} else {
mPhotoAdapter.setmIsScroll(true);
}
}
});
2) getView中對imageView的bitmap的url進行記錄,即設置完bitmap後,將圖片的url通過imageView.setTag保存起來,在getView開始時就用實際的url與getTag裏對比,如果不一致,則設置bitmap爲默認圖像,避免圖片錯位。