原文链接http://blog.csdn.net/guolin_blog/article/details/9316683
一.高效加载大图
1.查看程序可用内存大小
int maxMemory = (int)(Runtime.getRuntime().maxMemory()/1024);
Log.d("TAG","Max memory is "+maxmoory+"KB");
因此在展示高分辨率图片的时候,最好先将图片进行压缩。压缩后的图片大小应该和用来展示它的控件大小相近,在一个很小的ImageView上显示一张超大的图片不会带来任何视觉上的好处,但却会占用我们相当多宝贵的内存,而且在性能上还可能会带来负面影响。
每一种解析方法都提供了一个可选的BitmapFactory.Options参数,将这个参数的inJustDecodeBounds属性设置为true就可以让解析方法禁止为bitmap分配内存,返回值也不再是一个Bitmap对象,而是null。虽然Bitmap是null了,但是BitmapFactory.Options的outWidth、outHeight和outMimeType属性都会被赋值。这个技巧让我们可以在加载图片之前就获取到图片的长宽值和MIME类型,从而根据情况对图片进行压缩。如下代码所示:
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;//禁止为Bitmap分配内存
BitmapFactory.decodeResource(getResources(),R.id.myimage,options);
int imageHeihht = options.outHeight;
int imageWidth = options.outWidth;
String imageType = options.outMimeType;
加载图片前考虑是完整显示图片还是要压缩后再显示,就需要考虑以下因素:
- 预估整张图片占用的内存
- 为了加在一张图片,你愿意提供多少内存
- 用于展示的图片控件的实际大小
- 当前设备的屏幕尺寸和分辨率
对图片压缩需要使用BitmapFactory.Options中的inSampleSize(例如:2048 * 1536像素的图片,inSampleSize= 4,图片被压缩成 512 * 384 所占的内存大小为512 * 384 *4 = 0.75M (假设图片是ARGB_8888类型,即每个像素点占用4个字节)
下面的方法可以根据宽高计算出合适的inSampleSize值:
public static int calculateInSampleSize(BitmapFactory.Options options,int reqWidth,int reqHeight){
//源图片的高度和宽度
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSaze= 1;
if(height>reqHeight||width>reqWidth){
//计算实际宽高和目标宽高的比率
final int heightRatio = Math.round((float)height/(float)reqHeight);
final int widthRatio = Math.round((float)height/(float)reqHeight);
//选择宽高比例较小的作为InSampleSize的值,这样保证最终图片的宽高是大于目标的宽高
inSampleSize = heightRatio>widthRatio?widthRatio:heightRatio;
}
return inSampleSize;
}
获取到inSampleSize值以后再把inJustDecodeBounds设置为false,就可以使用压缩后的图片了
public static Bitmap decodeSampleBitmapFromResource(Resources res,int resId,int reqWidth,int reqHeight){
//第一次设置inJustDecodeBounds为true,来获取图片大小
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res,resId,options);
//调用方法计算inSampleSize
options.inSampleSize = calculateInSampleSize(options,reqWidth,reqHeight);
//使用insamplesize在此解析图片
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res,resId,options);
}
下面的代码非常简单的将任意一张图片设置压缩成100*100的缩略图,并显示在ImageView上
mImageView.setImageBitmap(decodeSampleBitmapFromResource(getResource(),R.id.myimage,100,100));
二.使用图片缓存技术
防止频繁的显示多张图片 以及回收过的图片再次显示导致大量的加载而引起OOM
内存缓存技术对那些大量占用应用程序宝贵内存的图片提供了快速访问的方法。其中最核心的类是LruCache (此类在android-support-v4的包中提供) 。这个类非常适合用来缓存图片,它的主要算法原理是==把最近使用的对象用强引用存储在 LinkedHashMap 中,并且把最近最少使用的对象在缓存值达到预设定值之前从内存中移除。==
为了能够选择一个合适的缓存大小给LruCache, 有以下多个因素应该放入考虑范围内,例如:
- 你的设备可以为每个应用程序分配多大的内存?
- 设备屏幕上一次最多能显示多少张图片?有多少图片需要进行预加载,因为有可能很快也会显示在屏幕上?
- 你的设备的屏幕大小和分辨率分别是多少?一个超高分辨率的设备(例如 Galaxy Nexus) 比起一个较低分辨率的设备(例如 Nexus S),在持有相同数量图片的时候,需要更大的缓存空间。
- 图片的尺寸和大小,还有每张图片会占据多少内存空间。
- 图片被访问的频率有多高?会不会有一些图片的访问频率比其它图片要高?如果有的话,你也许应该让一些图片常驻在内存当中,或者使用多个LruCache 对象来区分不同组的图片。
- 你能维持好数量和质量之间的平衡吗?有些时候,存储多个低像素的图片,而在后台去开线程加载高像素的图片会更加的有效
下面是一个使用 LruCache 来缓存图片的例子:
private LruCache<String,Bitmap>mMemoryChahe;
@Override
protected void onCreate(Bundle savedInstanceState){
/ 获取到可用内存的最大值,使用内存超出这个值会引起OutOfMemory异常。
// LruCache通过构造函数传入缓存值,以KB为单位。
int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
// 使用最大可用内存值的1/8作为缓存的大小。
int cacheSize = maxMemory / 8;
mMemoryCache = new LruCache<String,Bitmap>(cacheSize){
@Override
protect int sizeOf(String key,Bitmap bitmap){
//重写此方法来衡量每张图片的大小,默认返回图片数量.
return bitmap.getByteCount()/1024;
}
};
}
public void addBitmapToMemoryCache(String key,Bitmap bitmap){
if(getBitmapFromMemCache(key)==null){
mMemoryCache.put(key,bitmap);
}
}
public Bitmap getBitmapFromMemoryCache(String key){
return mMemoryCache.get(key);
}
在这个例子当中,使用了系统分配给应用程序的八分之一内存来作为缓存大小。在中高配置的手机当中,这大概会有4兆(32/8)的缓存空间。一个全屏幕的 GridView 使用4张 800x480分辨率的图片来填充,则大概会占用1.5兆的空间(800 * 480 * 4)。因此,这个缓存大小可以存储2.5页的图片。
当向 ImageView 中加载一张图片时,首先会在 LruCache 的缓存中进行检查。如果找到了相应的键值,则会立刻更新ImageView ,否则开启一个后台线程来加载这张图片。
public void loadBitmap(int resId,ImageView imageview){
final String imageKay = String valueOf(resId);
Bitmap bitmap = mMemoryCache.getBitmapFromMemoryCache(imageKey);
if(bitmap!=null){
imageview.setImageBitmap(bitmap);
}else{
imageview.setImageResource(R.drawable.image_placeholder);
//缓存
BitmapWorkerTask task = new BitmapWorkTask(imageview);
task.execute(resId);
}
}
class BitmapWorkerTask extends AsyncTask<Integer,Void,Bitmap>{
//异步加载图片
@Override
protected Bitmap doInBackground(Integer...params){
final Bitmap bitmap = edcodeSampleBitmapFromResource(getResource(),params[0],100,100);
addBitmapToMemoryCache(String.valueOf(params[0],bitmap));
return bitmap;
}
}