最近做的项目有用到查看系统图片并选择多张图片的功能,网上搜索了一些源码运行起来,拖动一下缩略图列表就卡住了,都是没有考虑到OOM的原因。结合网上的一些LRUCACHE和ASYNCTASK资料,自己写了个小组件。最开始通过读取系统thumbnail(缩略图)表获取缩略图,但是有的缩略图图片是旋转过的,又读不到旋转的角度,而原始图片可以读到旋转角度,所以只有先读取原始图片,裁剪成缩略图。此处读的是Camera文件夹的图片,还可以像微信图片那样扩展,按文件夹分类(图片、视频、截屏等),在此抛砖引玉,欢迎各位雅正。
先获取所有原始图片路径。
/* 获取本地所有图片路径 */
private void getAllPaths() {
if (!DeviceUtil.isSDCardMounted()) {
Toast.makeText(this, "SD卡没有正常挂载,无法读取图片!", Toast.LENGTH_SHORT)
.show();
return;
}
mProgressDialog = ProgressDialog.show(this, null, "正在扫描图片,请稍候...");
new Thread(new Runnable() {
@Override
public void run() {
Cursor cursor = null;
try {
String[] proj = { MediaStore.Images.Media.DATA };
String where = "mime_type in ('image/png','image/jpeg')";
String sortOrder = Media.DATE_MODIFIED;
cursor = mContentResolver.query(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI, proj,
where, null, sortOrder + " DESC");
if (cursor == null || !cursor.moveToFirst()) {
return;
}
do {
int column_index = cursor
.getColumnIndexOrThrow(proj[0]);
String imagePath = cursor.getString(column_index);
imagePaths.add(imagePath);// 图片路径
} while (cursor.moveToNext());
} catch (Exception e) {
e.printStackTrace();
} finally {
if (cursor != null) {
cursor.close();
}
mHandler.sendEmptyMessage(SCAN_OK);
}
}
}).start();
}
LruCaheUtil类
public class LruCacheUtil {
/**
* 缓存Image的类,当存储Image的大小大于LruCache设定的值,系统自动释放内存
*/
private LruCache<String, Bitmap> mMemoryCache;
public LruCacheUtil(Context context) {
// 获取系统分配给每个应用程序的最大内存,每个应用系统分配32M
int maxMemory = (int) Runtime.getRuntime().maxMemory();
int mCacheSize = maxMemory / 8;
// 给LruCache分配1/8 4M
mMemoryCache = new LruCache<String, Bitmap>(mCacheSize) {
// 必须重写此方法,来测量Bitmap的大小
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getRowBytes() * value.getHeight();
}
};
}
/**
* 移除图片缓存
*
* @param key
*/
public synchronized void removeImageCache(String key) {
if (key != null) {
if (mMemoryCache != null) {
Log.i("LruCacheUtils", "移除的图片名称" + key);
Bitmap bm = mMemoryCache.remove(key);
if (bm != null)
bm.recycle();
}
}
}
/**
* 清除缓存
*/
public void clearCache() {
if (mMemoryCache != null) {
if (mMemoryCache.size() > 0) {
Log.d("LruCacheUtils", "缓存的大小为:mMemoryCache.size() "
+ mMemoryCache.size());
mMemoryCache.evictAll();
Log.d("LruCacheUtils", "清空后缓存的大小为:mMemoryCache.size()"
+ mMemoryCache.size());
}
mMemoryCache = null;
}
}
/**
* 添加Bitmap到内存缓存
*
* @param key
* @param bitmap
*/
public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
if (getBitmapFromMemCache(key) == null && bitmap != null) {
mMemoryCache.put(key, bitmap);
}
}
/**
* 从内存缓存中获取一个Bitmap
*
* @param key
* @return
*/
public Bitmap getBitmapFromMemCache(String key) {
return mMemoryCache.get(key);
}
}
GridView的Adapter实现呢了OnScrollListener接口,这样可以控制每次只加载屏幕可见部分的缩略图。缩略图的加载是通过ASYNCTASK来实现的。内存缓存中有,取内存缓存;内存缓存中没有,取文件缓存,并将图片存入内存缓存;文件缓存中没有,再读取原图处理,并将图片存入内存缓存和文件缓存。这样可以浏览海量图片时发生OOM。
public class GridAdapter extends BaseAdapter implements OnScrollListener {
private static final String TAG="GridAdapter";
/**
* 获取选中图片数量监听
*/
public OnSelectedListener onSelectedListener;
/**
* 异步图片处理器
*/
private AsyncLoadedImage asyncLoadedImage;
/**
* LruCache缓存工具
*/
private LruCacheUtil mLruCacheUtils;
/**
* 获取每个ITEM大小,作为缩放图片的宽度和高度
*/
private Point mPoint = new Point(0, 0);
private LayoutInflater laytouInflater;
/**
* 本地所有图片路径列表
*/
private List<String> mImagePaths;
/**
* 记录全部缩略图选中状态
*/
private HashMap<Integer, Boolean> mSelectedMap = new HashMap<Integer, Boolean>();
/**
* 记录已选中缩略图路径
*/
@SuppressLint("UseSparseArrays")
private HashMap<Integer, String> mPathSelectedMap = new HashMap<Integer, String>();
/**
* 是否首次进入界面
*/
private boolean mIsFirstEnter = true;
/**
* 一屏中第一个item的位置
*/
private int mFirstVisibleItem;
/**
* 一屏中所有item的个数
*/
private int mVisibleItemCount;
/**
* 父容器
*/
private GridView mGridView;
private Context mContext;
/* 一次最多可选的图片数量 */
private int mMaxSelectedCount = 0;
/**
* 设置选中项目后的监听
*
* @param onSelectedCountListener
*/
public void setOnSelectedListener(OnSelectedListener onSelectedListener) {
this.onSelectedListener = onSelectedListener;
}
public GridAdapter(Context context, GridView gridView,
List<String> imagePaths, int maxSelectedCount) {
mContext = context;
laytouInflater = LayoutInflater.from(context);
mGridView = gridView;
mGridView.setOnScrollListener(this);
mImagePaths = imagePaths;
asyncLoadedImage = new AsyncLoadedImage();
mLruCacheUtils = new LruCacheUtil(context);
mMaxSelectedCount = maxSelectedCount;
}
@Override
public int getCount() {
return mImagePaths.size();
}
@Override
public Object getItem(int position) {
return mImagePaths.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
ViewHolder viewHolder = null;
final String imagePath = mImagePaths.get(position);
if (convertView == null) {
viewHolder = new ViewHolder();
convertView = laytouInflater.inflate(R.layout.grid_view_item, null);
viewHolder.mCheckBox = (CheckBox) convertView
.findViewById(R.id.cb_select);
viewHolder.mImageView = (MyImageView) convertView
.findViewById(R.id.iv_thumbnail);
viewHolder.mImageView.setOnMeasureListener(new OnMeasureListener() {
@Override
public void onMeasureSize(int width, int height) {
mPoint.set(width, height);
}
});
convertView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) convertView.getTag();
viewHolder.mImageView
.setImageResource(R.drawable.friends_sends_pictures_no);// 先设置每个ITEM的默认图片
}
viewHolder.mImageView.setTag(imagePath);
// 图片选中监听
viewHolder.mCheckBox
.setOnCheckedChangeListener(new OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView,
boolean isChecked) {
mSelectedMap.put(position, isChecked);
// 统计已选择的图片
if (isChecked) {
mPathSelectedMap.put(position, imagePath);
} else {
mPathSelectedMap.remove(position);
}
// 限制每次选择图片的数量
if (mPathSelectedMap.size() > mMaxSelectedCount) {
Toast.makeText(
mContext,
String.format(
mContext.getResources()
.getString(
R.string.max_selected_cousts),
mMaxSelectedCount),
Toast.LENGTH_SHORT).show();
buttonView.setChecked(false);
mSelectedMap.put(position, false);
mPathSelectedMap.remove(position);
} else {
if (onSelectedListener != null) {
onSelectedListener
.setSelectResult(mPathSelectedMap);// 设置选中数量
}
}
}
});
viewHolder.mCheckBox
.setChecked(mSelectedMap.containsKey(position) ? mSelectedMap
.get(position) : false);
return convertView;
}
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
if (scrollState == AbsListView.OnScrollListener.SCROLL_STATE_IDLE) {
asyncLoadedImage = new AsyncLoadedImage();
asyncLoadedImage.execute();
} else {
if (asyncLoadedImage != null)
asyncLoadedImage.cancel(true);
}
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
mFirstVisibleItem = firstVisibleItem;
mVisibleItemCount = visibleItemCount;
// 因此在这里为首次进入程序开启下载任务。
if (mIsFirstEnter && visibleItemCount > 0) {
asyncLoadedImage.execute();
mIsFirstEnter = false;
}
}
class AsyncLoadedImage extends AsyncTask<Object,GridViewItem,Object>{
@Override
protected void onPostExecute(Object result) {
// TODO Auto-generated method stub
super.onPostExecute(result);
}
@Override
protected void onProgressUpdate(GridViewItem... values) {
if (isCancelled())
return;
for (GridViewItem gridViewItemBean : values) {
ImageView mImageView = (ImageView) mGridView
.findViewWithTag(gridViewItemBean.getmPathName());
if (mImageView != null) {
mImageView.setImageBitmap(gridViewItemBean.getmBitmap());
}
}
}
@Override
protected Object doInBackground(Object... params) {
if (isCancelled())
return null;
for (int i = mFirstVisibleItem; i < mFirstVisibleItem
+ mVisibleItemCount; ++i) {
try {
String imagePath = mImagePaths.get(i);
GridViewItem gridViewItemBean;
String fileName = FileUtil.getInstance().getFileName(
imagePath);
// 首先内存缓存中取
Bitmap bitmapLruCache = mLruCacheUtils
.getBitmapFromMemCache(fileName);
if (bitmapLruCache != null) {
gridViewItemBean = new GridViewItem();
gridViewItemBean.setmBitmap(bitmapLruCache);
gridViewItemBean.setmPathName(imagePath);
} else {// 内存缓存没有,再从文件缓存获取
Bitmap bitmapCache = FileUtil.getInstance().getBitmap(
fileName);// 获取文件缓存
if (bitmapCache != null) {
gridViewItemBean = new GridViewItem();
gridViewItemBean.setmBitmap(bitmapCache);
gridViewItemBean.setmPathName(imagePath);
mLruCacheUtils.addBitmapToMemoryCache(fileName,
bitmapCache);
} else {// 文件缓存没有,再读取原图处理
Bitmap bitmap;
Bitmap rotateBitmap;
Bitmap newBitmap;
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 10;
bitmap = BitmapFactory.decodeFile(imagePath,
options);
// 获取图片旋转的角度
int angle = BitmapUtil
.readPictureDegree(imagePath);
rotateBitmap = angle != 0 ? BitmapUtil
.rotaingImageView(angle, bitmap) : bitmap;
newBitmap = ThumbnailUtils.extractThumbnail(
rotateBitmap,
mPoint == null ? 0 : mPoint.x,
mPoint == null ? 0 : mPoint.y);// 获取缩略图
bitmap.recycle();
rotateBitmap.recycle();
if (newBitmap == null)
continue;
gridViewItemBean = new GridViewItem();
gridViewItemBean.setmBitmap(newBitmap);
gridViewItemBean.setmPathName(imagePath);
// 将图片存到文件缓存
FileUtil.getInstance().savaBitmap(fileName,
newBitmap);
// 将图片加入LruCache缓存
mLruCacheUtils.addBitmapToMemoryCache(fileName,
newBitmap);
}
}
publishProgress(gridViewItemBean);
} catch (Exception e) {
e.printStackTrace();
}
}
return null;
}
}
class ViewHolder {
MyImageView mImageView;
CheckBox mCheckBox;
}
/**
* 获取选中项目后监听
*
* @author SuttonSun
*
*/
public interface OnSelectedListener {
public void setSelectResult(HashMap<Integer, String> map);
}
/**
* 通知更新
*
* @param list
* 图片路径列表
*/
public void setDatasetChanged(List<String> list) {
mImagePaths = list;
mIsFirstEnter = true;
asyncLoadedImage = new AsyncLoadedImage();
this.notifyDataSetChanged();
}
/**
* 取消加载
*/
public void cancelTask() {
if (asyncLoadedImage != null) {
Log.i(TAG, "停止同步任务!");
asyncLoadedImage.cancel(true);
}
if (mLruCacheUtils != null) {
// 清除缓存
mLruCacheUtils.clearCache();
}
}
}
以上是关键部分代码。
效果图:
源码下载地址:http://download.csdn.net/detail/qq_33819047/9414437