1.Bitmap 的高效加载
a.Bitmap(位图):指一张图片,常见格式:.png、.jpg 等
b. 必要性:直接加载大容量的高清 Bitmap 很容易出现显示不完整、内存溢出 OOM 的问题(如报错:
- java.lang.OutofMemoryError: bitmap size exceeds VM budget
c. 核心思想:按一定的采样率将图片缩小后再加载进来。
d. 工具类:
注:
- 对应着 BitmapFactory 类的几个 native 方法;
- decodeFile() 和 decodeResource() 又间接调用 decodeStream()。
的参数
- BitmapFactory.Options
注意:BitmapFactory 获取的图片宽高信息和图片的位置以及程序运行的设备有关,会导致获取到不同的结果。
e. 加载流程
参数设为 true 并加载图片。
- BitmapFactory.Options.inJustDecodeBounds
中取出图片的原始宽高信息,对应 outWidth 和 outHeight 参数。
- BitmapFactory.Options
参数设为 false,然后重新加载图片。
- BitmapFactory.Options.inJustDecodeBounds
常用的获取采样率的代码片段:
- /**
- * 对一个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 像素:
- mImageView.setImageBitmap(decodeSampledBitmapFromResource(getResources(), R.mipmap.ic_launcher, 100, 100);
推荐阅读: Android 开发之高效加载 Bitmap
2. 缓存策略
为减少流量消耗,可采用缓存策略。常用的缓存算法是 LRU(Least Recently Used):
- 核心思想:当缓存满时, 会优先淘汰那些近期最少使用的缓存对象。
- 两种方式:LruCache(内存缓存)、DiskLruCache(磁盘缓存)。
a.LruCache(内存缓存)
- public class LruCache<K, V> {
- private final LinkedHashMap<K, V> map;
- ...
注:几种引用的含义
- 强引用:直接的对象引用,不会被 gc 回收;
- 软引用:当系统内存不足时,对象会被 gc 回收;
- 弱引用:随时会被 gc 回收。
而 LruCache 利用是 accessOrder=true 、时的 LinkedHashMap 实现 LRU 算法,使得最近访问的数据会在链表尾部,在容量溢出时,将链表头部的数据移除。
实例:
- //初始化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 流处理。
(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 完成
得到一个输出流;
- Editor.newOutputStream(0)
核心代码:
- //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) 查找缓存操作:和缓存添加的过程类似
得到一个输入流(可向下转型为 FileInputStream);
- Snapshot.getInputStream(0)
核心代码:
- //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. 使用场景:
希望这篇文章对你有帮助~
来源: http://www.jianshu.com/p/aaafcd72c127