Connor学Android - Bitmap的加载和缓存策略
Learn && Live
虚度年华浮萍于世,勤学善思至死不渝
前言
Hey,欢迎阅读Connor学Android系列,这个系列记录了我的Android原理知识学习、复盘过程,欢迎各位大佬阅读斧正!原创不易,转载请注明出处:http://t.csdn.cn/n6ljR,话不多说我们马上开始!
1.Bitmap 的高效加载
1.1 Bitmap 的加载
(1)Bitmap 在 Android 中指的是一张图片,可以是 png 格式也可以是 jpg 等其他常见的图片格式
(2)Bitmap 的加载可以通过 BitmapFactory 完成,这个类提供了四类方法
decodeFile:从文件系统中加载出一个 Bitmap 对象
decodeResource:从资源中加载出一个 Bitmap 对象
decodeStream:从输入流中加载出一个 Bitmap 对象
decodeByteArray:从字节数组中加载出一个 Bitmap 对象
其中 decodeFile 和 decodeResource 会间接调用 decodeStream 方法
(3)BitmapFactory 中的这四类方法最终是在 Android 的底层实现的,对应 BitmapFactory 类的几个 native 方法
1.2 Bitmap 的高效加载
(1)由于 Bitmap 的特殊性以及 Android 对应用的内存限制(16MB),导致加载 Bitmap 时很容易出现 OOM,因此需要更高效地加载
(2)Bitmap 高效加载的核心思想就是采用 BitmapFactory.Options 按一定的采样率来加载缩小后的图片,将缩小后的图片显示在页面上,这样就可以通过降低图片的内存占用,从而一定程度上避免了 OOM
(3)BitmapFactory 提供的四类方法都支持 BitmapFactory.Options 参数,通过它们就可以很方便地对一个图片进行采样缩放
(4)BitmapFactory.Options 缩放图片主要用到了它的 inSampleSize 参数,即采样率
- inSampleSize <= 1,采样后的图片大小为图片的原始大小
- inSampleSize > 1,缩放图片,缩放比例为 1 / (inSampleSize ^ 2)
- inSampleSize 推荐为 2 的整数次方,否则会向下取整并选择一个最接近的 2 的整数次方
(5)通过采样率即可有效地加载图片,那应如何获取采样率呢?过程如下
- 将 BitmapFactory.Options 的 inJustDecodeBounds 参数设为 true 并加载图片,这里说明一下这个参数
- 此参数为 true,BitmapFactory 只会解析图片的原始宽、高及 MIME 类型信息,并不会真正地加载图片,是轻量级的操作
- 从 BitmapFactory.Options 中取出图片的原始宽高信息,对应 outWidth 和 outHeight 参数
- 注意此时获取的图片宽高信息和图片的位置(不同的 drawable 目录下)以及硬件设备(屏幕密度不同)有关
- 根据采样率的规则并结合目标 View 的所需大小计算出采样率 inSampleSize
- 将 BitmapFactory.Options 的 inJustDecodeBounds 参数设为 false 并重新加载图片,此时就是真正加载缩小后的图片了
2.Android 中的缓存策略
2.1 基本介绍
缓存
Android 的缓存可用于避免过多的流量消耗,一般设计为三级的缓存机制
(1)当程序第一次从网络加载图片后,就将其缓存到存储设备上,这样下次使用这张图片就不需要再从网上获取了
(2)很多时候还会在内存中再缓存一份
(3)这样当应用要请求一张图片时,会首先从内存中获取,如果没有再从存储设备中获取,如果还没有就从网络上下载
缓存策略
(1)缓存策略主要包含缓存的添加、获取和删除,当缓存容量满了,则需要删除一些旧缓存来添加新缓存
(2)如何定义缓存的新旧就是一种缓存的策略,不同的策略对应不同的缓存算法
(3)常用的缓存算法是 LRU,最近最少使用算法,即优先淘汰最近最少使用的缓存对象,采用该算法的缓存主要有两种
- LruCache:用于实现内存缓存
- DiskLruCache:用于实现存储设备缓存
2.2 LruCache
(1)LruCache 是一个泛型类,内部采用一个 LinkedHashMap 以强引用的方式存储外界的缓存对象
public class LruCache<K, V> {@UnsupportedAppUsageprivate final LinkedHashMap<K, V> map;/** Size of this cache in units. Not necessarily the number of elements. */private int size;private int maxSize;...
}
(2)创建LruCache时只需要提供缓存的总容量大小并重写 sizeOf 方法即可
- 容量一般根据当前线程的可用内存来设置,单位是 KB:Runtime.getRuntime().maxMemory()
- sizeOf 方法用于计算缓存对象,即 Bitmap 对象的大小
- 此外,一些特殊情况下,还需要重写 entryRemoved 方法,当移除旧缓存时会调用该方法,可以完成一些资源回收工作
(3)通过 get、set 方法完成缓存的获取和添加操作
(4)还支持删除操作,可通过 remove 方法删除一个指定的缓存对象
(5)当缓存满时,会移除较早使用的缓存对象,然后再添加新的缓存对象
2.3 DiskLruCache
DiskLruCache 用于实现存储设备缓存,即磁盘缓存,通过将缓存对象写入到文件系统中实现缓存效果
创建
DiskLruCache 不能通过构造方法来创建,需要通过 open 方法来完成,open 方法共有四个参数
(1)第一个参数表示磁盘缓存在文件系统中的存储路径,可以选择 SD 卡上的缓存目录,也可以选择 SD 卡上的其他指定目录
- 如果应用卸载后就删除缓存文件,那么就选择 SD 卡上的缓存目录
- 如果希望保留缓存数据就应该选择 SD 卡上的其他指定目录
(2)第二个参数表示应用的版本号,一般设为1即可。当版本号发生改变时会清空之前所有的缓存文件,但在实际开发中并非那么有效
(3)第三个参数表示单个节点所对应的数据的个数,一般设为1即可
(4)第四个参数表示缓存的总大小,单位是 B,超出这个值时会清楚一些缓存来保证总大小不大于这个设定值
private static final long DISK_CACHE_SIZE = 1024 * 1024 * 50;File diskCacheDir = getDiskCacheDir(mContext, "bitmap");
if(!diskCacheDir.exists()) {diskCacheDir.mkdirs();
}
mDiskLruCache = DiskLruCache.open(diskCacheDir, 1, 1, DISK_CACHE_SIZE);
添加
缓存添加的操作是通过 Editor 完成的,Editor 表示一个缓存对象的编辑对象,添加步骤如下
(1)首先需要获取图片 url 对应的 key
- 这里使用 key 而非直接使用 url 是因为 url 中很可能有特殊字符,会影响它 Android 中的直接使用,一般采用 url 的 md5 值作为 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();
}
(2)根据 key 通过 edit 方法获取 Editor 对象,获取到后即可根据它来得到一个文件输出流
- 如果当前不存在其他 Editor 对象,则 edit 方法会返回一个新的 Editor 对象
- 如果这个缓存正在被编辑,则 edit 方法会返回 null,即 DiskLruCache 不允许同时编辑一个缓存对象
String key = hashKeyFormUrl(url);
DiskLruCache.Editor editor = mDiskLruCache.edit(key);
if (editor != null) {OutputStream outputstream = editor.newOutputStream(DISK_CACHE_INDEX);
}
(3)当我们从网络下载图片时,就可以通过拿到的文件输出流写入到文件系统上了
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) {...} finally {if (urlConnection != null) {urlConnection.disconnect();}MyUtils.close(out);MyUtils.close(in);}return false;
}
(4)此时还没有真正地将图片写入文件系统,还必须调用 Editor 的 commit 方法来提交写入操作,如果图片下载过程中发生了异常,还可以通过 Editor 的 abort 方法来回退整个操作
OutputStream outputStream = editor.newOutputStream(DISK_CACHE_INDEX);
if (downloadUrlToStream(url, outputStream)) {editor.commit();
} else {editor.abort();
}
mDiskLruCache.flush();
查找
和缓存的添加过程类似
(1)将 url 转换为 key
(2)根绝 key 调用 DiskLruCache 的 get 方法获取一个 Snapshot 对象
(3)根据 Snapshot 对象即可得到缓存的文件输入流,进而得到 Bitmap 对象
(4)一般不建议直接加载原始图片,而是加载缩放图片
- 但之前介绍的缩放方法会对 FileInputStream 的缩放存在问题,因为 FileInputStream 是有序的文件流,而两次 decodeStream 调用影响了文件流的位置属性,会导致第二次 decodeStream 时得到的是 null
- 可以通过文件流来获得其对应的文件描述符,然后再通过 BitmapFactory.decodeFileDescriptor 方法来加载一张缩放后的图片
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);}
}
删除
可通过 remove、delete 方法完成对磁盘缓存的删除操作
Connor学Android - Bitmap的加载和缓存策略相关推荐
- iOS网络加载图片缓存策略之ASIDownloadCache缓存优化
iOS网络加载图片缓存策略之ASIDownloadCache缓存优化 在我们实际工程中,很多情况需要从网络上加载图片,然后将图片在imageview中显示出来,但每次都要从网络上请求,会严重影响用户体 ...
- Android平滑图片加载和缓存库 Glide 使用详解
版权声明:本文原创作者:一叶飘舟 作者博客地址:http://blog.csdn.net/jdsjlzx 一.简介 在泰国举行的谷歌开发者论坛上,谷歌为我们介绍了一个名叫 Glide的图片加载库,作者 ...
- Android平滑图片加载和缓存库Glide使用详解
在图片加载库烂大街的今天,选择一个适合自己使用的图片加载库已经成为了每一个Android开发者的必经之路.现在市面上知名的图片加载库有UIL,Picasso,Volley ImageLoader,Fr ...
- Bitmap的加载和Cache
本章的主题是Bitmap的加载和Cache,主要包含三个方面的内容.首先讲述如何有效地加载一个Bitmap,这是一个很有意义的话题,由于Bitmap的特殊性以及Android对单个应用所施加的内存限制 ...
- 《Android开发艺术探索》第12章- Bitmap 的加载和 Cache 读书笔记
目录 1. 前言 2. 正文 2.1 Bitmap 的高效加载 2.1.1 说一下对于Android 中的 Bitmap 的理解 2.1.2 内存中存储的 Bitmap 对象和本地图片有什么区别? 2 ...
- 《android开发艺术探索》笔记之Bitmap的加载和Cache
<Android开发艺术探索>笔记之Bitmap的加载和Cache<一> 我放暑假前,就在图书馆借了一本<Android开发艺术探索>,这也是我看到很多人推荐的.之 ...
- Android开发-自己动手写Bitmap高效加载 跟OOM说再见
Bitmap 前言 BitmapFactory.Options BitmapFactory 高效加载Bitmap inDensity,inTargetDensity,inScaled inSample ...
- Android 高清加载巨图方案 拒绝压缩图片
Android 高清加载巨图方案 拒绝压缩图片 转载请标明出处: http://blog.csdn.net/lmj623565791/article/details/49300989: 本文出自: ...
- Connor学Android - JNI和NDK编程
Learn && Live 虚度年华浮萍于世,勤学善思至死不渝 前言 Hey,欢迎阅读Connor学Android系列,这个系列记录了我的Android原理知识学习.复盘过程,欢迎各位 ...
最新文章
- Cloud Native概念
- python编程入门课程视频-带学《Python编程:从入门到实践》
- 自定义简单版本python线程池
- MAX232和PL2303、CH340的区别
- 笔记(用Python做些事情)--变量(数字、字符串)
- 开箱即用Bumblebee独立部署搭建webapi网关详解
- ios 如何在cell中去掉_iOS开发:关于 去除UITableViewCell复用机制 的几种方法
- matlab学习笔记第七章——常微分方程(ODE)的数值解
- 这款 Android 图片选择库美哭了
- x86 android 显卡 tablet2,Re: android4.4.2将tablet更改为phone
- [Python ]个税计算
- Linux开机密码重置
- IPSec Over GRE配置实验
- openharmony标准系统移植之适配hdc功能
- 离散数学——哈斯图,最大最小值,极大极小值,上界和下界
- 渗透测试体系学习——学习寄语
- 女同学说我学会KALI她就做我女朋友:系列之liunx基础(一)
- 创新点定义,如何写创新点
- IOS打开Micosoft文档
- 用C语言程序实现十进制转换为二进制
热门文章
- Python web应用程序
- matlab狐狸与野兔数学模型,狐狸与兔子数学模型的论文
- jira新增、修改、关闭问题,“处理结果”错误
- linux 命令行 webcamera,如何在Linux上运行网络摄像头(Run a Webcam on Linux)?
- 文化课2021-2022游记
- 人类视觉的几种感知错觉现象
- 近段时间整理出的三款Android游戏源码
- QQ快捷登录需要在 AndroidManifest.xml 注册添加如下信息,sdk中提供开始配置少了回调的配置,报错
- STM32读取旋钮编码器
- New Era with AlphaGo