//10.28
关于bitmap的缓存算法,现在可以用lruCache代替。
另外最近遇到一个问题,公司里的某一个项目,通过多次生成bitmap来调整bitmap的尺寸
虽然不知道目的是什么,但是我试着用option的outWidth和outHeight代替生成bitmap后拿出来的长度和宽度
但是outWidth和outHeight都是图片的原始尺寸,所以反而没有实现最终效果
暂时没有好的想法解决
===============================================
之前接触的一些技术,没用一段时间都忘了,唉,趁现在赶紧保存起来。
由于Android只能识别PNG格式的图片,所以当我们要读取其他格式的图片,或者同时、甚至不断加载图片时,就要用到Bitmap去解码图片、生成缩略图。
但是由于生成Bitmap是直接占用内存的,所以往往加载一些大的图片或者数量变多之后,直接内存溢出。
所以关于Bitmap的优化是必须的。
以下为部分转载:
Android提供了一个用于图像解码的类BitmapFactory,其中提供了一系列方法解析图片对象,包括byte 数组,InputStream ,资源ID,或者指定的文件名。
BitmapFactory.Options 用于指定解码时的一些设置:
1)inBitmap如果设置,当加载内容时该方法将尝试重用这个位图;
2)inDensity使用像素密度来表示位图;
3)inDither如果存在抖动,解码器将尝试解码图像抖动;
4)inPurgeable如果设置为true,则由此产生的位图将分配其像素,以便系统需要回收内存时可以将它们清除;
5)inInputShareable与inPurgeable一起使用,如果inPurgeable为false那该设置将被忽略,如果为true,那么它可以决定位图是否能够共享一个指向数据源的引用,或者是进行一份拷贝;
(第4、5点说得很清楚,要一起使用。inPurgeable我的理解是,设置为true后,生成Bitmap所存储的其像素的内存空间可以在内存不足的情况下被回收,在需要再次调用该Bitmap的像素时[如重新绘制Bitmap等],系统会调用BitmapFactory
对于这个在网上还有一些资料:
1,在Android 2.3以及之后,采用的是并发回收机制,避免在回收内存时的卡顿现象。
2,在Android 2.3.3(API Level 10)以及之前,Bitmap的backing pixel 数据存储在native memory, 与Bitmap本身是分开的,Bitmap本身存储在dalvik heap 中。导致其pixel数据不能判断是否还需要使用,不能及时释放,容易引起OOM错误。
从Android 3.0(API 11)开始,pixel数据与Bitmap一起存储在Dalvik heap中。
6)inJustDecodeBounds如果设置,那返回的位图将为空,但会保存数据源图像的宽度和高度;
(
- BitmapFactory.Options options = new BitmapFactory.Options();
- options.inJustDecodeBounds = true;
- Bitmap bmp = BitmapFactory.decodeFile(path, options);
- /* 这里返回的bmp是null *
这样就可以只获取该图片的宽度和高度,而不用多占内存。而后面要再生成Bitmap时要把isJustDecodeBounds设置为false
7)inMutable如果设置,解码方法将始终返回一个可变的位图;
8)inPreferQualityOverSpeed如果设置为true,解码器将尝试重建图像以获得更高质量的解码,甚至牺牲解码速度;
9)inPreferredConfig 如果为非空,解码器将尝试解码成这个内部配置;
(一般会用这个设置像素的位数,下面是相关资料:
A:透明度
R:红色
G:绿
B:蓝
Bitmap.Config ARGB_4444:每个像素占四位,即A=4,R=4,G=4,B=4,那么一个像素点占4+4+4+4=16位
Bitmap.Config ARGB_8888:每个像素占四位,即A=8,R=8,G=8,B=8,那么一个像素点占8+8+8+8=32位
Bitmap.Config RGB_565:每个像素占四位,即R=5,G=6,B=5,没有透明度,那么一个像素点占5+6+5=16位
Bitmap.Config ALPHA_8:每个像素占四位,只有透明度,没有颜色。
一般情况下我们都是使用的ARGB_8888,由此可知它是最占内存的,因为一个像素占32位,8位=1字节,所以一个像素占4字节的内存。假设有一张480x800的图片,如果格式为ARGB_8888,那么将会占用1500KB的内存。
)10)inSampleSize 如果设置的值大于1,解码器将等比缩放图像以节约内存;(inSampleSize这个值只能取2的次幂,若设置为2时,所获取的图片长宽都为原来的1/2,则图片大小为原来的1/4)
11)inScaled如果设置,当inDensity和inTargetDensity不为0,加载时该位图将被缩放,以匹配inTargetDensity,而不是依靠图形系统缩放每次将它绘制到画布上;
12)inScreenDensity当前正在使用的实际屏幕的像素密度;
13)inTargetDensity这个位图将被画到的目标的像素密度;
14)mCancel用于指示已经调用了这个对象的取消方法的标志;
15)outHeight、outWidth图像的高度和宽度;
16)outMimeType 如果知道,这个字符串将被设置为解码图像的MIME类型。
上面红字部分是最近自己用到的一些方法,没用到的等后面自己慢慢理解了。
总之,我们可以用到这些方法来给BItmap进行优化,下面是一些在网上看到的资料:
在加载图片资源时,可采用以下一些方法来避免OOM的问题:
1,在Android 2.3.3以及之前,建议使用Bitmap.recycle()方法,及时释放资源。
2,在Android 3.0开始,可设置BitmapFactory.options.inBitmap值,(从缓存中获取)达到重用Bitmap的目的。如果设置,则inPreferredConfig属性值会被重用的Bitmap该属性值覆盖。
3,通过设置Options.inPreferredConfig值来降低内存消耗:
默认为ARGB_8888: 每个像素4字节. 共32位。
Alpha_8: 只保存透明度,共8位,1字节。
ARGB_4444: 共16位,2字节。
RGB_565:共16位,2字节。
如果不需要透明度,可把默认值ARGB_8888改为RGB_565,节约一半内存。
4,通过设置Options.inSampleSize 对大图片进行压缩,可先设置Options.inJustDecodeBounds,获取Bitmap的外围数据,宽和高等。然后计算压缩比例,进行压缩。
5,设置Options.inPurgeable和inInputShareable:让系统能及时回收内存。
inPurgeable:设置为True,则使用BitmapFactory创建的Bitmap用于存储Pixel的内存空间,在系统内存不足时可以被回收,当应用需要再次访问该Bitmap的Pixel时,系统会再次调用BitmapFactory 的decode方法重新生成Bitmap的Pixel数组。
设置为False时,表示不能被回收。
inInputShareable:设置是否深拷贝,与inPurgeable结合使用,inPurgeable为false时,该参数无意义。
True: share a reference to the input data(inputStream, array,etc) 。 False :a deep copy。
6,使用decodeStream代替其他decodeResource,setImageResource,setImageBitmap等方法来加载图片。
区别:
decodeStream直接读取图片字节码,调用nativeDecodeAsset/nativeDecodeStream来完成decode。无需使用Java空间的一些额外处理过程,节省dalvik内存。但是由于直接读取字节码,没有处理过程,因此不会根据机器的各种分辨率来自动适应,需要在hdpi,mdpi和ldpi中分别配置相应的图片资源,否则在不同分辨率机器上都是同样的大小(像素点数量),显示的实际大小不对。
decodeResource会在读取完图片数据后,根据机器的分辨率,进行图片的适配处理,导致增大了很多dalvik内存消耗。
decodeStream调用过程:
decodeStream(InputStream,Rect,Options) -> nativeDecodeAsset/nativeDecodeStream
decodeResource调用过程:即finishDecode之后,调用额外的Java层的createBitmap方法,消耗更多dalvik内存。
decodeResource(Resource,resId,Options) -> decodeResourceStream (设置Options的inDensity和inTargetDensity参数) -> decodeStream() (在完成Decode后,进行finishDecode操作)
finishDecode() -> Bitmap.createScaleBitmap()(根据inDensity和inTargetDensity计算scale) -> Bitmap.createBitmap()
以上方法的组合使用,合理避免OOM错误。
下面贴一下在网上一些项目里看到一段BitmapCache代码,自己修改了一小部分:
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.util.Hashtable;
import android.content.Context;
import android.content.res.AssetManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
/**
* 防止溢出
*
*/
public class BitmapCache {
static private BitmapCache cache;
/** 用于Chche内容的存储 */
private Hashtable<String, BtimapRef> bitmapRefs;
/** 垃圾Reference的队列(所引用的对象已经被回收,则将该引用存入队列中) */
private ReferenceQueue<Bitmap> q;
/**
* 继承SoftReference,使得每一个实例都具有可识别的标识。
*/
private class BtimapRef extends SoftReference<Bitmap> {
private String _key = "";
public BtimapRef(Bitmap bmp, ReferenceQueue<Bitmap> q, String key) {
super(bmp, q);
_key = key;
}
}
private BitmapCache() {
bitmapRefs = new Hashtable<String, BtimapRef>();
q = new ReferenceQueue<Bitmap>();
}
/**
* 取得缓存器实例
*/
public static BitmapCache getInstance() {
if (cache == null) {
cache = new BitmapCache();
}
return cache;
}
/**
* 以软引用的方式对一个Bitmap对象的实例进行引用并保存该引用
*/
private void addCacheBitmap(Bitmap bmp, String key) {
cleanCache();// 清除垃圾引用
BtimapRef ref = new BtimapRef(bmp, q, key);
bitmapRefs.put(key, ref);
}
/**
* 依据所指定的文件名获取图片
*/
public Bitmap getBitmap(String filename, String filepath,int itemwidth) {
Bitmap bitmapImage = null;
// 缓存中是否有该Bitmap实例的软引用,如果有,从软引用中取得。
if (bitmapRefs.containsKey(filename)) {
BtimapRef ref = (BtimapRef) bitmapRefs.get(filename);
bitmapImage = (Bitmap) ref.get();
}
// 如果没有软引用,或者从软引用中得到的实例是null,重新构建一个实例,
// 并保存对这个新建实例的软引用
if (bitmapImage == null) {
BitmapFactory.Options options = new BitmapFactory.Options();
// options.inSampleSize=4;
// options.inTempStorage = new byte[16 * 1024];
// bitmapImage = BitmapFactory.decodeFile(filename, options);
options.inPurgeable=true;
options.inInputShareable=true;
options.inPreferredConfig=Bitmap.Config.ARGB_4444;
options.inJustDecodeBounds=true;
bitmapImage=BitmapFactory.decodeFile(filepath, options);
options.inSampleSize=options.outWidth / itemwidth;
options.inJustDecodeBounds=false;
BufferedInputStream buf;
try {
buf = new BufferedInputStream(new FileInputStream(filepath));
bitmapImage = BitmapFactory.decodeStream(buf,null, options);
this.addCacheBitmap(bitmapImage, filename);
} catch (IOException e) {
e.printStackTrace();
}
}
return bitmapImage;
}
private void cleanCache() {
BtimapRef ref = null;
while ((ref = (BtimapRef) q.poll()) != null) {
bitmapRefs.remove(ref._key);
}
}
// 清除Cache内的全部内容
public void clearCache() {
cleanCache();
bitmapRefs.clear();
System.gc();
System.runFinalization();
}
}