一、Bitmap的高效加载
Bitmap(位图)在Android中指的是一张图片,可以是png格式也可以是jpg等其他常见的图片格式。那么如何加载一张图片呢?BitmapFactory类提供了四类方法,decedeFile,decodeResource,decodeStream,decodeByteArray,分别用于支持从文件系统,资源,输入流以及字节数组中加载出一个Bitmap对象,其中decedeFile和decodeResource又间接调用了decodeStream方法,这四类方法最终在Android的底层实现,对应着BitmapFactory类的几个native方法。
如何高效的加载Bitmap呢?其核心思想很简单,那就是采用BitmapFactory.Options来加载所需尺寸的图片。这里假设通过ImageView来显示图片,很多时候ImageView并没有图片的原始尺寸那么大,这个时候把整个图片加载进来在设给ImageView,这显然是没有这个必要的,因为ImageView并没有办法显示原始的图片,通过BitmapFactory.Options局可以按一定的采样率来加载缩小后的图片,将缩小后的图片在ImageView中显示,这样就能降低内存占用从而在一定程度上避免OOM,提高Bitmap加载时的性能。BitmapFactory提供的加载图片的四类方法都支持BitmapFactory.Options参数,通过他们可以对图片进行采样缩放。
通过BitmapFactory.Options来缩放图片,主要是通过其inSampleSize参数,即采样率。当inSampleSize 为1时,采样图片为原始图片大小,当为2时,采样图片宽高均为原始图片1/2,而像素为原图的四分之一,其占内存也是原图的四分之一,缩放比例要是2的指数,例如:1,2,4,8,16.
如何获得采样率:
- 将BitmapFactory.Options的inJustDecodeBounds参数设置为true加载图片
- 从BitmapFactory.Options中取出图片的原始宽高信息,他们对应于outWidth,outHeight参数
- 根据采样率的规则并结合目标view的所需大小计算出采用率inSampleSize
- 将BitmapFactory.Options的inJustDecodeBounds参数设置为false,然后重新加载图片。
public class ImageResizer {
private static final String TAG = "ImageResizer";
public ImageResizer() {
}
public Bitmap decodeSampledBitmapFromResource(Resources res,
int resId, int reqWidth, int reqHeight) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth,
reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
}
public Bitmap decodeSampledBitmapFromFileDescriptor(FileDescriptor fd, int reqWidth, int reqHeight) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFileDescriptor(fd, null, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth,
reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeFileDescriptor(fd, null, options);
}
public int calculateInSampleSize(BitmapFactory.Options options,
int reqWidth, int reqHeight) {
if (reqWidth == 0 || reqHeight == 0) {
return 1;
}
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
Log.d(TAG, "origin, w= " + width + " h=" + height);
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
// Calculate the largest inSampleSize value that is a power of 2 and
// keeps both
// height and width larger than the requested height and width.
while ((halfHeight / inSampleSize) >= reqHeight
&& (halfWidth / inSampleSize) >= reqWidth) {
inSampleSize *= 2;
}
}
Log.d(TAG, "sampleSize:" + inSampleSize);
return inSampleSize;
}
}
使用,ImageView所希望大小是100*100像素。
imgeView.setImageBitmap(decodeSampledBitmapFromResource(getResource(),R.id.imgae,100,100));
二、Android中的缓存策略
1.LruCache
LruCache是Android3.1所提供的一个缓存类,通过support-v4可以兼容到早期的2.2版本。LruCache是一个泛型类,它内部采用一个LinkedHashMap以强引用的方式存储外界的缓存对象,其提供get/put方法来完成缓存的获取和添加操作,当缓存满了,LruCache会移除较早使用的缓存对象,然后再添加新的缓存对象。
- 强引用:直接的对象引用。
- 软引用:当一个对象只有软引用时,系统内存不足时此对象会被gc回收。
- 弱引用:当一个对象只有弱引用存在时,此对象随时会被gc回收。
LruCache还是线程安全的。
2.DiskLruCache
DiskLruCache用于实现存储设备缓存,即磁盘缓存,它通过将缓存对象写入文件系统从而实现缓存效果。
1.DiskLruCache的创建
public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize)
- directory:磁盘缓存在文件系统中的存储路径,可以是SD卡上的缓存路径,会随着程序的卸载而删除,也可以指定路径,卸载应用后任然保存。
- appVersion:应用的版本号,版本号发生改变时,DiskLruCache会清空之前的所有缓存文件,一般设为1就好。
- valueCount:单个节点所对应的数据的个数,一般设置为1就好
- maxSize:缓存的总大小
下面是一个DiskLruCache创建的典型过程:
private static final long DISK_CACHE_SIZE = 1024*1024*50;
try {
File file = getDiskCacheDir(this,"ca");
if(!file.exists()){
file.mkdirs();
}
DiskLruCache c = DiskLruCache.open(file,1,1,DISK_CACHE_SIZE);
} catch (IOException e) {
e.printStackTrace();
}
public File getDiskCacheDir(Context context, String uniqueName) {
boolean externalStorageAvailable = Environment
.getExternalStorageState().equals(Environment.MEDIA_MOUNTED);
final String cachePath;
if (externalStorageAvailable) {
cachePath = context.getExternalCacheDir().getPath();
} else {
cachePath = context.getCacheDir().getPath();
}
return new File(cachePath + File.separator + uniqueName);
}
2.DiskLruCache的缓存添加
将url转换成key
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();
}
获取Editor 对象
private Bitmap loadBitmapFromHttp(String url, int reqWidth, int reqHeight)
throws IOException {
if (Looper.myLooper() == Looper.getMainLooper()) {
throw new RuntimeException("can not visit network from UI Thread.");
}
if (mDiskLruCache == null) {
return null;
}
String key = hashKeyFormUrl(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();
}
return loadBitmapFromDiskCache(url, reqWidth, reqHeight);
}
从当前网络下载图片,然后通过文件流写到文件系统中
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的缓存查找
private Bitmap loadBitmapFromDiskCache(String url, int reqWidth,
int reqHeight) throws IOException {
if (Looper.myLooper() == Looper.getMainLooper()) {
Log.w(TAG, "load bitmap from UI Thread, it's not recommended!");
}
if (mDiskLruCache == null) {
return null;
}
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);
}
}
return bitmap;
}
3.ImageLoader的实现
具有以下几个功能
- 图片的同步加载
- 图片的异步加载
- 图片的压缩
- 内存缓存
- 磁盘缓存
- 网络拉取
见书424