图片作为内存消耗大户,一直是开发人员尝试优化的重点对象。Bitmap的内存从3.0以前的位于native,到后来改成jvm,再到8.0又改回到native。jvm每个进程都有内存上限,而native则没有限制(不是没有影响,至少不会oom),所以把内存大户Bitmap挪到native可能是很多人的梦想,但native的管理和实现明显比jvm更为复杂,除非有现成实现,很少有人去动这一块。
手段一:使用inSampleSize采样率压缩,尺寸压缩:这个是老生常谈了,使用options.inJustDecodeBounds来获取原始尺寸,然后按需使用options.inSampleSize来采样图片到接近view尺寸。
BitmapFactory 在解码图片时,可以带一个Options,有一些比较有用的功能,比如:
-
inTargetDensity 表示要被画出来时的目标像素密度
-
inSampleSize 这个值是一个int,当它小于1的时候,将会被当做1处理,如果大于1,那么就会按照比例(1 / inSampleSize)缩小bitmap的宽和高、降低分辨率,大于1时这个值将会被处置为2的倍数。例如,width=100,height=100,inSampleSize=2,那么就会将bitmap处理为,width=50,height=50,宽高降为1 / 2,像素数降为1 / 4
-
inJustDecodeBounds 字面意思就可以理解就是只解析图片的边界,有时如果只是为了获取图片的大小就可以用这个,而不必直接加载整张图片。
-
inPreferredConfig 默认会使用ARGB_8888,在这个模式下一个像素点将会占用4个byte,而对一些没有透明度要求或者图片质量要求不高的图片,可以使用RGB_565,一个像素只会占用2个byte,一下可以省下50%内存。
-
inPurgeable和inInputShareable 这两个需要一起使用,BitmapFactory.java的源码里面有注释,大致意思是表示在系统内存不足时是否可以回收这个bitmap,有点类似软引用,但是实际在5.0以后这两个属性已经被忽略,因为系统认为回收后再解码实际会反而可能导致性能问题
-
inBitmap 官方推荐使用的参数,表示重复利用图片内存,减少内存分配,在4.4以前只有相同大小的图片内存区域可以复用,4.4以后只要原有的图片比将要解码的图片大既可以复用了。
ARGB8888格式的图片,每像素占用 4 Byte,而 RGB565则是 2 Byte。如果是 ARGB8888 那么就是一个像素4个字节,如果是 RGB565 那就是2个字节。
格式描述ALPHA_8只有一个alpha通道,每个像素用一个字节(8位)存储ARGB_4444这个从API 13开始不建议使用,因为质量太差ARGB_8888ARGB四个通道,每个像素用四个字节(32位)存储RGB_565每个像素占2字节,其中红色占5bit,绿色占6bit,蓝色占5bitALPHA8 没必要用,因为我们随便用个颜色就可以搞定的;
ARGB4444 虽然占用内存只有 ARGB8888 的一半,不过已经被官方嫌弃『又要占省内存,又要看着爽,臣妾做不到啊T T』;
ARGB8888、RGB565 :默认会使用ARGB_8888,在这个模式下一个像素点将会占用4个byte,而对一些没有透明度要求或者图片质量要求不高的图片,可以使用RGB_565,一个像素只会占用2个byte,一下可以省下50%内存。
手段三:将图片放到合适的文件夹下:同一张图片,放在不同目录下,会生成不同大小的bitmap,因为它的宽高都被缩放了,所以图片资源应该尽可能放在高密度的资源文件夹下,这样可以节省图片的内存开支,一般建议放在xxhdpi下,目前主流手机都是这个dpi,并且UI给我们提供切图时也应该尽量面向高密度的屏幕设备来提供
jpg 是一种有损压缩的图片存储格式,而 png 则是 无损压缩的图片存储格式,显而易见,jpg 会比 png 小.
Bitmap 在内存当中占用的大小其实取决于:
-
色彩格式,前面我们已经提到,如果是 ARGB8888 那么就是一个像素4个字节,如果是 RGB565 那就是2个字节
-
原始文件存放的资源目录(是hdpi还是 xhdpi 还是 xxhdpi 可不能傻傻分不清楚)
-
目标屏幕的密度(所以同等条件下,红米在资源方面消耗的内存肯定是要小于三星S6的)
内存缓存:可以使用官方已经帮我们实现好的LurCache;
复用池:用来装载内存缓存LRU中被抛弃掉的,但是可能又会马上使用到的那些图片,我们在复用池中缓存下来,防止立马被GC回收掉;
硬盘缓存:有JakeWharton大神实现的DiskLruCache,下载地址:https://github.com/JakeWharton/DiskLruCache
为什么要设置复用池呢?
因为在官方8.0Android系统中,将图片放在native层中进行了处理,例如回收机制,在java层我们无法进行干预,为了能够不立即将图片交给native层处理,我们利用复用池中的弱引用暂时保存在java层中,以方便我们需要使用刚刚被内存缓存LRU放弃掉的图片,而不是重复decode图片再放入到LRU队列中,降低性能消耗。
下面就是我们的三级缓存机制:内存缓存、复用池、磁盘缓存
完整项目地址:https://github.com/buder-cp/base_component_learn/tree/master/performanceOPT/buderdn07
/**
* 管理内存中的图片
*/
public class ImageCache {
private static ImageCache instance;
private Context context;
private LruCache memoryCache;
private DiskLruCache diskLruCache;
BitmapFactory.Options options = new BitmapFactory.Options();
/**
* 定义一个复用沲
*/
public static Set reuseablePool;
public static ImageCache getInstance() {
if (null == instance) {
synchronized (ImageCache.class) {
if (null == instance) {
instance = new ImageCache();
}
}
}
return instance;
}
//引用队列
ReferenceQueue referenceQueue;
Thread clearReferenceQueue;
boolean shutDown;
private ReferenceQueue getReferenceQueue() {
if (null == referenceQueue) {
//当弱用引需要被回收的时候,会进到这个队列中
referenceQueue = new ReferenceQueue();
//单开一个线程,去扫描引用队列中GC扫到的内容,交到native层去释放
clearReferenceQueue = new Thread(new Runnable() {
@Override
public void run() {
while (!shutDown) {
try {
//remove是阻塞式的
Reference reference = referenceQueue.remove();
Bitmap bitmap = reference.get();
if (null != bitmap && !bitmap.isRecycled()) {
bitmap.recycle();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
clearReferenceQueue.start();
}
return referenceQueue;
}
//dir是用来存放图片文件的路径
public void init(Context context, String dir) {
this.context = context.getApplicationContext();
//复用池
reuseablePool = Collections.synchronizedSet(new HashSet());
ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
//获取程序最大可用内存 单位是M
int memoryClass = am.getMemoryClass();
//参数表示能够缓存的内存最大值 单位是byte
memoryCache = new LruCache(memoryClass / 8 * 1024 * 1024) {
/**
* @return value占用的内存大小
*/
@Override
protected int sizeOf(String key, Bitmap value) {
//19之前 必需同等大小,才能复用 inSampleSize=1
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) {
return value.getAllocationByteCount();
}
return value.getByteCount();
}
/**
* 拒接策略:当从lru缓存中移除后,可以获取到被移除的Bitmap
* 当lru满了,bitmap从lru中移除对象时,会回调
*/
@Override
protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) {
if (oldValue.isMutable()) {//如果是设置成能复用的内存块,拉到java层来管理
//3.0以下 Bitmap native
//3.0以后---8.0之前 java
//8。0开始 native
//lru缓存中多余图片放到一个复用池中
reuseablePool.add(new WeakReference(oldValue, referenceQueue));
} else {
//oldValue就是移出来的对象
oldValue.recycle();
}
}
};
//valueCount:表示一个key对应valueCount个文件
try {
diskLruCache = DiskLruCache.open(new File(dir), BuildConfig.VERSION_CODE, 1, 10 * 1024 * 1024);
} catch (Exception e) {
e.printStackTrace();
}
getReferenceQueue();
}
/**
* 加入内存缓存
*/
public void putBitmapToMemeory(String key, Bitmap bitmap) {
memoryCache.put(key, bitmap);
}
public Bitmap getBitmapFromMemory(String key) {
return memoryCache.get(key);
}
public void clearMemoryCache() {
memoryCache.evictAll();
}
//获取复用池中的内容
public Bitmap getReuseable(int w, int h, int inSampleSize) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
return null;
}
Bitmap reuseable = null;
Iterator iterator = reuseablePool.iterator();
while (iterator.hasNext()) {
Bitmap bitmap = iterator.next().get();
if (null != bitmap) {
//可以复用
if (checkInBitmap(bitmap, w, h, inSampleSize)) {
reuseable = bitmap;
iterator.remove();
break;
} else {
iterator.remove();
}
}
}
return reuseable;
}
private boolean checkInBitmap(Bitmap bitmap, int w, int h, int inSampleSize) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
return bitmap.getWidth() == w && bitmap.getHeight() == h && inSampleSize == 1;
}
if (inSampleSize >= 1) {
w /= inSampleSize;
h /= inSampleSize;
}
int byteCount = w * h * getPixelsCount(bitmap.getConfig());
return byteCount
关注
打赏
最近更新
- 深拷贝和浅拷贝的区别(重点)
- 【Vue】走进Vue框架世界
- 【云服务器】项目部署—搭建网站—vue电商后台管理系统
- 【React介绍】 一文带你深入React
- 【React】React组件实例的三大属性之state,props,refs(你学废了吗)
- 【脚手架VueCLI】从零开始,创建一个VUE项目
- 【React】深入理解React组件生命周期----图文详解(含代码)
- 【React】DOM的Diffing算法是什么?以及DOM中key的作用----经典面试题
- 【React】1_使用React脚手架创建项目步骤--------详解(含项目结构说明)
- 【React】2_如何使用react脚手架写一个简单的页面?