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的加载和缓存策略相关推荐

  1. iOS网络加载图片缓存策略之ASIDownloadCache缓存优化

    iOS网络加载图片缓存策略之ASIDownloadCache缓存优化 在我们实际工程中,很多情况需要从网络上加载图片,然后将图片在imageview中显示出来,但每次都要从网络上请求,会严重影响用户体 ...

  2. Android平滑图片加载和缓存库 Glide 使用详解

    版权声明:本文原创作者:一叶飘舟 作者博客地址:http://blog.csdn.net/jdsjlzx 一.简介 在泰国举行的谷歌开发者论坛上,谷歌为我们介绍了一个名叫 Glide的图片加载库,作者 ...

  3. Android平滑图片加载和缓存库Glide使用详解

    在图片加载库烂大街的今天,选择一个适合自己使用的图片加载库已经成为了每一个Android开发者的必经之路.现在市面上知名的图片加载库有UIL,Picasso,Volley ImageLoader,Fr ...

  4. Bitmap的加载和Cache

    本章的主题是Bitmap的加载和Cache,主要包含三个方面的内容.首先讲述如何有效地加载一个Bitmap,这是一个很有意义的话题,由于Bitmap的特殊性以及Android对单个应用所施加的内存限制 ...

  5. 《Android开发艺术探索》第12章- Bitmap 的加载和 Cache 读书笔记

    目录 1. 前言 2. 正文 2.1 Bitmap 的高效加载 2.1.1 说一下对于Android 中的 Bitmap 的理解 2.1.2 内存中存储的 Bitmap 对象和本地图片有什么区别? 2 ...

  6. 《android开发艺术探索》笔记之Bitmap的加载和Cache

    <Android开发艺术探索>笔记之Bitmap的加载和Cache<一> 我放暑假前,就在图书馆借了一本<Android开发艺术探索>,这也是我看到很多人推荐的.之 ...

  7. Android开发-自己动手写Bitmap高效加载 跟OOM说再见

    Bitmap 前言 BitmapFactory.Options BitmapFactory 高效加载Bitmap inDensity,inTargetDensity,inScaled inSample ...

  8. Android 高清加载巨图方案 拒绝压缩图片

    Android 高清加载巨图方案 拒绝压缩图片 转载请标明出处:  http://blog.csdn.net/lmj623565791/article/details/49300989:  本文出自: ...

  9. Connor学Android - JNI和NDK编程

    Learn && Live 虚度年华浮萍于世,勤学善思至死不渝 前言 Hey,欢迎阅读Connor学Android系列,这个系列记录了我的Android原理知识学习.复盘过程,欢迎各位 ...

最新文章

  1. Cloud Native概念
  2. python编程入门课程视频-带学《Python编程:从入门到实践》
  3. 自定义简单版本python线程池
  4. MAX232和PL2303、CH340的区别
  5. 笔记(用Python做些事情)--变量(数字、字符串)
  6. 开箱即用Bumblebee独立部署搭建webapi网关详解
  7. ios 如何在cell中去掉_iOS开发:关于 去除UITableViewCell复用机制 的几种方法
  8. matlab学习笔记第七章——常微分方程(ODE)的数值解
  9. 这款 Android 图片选择库美哭了
  10. x86 android 显卡 tablet2,Re: android4.4.2将tablet更改为phone
  11. [Python ]个税计算
  12. Linux开机密码重置
  13. IPSec Over GRE配置实验
  14. openharmony标准系统移植之适配hdc功能
  15. 离散数学——哈斯图,最大最小值,极大极小值,上界和下界
  16. 渗透测试体系学习——学习寄语
  17. 女同学说我学会KALI她就做我女朋友:系列之liunx基础(一)
  18. 创新点定义,如何写创新点
  19. IOS打开Micosoft文档
  20. 用C语言程序实现十进制转换为二进制

热门文章

  1. Python web应用程序
  2. matlab狐狸与野兔数学模型,狐狸与兔子数学模型的论文
  3. jira新增、修改、关闭问题,“处理结果”错误
  4. linux 命令行 webcamera,如何在Linux上运行网络摄像头(Run a Webcam on Linux)?
  5. 文化课2021-2022游记
  6. 人类视觉的几种感知错觉现象
  7. 近段时间整理出的三款Android游戏源码
  8. QQ快捷登录需要在 AndroidManifest.xml 注册添加如下信息,sdk中提供开始配置少了回调的配置,报错
  9. STM32读取旋钮编码器
  10. New Era with AlphaGo