思路
第一步,获取所有图片路径
第一步获取手机上的所有图片路径:
Uri uri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
ContentResolver contentResolver = getContentResolver();
//获取jpeg和png格式的文件,并且按照时间进行倒序
Cursor cursor = contentResolver.query(uri, null, MediaStore.Images.Media.MIME_TYPE + "=\"image/jpeg\" or " +
MediaStore.Images.Media.MIME_TYPE + "=\"image/png\"", null, MediaStore.Images.Media.DATE_MODIFIED+" desc");
if (cursor != null){
while (cursor.moveToNext()){
//do something
}
handler.sendEmptyMessage(0);
}
然后定义一个存储图片的数据格式:
/** 按时间排序的所有图片list */
private ArrayList<SingleImageModel> allImages;
/** 按目录排序的所有图片list */
private ArrayList<SingleImageDirectories> imageDirectories;
/**
* 一个文件夹中的图片数据实体
*/
private class SingleImageDirectories{
/** 父目录的路径 */
public String directoryPath;
/** 目录下的所有图片实体 */
public ImageDirectoryModel images;
}
一个是全部图片的存储顺序,第二个是按照目录的图片存储顺序。
第二步,压缩加载图片
获取到图片之后,放入到 Gridview 中进行显示,但是 BitmapFactory.decodeFile() 函数会非常耗时,所以为了使得非常流畅的显示图片,创建一个类 AlbumBitmapCacheHelper
,用来异步加载图片,该类使用 LruCache<String,Bitmap>
来缓存 Bitmap,使得存储图片不会造成 OOM,我这里设置 LruCache 的初始大小为 1/4 的运行时内存然后使用 ThreadPoolExecutor
线程池来处理图片的显示,线程池大小应该设置适中(可以根据系统的处理器线程数来设置,系统也提供一个相关的线程池,感兴趣的也可以去了解),做完这两件事情之后就可以用来加载图片了,方法 getBitmap 用来返回图片:
Bitmap bitmap = getBitmapFromCache(path, width, height);
//如果能够从缓存中获取符合要求的图片,则直接回调
if (bitmap != null) {
} else {
//新建线程放入线程池去处理该图片的显示
}
return bitmap;
如果 cache 中找不到该图片,则调用 BitmapFactory.decodeFile() 去加载图片,加载图片不能够直接加载原图,会造成 OOM,所以要去计算压缩比:
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(path, options);
options.inSampleSize = computeScale(options, width, height);
options.inJustDecodeBounds = false;
bitmap = BitmapFactory.decodeFile(path, options);
//获取之后,放入缓存,以便下次继续使用
if (bitmap != null && cache!=null) {
cache.put(path, bitmap);
}
方法 computeScale() 主要是计算图片最小的压缩比,这样在 Gridview 中的 getview 方法中去调用 AlbumBitmapCacheHelper
的 getBitmap() 方法即可。
第三步,处理显示问题
经过上面的处理之后,在实际显示的时候会出现一些问题,这里也汇总和分析一下:
显示图片闪烁
第一个问题就是图片显示会闪烁,这主要是由于 getview 方法的 convertView 的复用导致一个 Imageview 被设置多次 background,解决方法就是使用 setTag 方法:
holder.iv_content.setTag(path);
将要显示的 Imageview 的 tag 设置为需要显示的图片路径,这样在回调的时候使用方法 gridView.findViewWithTag(path),找到这个 Imageview 进行显示,闪烁的问题就解决了。
大图片加载速度缓慢
第二个问题就是加载速度很慢,拉的速度很快的情况下,图片要很久才会加载出来,特别是很大的图片,比如拍照和截图的照片,这个问题可以有两个步骤去优化:第一个优化方案就是在 AlbumBitmapCacheHelper
类中维护一个 ArrayList<String> currentShowString,在 getView 方法中,如果该图片要显示,则直接将 path 加入到该 list 中,同时如果这个 view 的 tag 不为空,说明该 view 的原来的 path 是不需要显示的,所以需要将这个 path 从 ArrayList 中删除:
//优化显示效果
if(holder.iv_content.getTag() != null) {
String remove = (String) holder.iv_content.getTag();
AlbumBitmapCacheHelper.getInstance().removePathFromShowlist(remove);
}
AlbumBitmapCacheHelper.getInstance().addPathToShowlist(path);
这样在线程池中的处理方式就是先查看需要显示的 path 是否在 ArrayList 中,如果没有在 ArrayList 中,则该线程直接关闭,如果在 ArrayList 中,则显示该图片:
if (!currentShowString.contains(path)||cache==null) {
return;
}
第二个优化方案是如果显示的图片很大,特别是拍照和截图的图片,decode 有时会耗时几秒钟,微信显示效果非常好,我自己参考微信的表现想出来的处理的方式是:<ol><li>第一步,从应用的缓存 temp 目录下取,如果取不到,则进行下一步;</li><li>第二步,计算图片的压缩比例 samplesize,如果 samplesize < 4(根据表现一般的相机拍摄照片为 4000*3000,需要缩放 4 倍才能加速 decode 步骤),图片的 BitmapFactory.decodeFile() 时间短,直接返回图片,但是如果 samplesize > 4,执行第三步;</li><li>第三步则将压缩后的图片存入 temp 目录下,以便下次快速取出,这样微信图片展示的效果就出来了,显示的速度和微信一样,第一次大图加载慢之外,之后的显示就能很快:
if (!new File(CommonUtil.getDataPath()).exists())
new File(CommonUtil.getDataPath()).mkdirs();
//临时文件的文件名
String tempPath = CommonUtil.getDataPath() + hash + ".temp";
//如果该文件存在
if (new File(tempPath).exists())
bitmap = BitmapFactory.decodeFile(tempPath);
......
//第三步,如果缩放比例大于4,该图的加载会非常慢,所以将该图保存到临时目录下以便下次的快速加载
if (options.inSampleSize >= 4) {
try {
File file = new File(tempPath);
if (!file.exists())
file.createNewFile();
FileOutputStream fos = new FileOutputStream(file);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
100, baos);
fos.write(baos.toByteArray());
fos.flush();
fos.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
</li></ol>
问题到这里就差不多解决了;
第四步,详情页面大图的展示
第四步大图的查看,大图主要是使用网上开源的 ZoomImageView+Viewpagger 的组合,但是使用这个出现的问题就是很容易 OOM,没办法,我的处理方式就是在点进去大图的时候:
public void releaseHalfSizeCache() {
cache.resize((int) (Runtime.getRuntime().maxMemory() / 1024 / 8));
}
第五步,退出相册页面清空 LruCache
图片选择完成之后,完成善后工作,将 AlbumBitmapCacheHelper
类中 LruCache 清空,差不多就这样了,还有很多的功能小点,比如图片时间的显示,这里就不详细一一去介绍了,具体大家看源码。