本章的学习内容有以下几点:
- Bitmap的加载
- Cache缓存策略
- ImageLoader实现
一、Bitmap的加载
(一)BitmapFactory类提供的四种加载图片的方法:
-
BitmapFactory.decodeFile
:文件系统 -
BitmapFactory.decodeResource
:资源 -
BitmapFactory.decodeStream
:输入流 -
BitmapFactory.decodeByteArray
:字节数组
注:
decodeFile()
和decodeResource()
又间接调用decodeStream()
。
(二)高效加载Bitmap
-
采用
BitmapFactory.Options
来加载所需尺寸的图片,就可以按一定采样率
来加载缩小后的图片,将缩小后的图片在 ImageView 中显示,这样就能降低内存占用从而在一定程度上避免OOM
,提高了 Bitmap 加载时的性能。 -
当采样率
inSampleSize=K=2
的时候,采样后的图片其宽高
均为原图大小的1/K
。图片的内存
大小缩放比例为1/(K^2)
。 -
采样率
inSampleSize
的取值范围为2的指数
(1、2、4...),非 2 的指数向下取(例子:3取2)。
注意:根据图片宽高的
实际大小&需要大小
,而计算出的缩放比尽可能取最小,避免由于缩小的过多,导致在控件中不能铺满而被拉伸至模糊。
(三)有效加载图片的步骤
- 将 BitmapFactory.Options 的
inJustDecodeBounds
参数设为 true 进行解析图片宽高。 - 从 BitmapFactory.Options 的中取出图片的
原始宽高
。 - 根据采样率的规则结合目标 View 的所需大小计算出采样率
inSampleSize
。 - 将 BitmapFactory.Options 的
inJustDecodeBounds
参数设为 false,然后重新加载图片。
/**
* 对一个Resources的资源文件进行指定长宽来加载进内存, 并把这个bitmap对象返回
* @param res 资源文件对象
* @param resId 要操作的图片id
* @param reqWidth 最终想要得到bitmap的宽度
* @param reqHeight 最终想要得到bitmap的高度
* @return 返回采样之后的bitmap对象
*/
public static Bitmap decodeFromResource(
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);
//关闭解析,重新加载宽高
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
}
/**
* 计算采样率
*
* @param options 要操作的原始图片属性
* @param reqWidth 最终想要得到bitmap的宽度
* @param reqHeight 最终想要得到bitmap的高度
* @return 返回采样率
*/
private 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;
//计算缩放比,是2的指数
while ((halfHeight / inSampleSize) >= reqHeight
&& (halfWidth / inSampleSize) >= reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
执行代码的方法:
//通过这样的方式可以高效的加载并显示图片
ImageView.setImageBitmap(decodeFromResource(getResources(),R.id.myImg,100,100));
其实在Android中有许多优秀的图片加载开源框架,比如Glide、Picasso和Fresco等等。一定要去学习这些优秀框架,要学习他们是怎么写的,从中提升自己的代码能力。
所以在这节的内容中只是了解怎么加载图片而已,真正重要的是怎么去做缓存
二、缓存策略
(一)缓存的概念
- 缓存:就是将从服务器请求到的数据(Json,File)等保存到本地。
(二)缓存的优势
- 对一些不是经常发生变化的数据直接使用本地缓存,减少消耗用户的流量。
- 不再频繁请求服务器,可以降低服务器的负载压力。
- 可以在一些特殊场景下使用:离线使用。
(三)缓存的使用场景
- 对 Bitmap 和 File 等大数据进行缓存,无需每次都下载,比如 ListView。
- 数据不需实时更新。
(四)常用缓存策略
-
缓存算法LRU(Least Recently Used):当缓存满时,会优先淘汰那些近期最少使用的缓存对象。
-
LruCache
:实现内存缓存。 -
DiskLruCache
:实现硬盘缓存。
-
(五)LruCache
-
LruCache 类是一个线程安全的泛型类:内部采用一个
LinkedHashMap
以强引用的方式存储外界的缓存对象,并提供get
和put
方法来完成缓存的获取和添加操作,当缓存满时会移除较早使用的缓存对象,再添加新的缓存对象。
public class LruCache<K, V> {
private final LinkedHashMap<K, V> map;
...
}
//私有构造函数,用来初始化缓存对象
private SimpleImageLoader() {
//1.获取当前进程的可用内存,转换成KB单位
int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
//2.分配缓存的大小
int cacheSize = maxMemory / 8;
//3.创建LruCache对象并重写 sizeOf 方法
mLruCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
//转换成KB,sizeOf完成Bitmap对象的大小计算
return bitmap.getRowBytes() * bitmap.getHeight() / 1024;
}
};
}
//4.从缓存中读取图片
private Bitmap getBitmapfromCache(String url) {
return mLruCache.get(url);
}
//5.将下载的图片保存在缓存中
private void putBitmaptoCache(Bitmap bitmap, String url) {
if (bitmap != null) {
mLruCache.put(url, bitmap);
}
}
- 实现原理:LinkedHashMap 利用一个双重链接链表来维护所有条目item。
-
常用属性accessOrder:决定LinkedHashMap的链表顺序。
- 值为true:以访问顺序维护链表。
- 值为false:以插入的顺序维护链表。
- 而LruCache利用是 accessOrder=true 时的LinkedHashMap实现LRU算法,使得最近访问的数据会在链表尾部,在容量溢出时,将链表头部的数据移除。
注:几种引用的含义
- 强引用:直接的对象引用,不会被gc回收;
- 软引用:当系统内存不足时,对象会被gc回收;
- 弱引用:随时会被gc回收;
(六)DiskLruCache
-
DiskLruCache:用于实现存储设备缓存,即磁盘缓存,通过将缓存对象写入文件系统从而实现缓存的效果。
-
DiskLruCache的使用步骤:
1. 通过DiskLruCache.open方法初始化创建对象。
2. 通过Editor添加、Snapshot获取、remove删除实现对数据的操作。
3. 调用 flush() 将数据写入磁盘。 -
第一步:DiskLruCache.open方法初始化创建对象
DiskLruCache mDiskLruCache = null;
protected void onCreate(Bundle savedInstanceState) {
...
File diskCacheDir = getDiskCacheDir(mContext, "bitmap");
if (!diskCacheDir.exists()) {
diskCacheDir.mkdirs(); //若缓存地址的路径不存在就创建一个
}
//创建对象
mDiskLruCache = DiskLruCache.open(diskCacheDir, 1, 1, DISK_CACHE_SIZE);
}
//用于获取到缓存地址的路径
public File getDiskCacheDir(Context context, String uniqueName) {
String cachePath;
if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) || !Environment.isExternalStorageRemovable()) {
//SD卡,获取路径 /sdcard/Android/data/<application package>/cache
cachePath = context.getExternalCacheDir().getPath();
} else {
//手机本地,获取路径/data/data/<application package>/cache
cachePath = context.getCacheDir().getPath();
}
return new File(cachePath + File.separator + uniqueName);
}
/**
* @param directory 表示磁盘缓存在文件系统中的存储路径:SD卡和手机
* @param appVersion 表示应用的版本号,一般设为1即可
* @param valueCount 表示单个节点对应的数据的个数,1即可
* @param maxSize 表示缓存的总大小,比如50MB,当缓存超出就会开始清理一部分
* @return
*/
public static DiskLruCacheDemo open(File directory, int appVersion, int valueCount, long maxSize){}
- 第二步:DiskLruCache.Editor方法缓存添加
- 第三步:DiskLruCache缓存查找
网友评论