Android Bitmap的加载优化与Cache相关介绍

一 . 高效加载 Bitmap

BitMapFactory 提供了四类方法: decodeFile,decodeResource,decodeStream 和 decodeByteArray 分别用于从文件系统,资源,输入流以及字节数组中加载出一个 Bitmap 对象。

高效加载 Bitmap 很简单,即采用 BitMapFactory.options 来加载所需要尺寸图片。BitMapFactory.options 就可以按照一定的采样率来加载缩小后的图片,将缩小后的图片置于 ImageView 中显示。

通过采样率即可高效的加载图片,遵循如下方式获取采样率:

将 BitmapFactory.Options 的 inJustDecodeBounds 参数设置为 true 并加载图片 从 BitmapFactory.Options 中取出图片的原始宽高信息,即对应于 outWidth 和 outHeight 参数 根据采样率的规则并结合目标 View 的所需大小计算出采样率 inSampleSize 将 BitmapFactory.Options 的 injustDecodeBounds 参数设置为 false,然后重新加载图片

过上述四个步骤,加载出的图片就是最终缩放后的图片,当然也有可能没有缩放。

代码实现如下:

public Bitmap decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight) { // First decode with inJustDecodeBounds=true to check dimensions final BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeResource(res, resId, options); // Calculate inSampleSize options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); // Decode bitmap with inSampleSize set options.inJustDecodeBounds = false; return BitmapFactory.decodeResource(res, resId, options); } public int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) { if (reqWidth == 0 || reqHeight == 0) { return 1; } // Raw height and width of image final int height = options.outHeight; final int width = options.outWidth; Log.d(TAG, "origin, w= " + width + " h=" + height); int inSampleSize = 1; if (height > reqHeight || width > reqWidth) { final int halfHeight = height / 2; final int halfWidth = width / 2; // Calculate the largest inSampleSize value that is a power of 2 and // keeps both height and width larger than the requested height and width. while ((halfHeight / inSampleSize) >= reqHeight && (halfWidth / inSampleSize) >= reqWidth) { inSampleSize *= 2; } } Log.d(TAG, "sampleSize:" + inSampleSize); return inSampleSize; }

实际使用就可以像下面这样了,如加载 100*100 的图片大小,就可以像下面这样高效的加载图片了:

mImageView.setImageBitmap( decodeSampledBitmapFromResource(getResource(),R.id.myimage,100,100));

二 . Android 中的缓存策略

目前常用的算法是 LRU,即近期最少使用算法,当缓存存满时,会优先淘汰近期最少使用的缓存对象

2.1 LruCache

LruCache 是一个泛型类,其内部实现机制是 LinkedHashMap 以强引用的方式存储外部的缓存对象,提供了 get() 和 put() 来完成缓存对象的存取。当缓存满了,移除较早的缓存对象,再添加新的。LruCache 是线程安全的。

强引用:直接的对象引用 软引用:当一个对象只有软引用时,系统内存不足时,会被 gc 回收 弱引用:当一个对象只有弱引用时,随时会被回收

2.2 DiskLriCache

DiskLruCache 用于实现存储设备缓存,即磁盘缓存。

2.2.1 DiskLruCache 的创建

由于它不属于 Android SDK的一部分,所以不能通过构造方法来创建,提供了 open() 方法用于自身的创建

public static DiskLruCache open(File directory,int appversion,int valueCount,long maxSize);

典型的 DiskLruCache 的创建过程

private static final Disk_CACHE_SIZE = 1024*1024*50;//50M File diskCaCheDir = getDiskCacheDir(mContext,"bitmap"); if(!diskCacheDir.exists()){ diskCacheDir.mkdirs(); } mDiskLruCache = DiskLruCache.open(diskCaCheDir,1,1,Disk_CACHE_SIZE);

第三个参数表示单个节点所对应的数据,一般设置为1即可。

2.2.2 DiskLruCache 的缓存添加 缓存的添加操作是通过 Editor 完成的, Editor 表示一个缓存对象的编辑对象。DiskLruCache 不允许同时编辑一个缓存对象。

2.2.3 DiskLruCache 的缓存查找

缓存查找过程也需要将 url 转换为 key,通过 DiskLruCache 的 get() 得到一个 Snapshot 对象,然后通过该对象即可得到缓存的文件输入流,得到文件输入流即可得到 Bitmap 对象了。为了避免加载过程中 OOM,一般不会直接加载原始图片。在前面介绍通过 BitmapFactory.Options 来加载一张缩放后的图片,但是那种方法对 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(); // 通过 BitmapFactory.decodeFileDescriptor 来加载一张缩放后的图片 bitmap = mImageResizer.decodeSampledBitmapFromFileDescriptor(fileDescriptor, reqWidth, reqHeight); if (bitmap != null) { addBitmapToMemoryCache(key, bitmap); } } return bitmap; }

三 . ImageLoader 的实现

具备的功能,即图片的同步加载,异步加载,图片的压缩,内存缓存,磁盘缓存以及网络拉取。

3.1 图片压缩功能

如前面所述。

3.2 内存缓存和磁盘缓存的实现

选择 LruCache 和 DiskLruCache 来分别完成内存缓存和磁盘缓存的工作

3.3 同步加载和异步加载的接口设计

关于同步加载:从 loadBitmap 的实现可以看出,其工作过程遵循如下几个步骤:先试着从内存缓存中读取图片,接着从磁盘缓存中读取图片,最后试着从网络拉取图片。另外该方法不能在主线程中调用,否则就会抛出异常。因为加载图片是一个耗时的操作。

关于异步加载:从 bindBitmap 中可以看出,binfBitmap 会先试着从内存缓存中读取结果,如果成功就直接返回,否则会从线程池中去调用 loadBitmap() ,当加载成功后,再讲图片,图片地址以及需要绑定的 ImageView 封装成一个 loaderResult 对象,通过 mMainHandler 向主线程发送一个消息,这样就可以在主线程中给 ImageView 设置图片了。图片的异步加载是一个很有用的功能,很多时候调用者不想在单独的线程中以同步的方式来加载图片,并将图片设置给需要的 ImageVIew, 从而ImageLoader 内部需要自己需要在内部线程中加载图片,并且将图片设置给所需要的 ImageView。

ImageLoader源码可以点击这里:下载 查看ImageLoader的实现

四 . ImageLoader 的使用

核心是 ImageAdapter , 其中的 getView() 的核心方法如下:

@Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder holder = null; if (convertView == null) { convertView = mInflater.inflate(R.layout.image_list_item,parent, false); holder = new ViewHolder(); holder.imageView = (ImageView) convertView.findViewById(R.id.image); convertView.setTag(holder); } else { holder = (ViewHolder) convertView.getTag(); } ImageView imageView = holder.imageView; final String tag = (String)imageView.getTag(); final String uri = getItem(position); if (!uri.equals(tag)) { imageView.setImageDrawable(mDefaultBitmapDrawable); } if (mIsGridViewIdle && mCanGetBitmapFromNetWork) { imageView.setTag(uri); // 这句话将图片的复杂加载过程交给 ImageLoader 了 mImageLoader.bindBitmap(uri, imageView, mImageWidth, mImageWidth); } return convertView; }

对于上述代码 ImageAdapter 来说, ImageLoader 的加载图片的复杂过程,更不需要知道。

优化列表卡顿现象:

不要在 getView() 中做加载图片的操作,那样肯定会耗时,像这个例子中一样,交给 ImageLoaer 来实现。 控制异步加载频率, 如果用户刻意的频繁的上下滑动,可能在一瞬间加载几百个异步任务,这样会给线程池造成拥堵。解决的办法是考虑在用户滑动列表时,停止加载图片。等到列表停下来时,在进行异步加载任务。 开启硬件加速:给Activity添加配置android:hardwareAccelerated=”true”

总结

以上就是这篇文章的全部内容了,希望本文的内容对给我Android开发者们能带来一定的帮助,如果有疑问大家可以留言交流。

时间: 2024-10-25 06:15:26

Android Bitmap的加载优化与Cache相关介绍的相关文章

Android中如何加载数据缓存_Android

最近app快完工了,但是很多列表加载,新闻咨询等数据一直从网络请求,速度很慢,影响用户体验,所以寻思用缓存来加载一些更新要求不太高的数据 首先做一个保存缓存的工具类 import java.io.File; import java.io.IOException; import android.content.Context; import android.os.Environment; import android.util.Log; /** * 缓存工具类 */ public class Co

js资源加载优化

互联网应用或者访问量大的应用,对js的加载优化是不可少的.下面记录几种优化方法 CDN  + 浏览器缓存 CDN(content delivery network)内容分发网络, 最传统的优化方式.其实就是将自己页面所依赖的js(静态的)放置到CDN上,或者使用一些CDN库,以此降低对应用服务器的请求,而浏览器缓存也是不重复加载js文件的性质. 优点: 1.简单.容易维护 2.304 cache 简单来说就是转掉请求,缓存不重加载. 缺点: 1.缓存会失效,当用户强制刷新时会有请求 2.无法增量

布局-Android imageloade异步加载图片的,怎么知道图片是否加载完成?

问题描述 Android imageloade异步加载图片的,怎么知道图片是否加载完成? -image-loade 用了这个异步加载图片包,怎么知道图片加载完成? 想当加载完成,再显示layout,而不是显示一张图片.. 解决方案 imageloader有回调函数的. ImageLoader.getInstance().displayImage(uri, imageView, new ImageLoadingListener() { @Override public void onLoading

Android开发之加载图片的方法_Android

本文实例讲述了Android开发之加载图片的方法.分享给大家供大家参考.具体分析如下: 加载网络上的图片需要在manifest中配置访问网络的权限,如下: <uses-permission android:name="android.permission.INTERNET" /> 如果不配置这个权限的话,会报错:unknown host exception. package com.example.loadimgfromweb; import java.io.InputSt

Android从xml加载到View对象过程解析_Android

我们从Activity的setContentView()入手,开始源码解析, //Activity.setContentView public void setContentView(int layoutResID) { getWindow().setContentView(layoutResID); initActionBar(); } //PhoneWindow.setContentView public void setContentView(int layoutResID) { if (

Android开发之加载图片的方法

本文实例讲述了Android开发之加载图片的方法.分享给大家供大家参考.具体分析如下: 加载网络上的图片需要在manifest中配置访问网络的权限,如下: <uses-permission android:name="android.permission.INTERNET" /> 如果不配置这个权限的话,会报错:unknown host exception. package com.example.loadimgfromweb; import java.io.InputSt

Android从xml加载到View对象过程解析

我们从Activity的setContentView()入手,开始源码解析, //Activity.setContentView public void setContentView(int layoutResID) { getWindow().setContentView(layoutResID); initActionBar(); } //PhoneWindow.setContentView public void setContentView(int layoutResID) { if (

Android中如何加载数据缓存

最近app快完工了,但是很多列表加载,新闻咨询等数据一直从网络请求,速度很慢,影响用户体验,所以寻思用缓存来加载一些更新要求不太高的数据 首先做一个保存缓存的工具类 import java.io.File; import java.io.IOException; import android.content.Context; import android.os.Environment; import android.util.Log; /** * 缓存工具类 */ public class Co

Android实现网络加载时的对话框功能

效果预览 简要说明 现在android程序网络请求操作是必不可少的,然而拥有好的交互体验的程序对网络耗时操作的处理尤为重要. 代码说明: dialog_loading.xml <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="