1.Bitmap的高效加载html
a.Bitmap(位图):指一张图片,常见格式:.png
、.jpg
等java
b.必要性:直接加载大容量的高清Bitmap很容易出现显示不完整、内存溢出OOM的问题(如报错:android
java.lang.OutofMemoryError:bitmap size exceeds VM budget
复制代码
c.核心思想:按必定的采样率将图片缩小后再加载进来。git
d.工具类:github
decodeFile()
:从文件系统加载出一个Bitmap对象decodeResource()
:从资源文件加载出一个Bitmap对象decodeStream()
:从输入流加载出一个Bitmap对象decodeByteArray()
:从字节数组加载出一个Bitmap对象注:算法
- 对应着BitmapFactory类的几个native方法;
decodeFile()
和decodeResource()
又间接调用decodeStream()
。
BitmapFactory.Options
的参数
注意:BitmapFactory获取的图片宽高信息和图片的位置以及程序运行的设备有关,会致使获取到不一样的结果。数组
e.加载流程缓存
BitmapFactory.Options.inJustDecodeBounds
参数设为true并加载图片。BitmapFactory.Options
中取出图片的原始宽高信息,对应outWidth和outHeight参数。BitmapFactory.Options.inJustDecodeBounds
参数设为false,而后从新加载图片。经常使用的获取采样率的代码片断:安全
/**
* 对一个Resources的资源文件进行指定长宽来加载进内存, 并把这个bitmap对象返回
*
* @param res 资源文件对象
* @param resId 要操做的图片id
* @param reqWidth 最终想要获得bitmap的宽度
* @param reqHeight 最终想要获得bitmap的高度
* @return 返回采样以后的bitmap对象
*/
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight){
BitmapFactory.Options options = new BitmapFactory.Options();
//1.设置inJustDecodeBounds=true获取图片尺寸
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res,resId,options);
//3.计算缩放比
options.inSampleSize = calculateInSampleSize(options,reqHeight,reqWidth);
//4.再设为false,从新从资源文件中加载图片
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 reqHeight, int reqWidth) {
//2.height、width为图片的原始宽高
int height = options.outHeight;
int width = options.outWidth;
int inSampleSize = 1;
if(height>reqHeight||width>reqWidth){
int halfHeight = height/2;
int halfWidth = width/2;
//计算缩放比,是2的指数
while((halfHeight/inSampleSize)>=reqHeight&&(halfWidth/inSampleSize)>=reqWidth){
inSampleSize*=2;
}
}
return inSampleSize;
}
复制代码
如今假设ImageView指望图片大小是为100*100像素:bash
mImageView.setImageBitmap(decodeSampledBitmapFromResource(getResources(),R.mipmap.ic_launcher,100,100);
复制代码
推荐阅读:Android开发之高效加载Bitmap
2.缓存策略
为减小流量消耗,可采用缓存策略。经常使用的缓存算法是LRU(Least Recently Used):
- 核心思想:当缓存满时, 会优先淘汰那些近期最少使用的缓存对象。
- 两种方式:LruCache(内存缓存)、DiskLruCache(磁盘缓存)。
a.LruCache(内存缓存)
LinkedHashMap
以强引用的方式存储外界的缓存对象,并提供get
和put
方法来完成缓存的获取和添加操做,当缓存满时会移除较早使用的缓存对象,再添加新的缓存对象。public class LruCache<K, V> {
private final LinkedHashMap<K, V> map;
...
复制代码
注:几种引用的含义
- 强引用:直接的对象引用,不会被gc回收;
- 软引用:当系统内存不足时,对象会被gc回收;
- 弱引用:随时会被gc回收。
LinkedHashMap
利用一个双重连接链表来维护全部条目item。
而LruCache利用是
accessOrder=true
时的LinkedHashMap实现LRU算法,使得最近访问的数据会在链表尾部,在容量溢出时,将链表头部的数据移除。
sizeOf()
用于计算每一个缓存对象的大小;实例:
//初始化LruCache对象
public void initLruCache()
{
//1.获取当前进程的可用内存,转换成KB单位
int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
//2.分配缓存的大小
int maxSize = maxMemory / 8;
//3.建立LruCache对象并重写sizeOf方法
lruCache = new LruCache<String, Bitmap>(maxSize)
{
@Override
protected int sizeOf(String key, Bitmap value) {
// TODO Auto-generated method stub
return value.getWidth() * value.getHeight() / 1024;
}
};
}
//4.LruCache对数据的操做
public void fun()
{
//添加数据
lruCache.put("lizhuo", bm1);
lruCache.put("sushe", bm2);
lruCache.put("jiqian", bm3);
//获取数据
Bitmap b1 = (lruCache.get("lizhuo"));
Bitmap b2 = (lruCache.get("sushe"));
Bitmap b3 = (lruCache.get("jiqian"));
//删除数据
lruCache.remove("sushe");
}
复制代码
推荐阅读:详细解读LruCache类、LruCache 源码解析
b.DiskLruCache(磁盘缓存)
与LruCache区别:DiskLruCache非泛型类,不能添加类型,而是采用文件存储,存储和读取经过I/O流处理。
open()
方法;flush()
将数据写入磁盘。(1)先来介绍DiskLruCache的建立:
public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize)
复制代码
其中,参数含义:
①directory:磁盘缓存的存储路径。有两种目录:
/sdcard/Android/data/package_name/cache
目录,当应用被卸载后会被删除。②appVersion:当前应用的版本号,通常设为1。
③valueCount:单个节点所对应的数据的个数,通常设为1。
④maxSize:缓存的总大小,超出这个设定值后DiskLruCache会清除一些缓存
例如,典型的建立过程:
DiskLruCache mDiskLruCache = null;
try {
File cacheDir = getDiskCacheDir(context, "bitmap");
if (!cacheDir.exists()) {
//若缓存地址的路径不存在就建立一个
cacheDir.mkdirs();
}
mDiskLruCache = DiskLruCache.open(cacheDir, getAppVersion(context), 1, 10 * 1024 * 1024);
} catch (IOException e) {
e.printStackTrace();
}
//用于获取到缓存地址的路径
public File getDiskCacheDir(Context context, String uniqueName) {
String cachePath;
if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())|| !Environment.isExternalStorageRemovable()) {
//当SD卡存在或者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);
}
//用于获取到当前应用程序的版本号
public int getAppVersion(Context context) {
try {
PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
return info.versionCode;
} catch (NameNotFoundException e) {
e.printStackTrace();
}
return 1;
}
复制代码
(2)添加缓存操做:经过Editor完成
DiskLruCache.edit()
获取对应key的Editor;Editor.newOutputStream(0)
获得一个输出流;Editor.commit()
提交写操做,若发生异常,则调用Editor.abort()
进行回退。核心代码:
//1.返回url的MD5算法结果
String key = hashKeyFormUrl(url);
//2.获取Editor对象
Editor editor = mDiskLruCache.edit(key);
//3.建立输出流,其中常量DISK_CACHE_INDEX = 0
OutputStream outputStream = editor.newOutputStream(DISK_CACHE_INDEX);
//4.写入数据
outputStream.wirte(data);
//5.提交写操做
editor.commit();
复制代码
(3)查找缓存操做:和缓存添加的过程相似
DiskLruCache.get()
获取对应key的Snapshot对象;Snapshot.getInputStream(0)
获得一个输入流(可向下转型为FileInputStream);核心代码:
//1.返回url的MD5算法结果
String key = hashKeyFormUrl(url);
//2.获取Snapshot对象
Snapshot snapshot = mDiskLruCache.get(key);
//3.建立输入流,其中常量DISK_CACHE_INDEX = 0
InputStream inputStream = snapshot.getInputStream(DISK_CACHE_INDEX);
//4.读出数据
int data = inputStream.read();
复制代码
- 问题:FileInputStream是一种有序的文件流,调用两次
BitmapFactory.decodeStream()
会影响文件流的位置属性,致使第二次解析结果为空。- 解决办法:经过文件流获得其对应的文件描述符,再调用
BitmapFactory.decodeFileDescriptor()
来加载一张缩放后的图片。
推荐阅读:Android DiskLruCache彻底解析、 源码解析
3.ImageLoader 的使用
a.ImageLoader内部封装了Bitmap的高效加载、LruCache和DiskLruCache。
b.应具有功能:
更多了解:Android 开源框架Universal-Image-Loader彻底解析、开源框架ImageLoader的完美例子
c.使用场景:
但愿这篇文章对你有帮助~