获取方式
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为默认图像,避免图片错位。