一、Bitmap的高效加载
BitmapFactory类提供了四种方法:
decodeFile、decodeResource、decodeStream和decodeByteArray,分别用于支持从文件系统、资源、输入流以及字节数组加载一个Bitmap对象。
如何高效加载Bitmap,那就是采用BitmapFactory.Options来加载所需的图片,通过BitmapFactory.Options可以按一定的采样率来加载缩小后的图片,可以在一定程度上避免OOM。
1、通过采样率加载图片步骤:
(1)将BitmapFactory.Options的inJustDecodeBounds参数设置为true并加载图片
(2)从BitmapFactory.Options中取出图片的原始宽高信息,他们对应于outWidth和outHeight参数
(3)根据采样率的规则并结合目标View的所需大小计算出采样率inSampleSize
(4)将BitmapFactory.Options的inJustDecodeBounds参数设为false,然后重新加载图片
示例代码:
public static Bitmap decodeSampleBitmapFromResource(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);
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 halfHeight = height / 2;
final int halfWidth = width / 2;
while((halfHeight / inSampleSize) >= reqHeight &&(halfWidth / inSampleSize) >= reqWidth){
inSampleSize *= 2;
}
}
return inSampleSize;
}
二、Android中的缓存策略
缓存策略主要包含缓存的添加、获取和删除三类操作。常用的缓存算法是LRU,他的核心思想是当缓存满时,会移除那些最近很少使用的缓存对象。
采用LRU缓存的有两种:LruCache和DiskLruCache,LruCache用于实现内存缓存,而DiskLruCache则是存储设备缓存。
1、LruCache
LruCache是一个泛型类,内部采用一个LinkedHashMap以强引用的方式存储外界的缓存对象,提供了get和put方法来完成缓存的获取和添加操作,当缓存满时,LruCache会移除较早使用的缓存对象,然后添加新的缓存对象。这里需要大家明白强引用、弱引用和软引用的区别。
(1)强引用:直接的对象引用
(2)弱引用:当一个对象只有软引用存在时,系统内存不足时此对象会被gc回收
(3)弱引用:当一个对象只有弱引用存在时,此对象随时会被gc回收
下面代码展示了LruCache的初始化过程:
int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
int cacheSize = maxMemory / 8;
mMemoryCache = new LruCache<String,Bitmap>(cacheSize){
@Override
protected int sizeOf(String key,Bitmap bitmap){
return bitmap.getRowBytes()*bitmap.getHeight() / 1204;
}
}
2、DiskLruCache
DiskLruCache用于实现存储设备缓存,即磁盘缓存,通过将缓存对象写入文件系统从而实现缓存的效果。DiskLruCache得到了Android官方文档的推荐,但他不属于AndroidSDK的一部分。
(1)DiskLruCache的创建
public static DiskLruCache open(File directory,int appVersion,int valueCount,long maxSize)
open有四个参数,第一个表示磁盘缓存在文件系统中的存储路径,第二个参数表示应用版本号,第三个参数表示单个节点所对应的数据的个数
下面是DiskLruCache的创建过程
private static final long DISK_CACHE_SIZE = 1024 * 1024 * 50;
File diskCacheDie = getDiskCacheDir(mContext,"bitmap");
if(!diskCacheDir.exists()){
diskCacheDir.mkdirs();
}
mDiskLruCache = DiskLruCache.open(diskCacheDir,1,1,DISK_CACHE_SIZE);
(2)DiskLruCache的缓存添加,示例代码:
String key = hashKeyFromUrl(url);
DiskLruCache.Editor editor = mDiskLruCache.edit(key);
if(editor != null){
OutputStream outputStream = editor.newOutputStream(DISK_CACHE_INDEX);
if(downloadUrlToStream(url,outputStream)){
editor.commit();
}else{
editor.abort();
}
mDiskLruCache.flush();
}
private String hashKeyFormUrl(String url){
String cacheKey;
try{
final MessageDigest mDigest = MessageDigest.getInstance("MD5");
mDigest.update(url.getBytes());
cacheKey = bytesToHexString(mDigest.digest());
}catch(NoSuchAlgorithmException e){
cacheKey = String.valueOf(url.hashCode());
}
return cacheKey;
}
private String bytesToHexString(byte[] bytes){
StringBuilder sb = new StringBuilder();
for(int i = 0 ; i < bytes.length; i ++){
String hex = Integer.toHexString(0xFF & bytes[i]);
if(hex.length() == 1){
sb.append('0');
}
sb.append(hex);
}
return sb.toString();
}
有了文件输出流,接下来从网络下载图片时,图片就可以通过这个流写入到文件系统中,代码如下
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(urlConnection.getInputStream(),IO_BUFFER_SIZE);
out = new BufferedOutputStream(outputStream,IO_BUFFER_SIZE);
int b;
while((b = in.read()) != -1 ){
out.write(b);
}
return true;
}catch(IOException e){
Log.e(TAG,"downloadBitmap failed"+e);
}finally{
if(urlConnection != null){
urlConnection.disconnect();
}
MyUtils.close(out);
MyUtils.close(in);
}
return false;
}
(3)DiskLruCache的缓存查找
和缓存的添加过程类似,缓存查找过程需要将url转换为key,然后通过DiskLruCache的get方法得到一个Snapshot对象,接着在通过Snapshot对象即可得到缓存的文件输入流。代码如下
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);
}
}
三、ImageLoader的实现
一般来说,一个优秀的ImageLoader应具备如下功能:
图片的同步加载、图片的异步加载、图片压缩、内存缓存、磁盘缓存、网络拉取
内存缓存和磁盘缓存是ImageLoader的核心,也是意义所在,通过这两级缓存极大地提高了程序的的效率并且有效地降低了对用于所造成的流量消耗,只有当两级缓存都不可用时才需要从网络拉取图片
代码已上传github:地址:
(1)图片压缩功能实现
(2)内存缓存和磁盘缓存的实现:这里使用LruCache和DiskLruCache来完成内存和磁盘缓存,在ImageLoader初始化时,创建LruCache和DiskLruCache。
(3)同步加载和异步加载接口的设计