一、Bitmap优化的策略
1、根据不同的密度的设备将图片资源放置再不同的drawable文件夹中。
注释:比当前设备密度低的文件夹中搜到图片,那么在ImageVIEw(宽高在wrap_content状态下)中显示的图片将会被放大.图片放大也就意味着所占内存也开始增多;而在高密度文件夹中搜到图片,图片在该设备上将会被缩小,内存也就相应减少。
2、利用inSampleSize对图片进行尺寸上的压缩。
3、利用inPreferredConfig对图片进行质量上的压缩。
4、Bitmap复用池。
5、弱引用 Bitmap 内存释放。
4、利用三级缓存,依次从内存缓存(LruCache)、磁盘缓存(diskLruCache)、网络上获取图片。
二、Bitmap基础知识
Bitmap.Config参数介绍,创建Bitmap使用到
Alpha_8
表示8位Alpha位图,即A=8,一个像素点占用1个字节,它没有颜色,只有透明度
ARGB_4444
表示16位ARGB位图,即A=4,R=4,G=4,B=4,一个像素点占4+4+4+4=16位,2个字节
ARGB_8888
表示32位ARGB位图,即A=8,R=8,G=8,B=8,一个像素点占8+8+8+8=32位,4个字节
RGB_565
表示16位RGB位图,即R=5,G=6,B=5,它没有透明度,一个像素点占5+6+5=16位,2个字节
BitmapFactory.Options参数介绍
领域 | |
---|---|
public Bitmap | inBitmap 如果设置,则采用Options对象的解码方法将在加载内容时尝试重用此位图。 |
public int | inDensity 用于位图的像素密度。 |
public boolean | inDither 在API级别24中 |
public boolean | inInputShareable 在API级别21中已弃用该字段。从 |
public boolean | @L_403_9@ 如果设置为true,则解码器将返回null(无位图),但是 |
public boolean | inMutable 如果设置,则解码方法将始终返回可变的位图,而不是不变的位图。 |
public boolean | inPreferQualityOverSpeed 在API级别24中 |
public ColorSpace | inPreferredColorSpace
如果非零,则解码器将尝试解码到该颜色空间中。 |
public Bitmap.Config | inPreferredConfig 如果非零,则解码器将尝试解码为该内部配置。 |
public boolean | inPremultiplied 如果为true(默认设置),则生成的位图的颜色通道将被Alpha通道预乘。 |
public boolean | inPurgeable 在API级别21中已弃用该字段。从 为了进行重新解码,位图必须可以通过共享对输入的引用或对其进行复制来访问编码数据。此区别由ininputShareable控制。如果是这样,则位图可能会保留对输入的浅引用。如果这是错误的,则位图将显式创建输入数据的副本,并保留该副本。即使允许共享,实现也可能会决定对输入数据进行深度复制。 尽管inPurgeable可以帮助避免大的Dalvik堆分配(从API级别11开始),但是它牺牲了性能可预测性,因为视图系统尝试绘制的任何图像都可能会导致解码延迟,从而导致丢帧。因此,大多数应用应避免使用inPurgeable来实现快速流畅的UI。为了最小化Dalvik堆分配,请改用该 注意:与
|
public int | inSampleSize 如果设置为大于1的值,则请求解码器对原始图像进行二次采样,返回较小的图像以节省内存。 |
public boolean | inScaled 设置此标志时,如果 |
public int | inScreenDensity 正在使用的实际屏幕的像素密度。 |
public int | inTargetDensity 该位图将被绘制到的目标像素密度。 |
public byte[] | inTempStorage 用于解码的临时存储。 |
public boolean | mCancel 在API级别24中 |
public ColorSpace | outColorSpace 如果知道,解码后的位图将具有的色彩空间。 |
public Bitmap.Config | outConfig 如果知道,解码位图将具有的配置。 |
public int | outHeight 位图的最终高度。 |
public String | outMimeType 如果知道,则将该字符串设置为解码图像的模仿类型。 |
public int | outWidth 位图的结果宽度。 |
三、等比例压缩
采样率压缩其原理其实也是缩放bitamp的尺寸,通过调节其inSampleSize参数,比如调节为2,宽高会为原来的1/2,内存变回原来的1/4.采样率压缩。
public static Bitmap decodeSampledBitmapFromresource(Resources res, int resID, int reqWIDth, int reqHeight){ BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; //加载图片 BitmapFactory.decodeResource(res,resID,options); //计算缩放比 options.inSampleSize = calculateInSampleSize(options,reqHeight,reqWIDth); //重新加载图片 options.inJustDecodeBounds =false; return BitmapFactory.decodeResource(res,resID,options);}public static int caculateInSampleSize(Options options, int reqWIDth, int reqHeight){ int wIDth = options.outWIDth; int height = options.outHeight; int inSampleSize = 1; if (wIDth > reqWIDth || height > reqHeight){ int wIDthRadio = Math.round(wIDth * 1.0f / reqWIDth); int heighTradio = Math.round(height * 1.0f / reqHeight); inSampleSize = Math.max(wIDthRadio, heighTradio); } return inSampleSize; }
四、质量压缩
质量压缩不会减少图片的像素,它是在保持像素的前提下改变图片的位深及透明度,来达到压缩图片的目的,图片的长,宽,像素都不会改变,那么bitmap所占内存大小是不会变的。
我们可以看到有个参数:quality,可以调节你压缩的比例,但是还要注意一点就是,质量压缩堆png格式这种图片没有作用,因为png是无损压缩。
public static Bitmap compressImage(Bitmap bitmap){ ByteArrayOutputStream baos = new ByteArrayOutputStream(); //质量压缩方法,这里100表示不压缩,把压缩后的数据存放到baos中 bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos); int options = 100; //循环判断如果压缩后图片是否大于50kb,大于继续压缩 while ( baos.toByteArray().length / 1024>50) { //清空baos baos.reset(); bitmap.compress(Bitmap.CompressFormat.JPEG, options, baos); options -= 10;//每次都减少10 } //把压缩后的数据baos存放到ByteArrayinputStream中 ByteArrayinputStream isBm = new ByteArrayinputStream(baos.toByteArray()); //把ByteArrayinputStream数据生成图片 Bitmap newBitmap = BitmapFactory.decodeStream(isBm, null, null); return newBitmap; }
五、放缩法压缩
放缩法压缩使用的是通过矩阵对图片进行裁剪,也是通过缩放图片尺寸,来达到压缩图片的效果,和采样率的原理一样。
private voID compressMatrix() { Matrix matrix = new Matrix(); matrix.setScale(0.5f, 0.5f); Bitmap bm = BitmapFactory.decodeResource(getResources(), R.drawable.test); mSrcBitmap = Bitmap.createBitmap(bm, 0, 0, bm.getWIDth(), bm.getHeight(), matrix, true); bm = null; }
六、工具类
package kim.hsl.bm.utils;import androID.app.ActivityManager;import androID.content.Context;import androID.graphics.Bitmap;import androID.os.Build;import androID.util.LruCache;import java.lang.ref.Reference;import java.lang.ref.ReferenceQueue;import java.lang.ref.WeakReference;import java.util.Collections;import java.util.HashSet;import java.util.Iterator;import java.util.Set;/** * Bitmap 内存缓存 * 在将图片缓存到 LruCache 内存中基础上 , * 将从 LruCache 中移除的最近没有使用的 Bitmap 对象的内存复用 * 这样能最大限度减少内存抖动 */public class BitmapLruCacheMemoryReuse { private static final String TAG = "BitmapMemoryCache"; /** * 应用上下文对象 */ private Context mContext; /** * 缓存图片的 LruCache */ private LruCache<String, Bitmap> mLruCache; /** * Bitmap 复用池 * 使用 inBitmap 复用选项 * 需要获取图片时 , 优先从 Bitmap 复用池中查找 * 这里使用弱引用保存该 Bitmap , 每次 GC 时都会回收该 Bitmap * 创建一个线程安全的 HashSet , 其中的元素是 Bitmap 弱引用 * * 该 Bitmap 复用池的作用是 , 假如 Bitmap 对象长时间不使用 , 就会从内存缓存中移除 * * Bitmap 回收策略 : * 3.0 以下系统中 , Bitmap 内存在 Native 层 * 3.0 以上系统中 , Bitmap 内存在 Java 层 * 8.0 及以上的系统中 , Bitmap 内存在 Native 层 * * 因此这里需要处理 Bitmap 内存在 Native 层的情况 , 监控到 Java 层的弱引用被释放了 * 需要调用 Bitmap 对象的 recycle 方法 , 释放 Native 层的内存 * * 需要使用引用队列监控弱引用的释放情况 */ Set<WeakReference<Bitmap>> bitmapReusePool; /** * 引用队列 , 用于监控 Set<WeakReference<Bitmap>> bitmapReusePool 的内存是否被回收 * 需要维护一个线程 , 不断尝试从该引用队列中获取引用 * */ private ReferenceQueue<Bitmap> referenceQueue; /** * 监控 Set<WeakReference<Bitmap>> bitmapReusePool 的内存是否被回收 , * 调用 ReferenceQueue<Bitmap> referenceQueue 的 remove 方法 , * 查看是否存在被回收的弱引用 , 如果存在 , 直接回收该弱引用对应的 Bitmap 对象 */ private Thread referenceQueueMonitorThread; /** * 是否持续监控引用队列 ReferenceQueue */ private boolean isMonitorReferenceQueue = true; /** * 单例实现 */ private static BitmapLruCacheMemoryReuse INSTANCE; private BitmapLruCacheMemoryReuse(){} public static BitmapLruCacheMemoryReuse getInstance(){ if(INSTANCE == null){ INSTANCE = new BitmapLruCacheMemoryReuse(); } return INSTANCE; } /** * 使用时初始化 * @param context */ public voID init(Context context){ // 初始化内存缓存 initLruCache(context); // 初始化弱引用队列 initBitmapReusePool(); } /** * 不使用时释放 */ public voID release(){ isMonitorReferenceQueue = false; } private voID initLruCache(Context context){ // 为成员变量赋值 this.mContext = context; // 获取 Activity 管理器 ActivityManager activityManager = (ActivityManager) context.getSystemService( Context.ACTIVITY_SERVICE); // 获取应用可用的最大内存 int maxMemory = activityManager.getMemoryClass(); // 获取的 maxMemory 单位是 MB , 将其转为字节 , 除以 8 int lruCacheMemoryByte = maxMemory / 8 * 1024 * 1024; // 设置的内存 , 一般是 APP 可用内存的 1/8 mLruCache = new LruCache<String, Bitmap>(lruCacheMemoryByte){ /** * 返回 LruCache 的键和值的大小 , 单位使用用户自定义的单位 * 默认的实现中 , 返回 1 ; size 是 键值对个数 , 最大的 size 大小是最多键值对个数 * 键值对条目在 LruCache 中缓存时 , 其大小不能改变 * @param key * @param value * @return 返回 LruCache<String, Bitmap> 的值 , 即 Bitmap 占用内存 */ @OverrIDe protected int sizeOf(String key, Bitmap value) { // 如果使用的是复用的 Bitmap 对象 , 其占用内存大小是之前的图像分配的内存大小 // 大于等于当前图像的内存占用大小 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { return value.getAllocationByteCount(); } return value.getByteCount(); } /** * 从 LruCache 缓存移除 Bitmap 时会回调该方法 * @param evicted * @param key * @param oldValue * @param newValue */ @OverrIDe protected voID entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) { super.entryRemoved(evicted, key, oldValue, newValue); /* 如果从 LruCache 内存缓存中移除的 Bitmap 是可变的 才能被复用 , 否则只能回收该 Bitmap 对象 Bitmap 回收策略 : 3.0 以下系统中 , Bitmap 内存在 Native 层 3.0 以上系统中 , Bitmap 内存在 Java 层 8.0 及以上的系统中 , Bitmap 内存在 Native 层 因此这里需要处理 Bitmap 内存在 Native 层的情况 , 监控到 Java 层的弱引用被释放了 需要调用 Bitmap 对象的 recycle 方法 , 释放 Native 层的内存 */ if(oldValue.isMutable()){ // 可以被复用 // 将其放入弱引用中 , 每次 GC 启动后 , 如果该弱引用没有被使用 , 都会被回收 bitmapReusePool.add(new WeakReference<Bitmap>(oldValue, referenceQueue)); }else{ // 不可被复用 , 直接回收 oldValue.recycle(); } } }; } private voID initBitmapReusePool(){ // 创建一个线程安全的 HashSet , 其中的元素是 Bitmap 弱引用 bitmapReusePool = Collections.synchronizedSet(new HashSet<WeakReference<Bitmap>>()); // 引用队列 , 当弱引用被 GC 扫描后 , 需要回收 , 会将该弱引用放入队列 // 一直不断的尝试从该引用队列中获取数据 , 如果获取到数据 , 就要回收该对象 referenceQueue = new ReferenceQueue<>(); // 定义监控线程 referenceQueueMonitorThread = new Thread(){ @OverrIDe public voID run() { while (isMonitorReferenceQueue){ try { Reference<Bitmap> reference = (Reference<Bitmap>) referenceQueue.remove(); Bitmap bitmap = reference.get(); // 不为空 , 且没有被回收 , 回收 Bitmap 内存 if(bitmap != null && !bitmap.isRecycled()){ bitmap.recycle(); } } catch (InterruptedException e) { e.printstacktrace(); } } } }; // 启动引用队列监控线程 referenceQueueMonitorThread.start(); } /** * 获取一个可以被复用的 Bitmap 对象 * * 与 BitmapFactory 配合使用 : * * AndroID 4.4 以后的 Bitmap 复用情况 : * 在 KITKAT ( AndroID 4.4 , 19 平台 ) 以后的代码中 , * 只要被解码生成的 Bitmap 对象的字节大小 ( 缩放后的 ) * 小于等于 inBitmap 的字节大小 , 就可以复用成功 ; * * AndroID 4.4 之前的 Bitmap 复用情况 : ( 比较苛刻 ) * 在 KITKAT 之前的代码中 , 被解码的图像必须是 * - JPEG 或 PNG 格式 , * - 并且 图像大小必须是相等的 , * - inssampleSize 设置为 1 , * 才能复用成功 ; * 另外被复用的图像的 像素格式 Config ( 如 RGB_565 ) 会覆盖设置的 inPreferredConfig 参数 * * @param wIDth * @param height * @param inSampleSize * @return */ public Bitmap getReuseBitmap(int wIDth,int height,int inSampleSize){ // AndroID 2.3.3(API 级别 10)及以下的版本中 , 使用 Bitmap 对象的 recycle 方法回收内存 if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.GINGERBREAD_MR1){ // 如果 API 级别小于等于 10 , 不启用 Bitmap 内存复用机制 , 返回 null 即可 return null; } // 获取准备复用的 Bitmap , 之后设置到 Options 中 Bitmap inBitmap = null; // 使用迭代器遍历该 Set 集合 , 如果遍历中涉及到删除 , 就要使用迭代器遍历 Iterator<WeakReference<Bitmap>> iterator = bitmapReusePool.iterator(); //迭代查找符合复用条件的Bitmap while (iterator.hasNext()){ // 循环遍历 Bitmap 对象 Bitmap bitmap = iterator.next().get(); if (bitmap != null){ /* 检查该 Bitmap 对象是否可以达到复用要求 , 如果达到复用要求 , 就取出这个 Bitmap 对象 , 并将其从队列中移除 */ if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.JELLY_BEAN_MR2){ /* AndroID 4.4(API 级别 19)以下的版本 : 在 AndroID 4.4(API 级别 19) 之前的代码中 , 复用的前提是必须同时满足以下 3 个条件 : 1. 被解码的图像必须是 JPEG 或 PNG 格式 2. 被复用的图像宽高必须等于 解码后的图像宽高 3. 解码图像的 BitmapFactory.Options.inSampleSize 设置为 1 , 也就是不能缩放 才能复用成功 , 另外被复用的图像的像素格式 Config ( 如 RGB_565 ) 会覆盖设置的 BitmapFactory.Options.inPreferredConfig 参数 ; */ if(bitmap.getWIDth() == wIDth && bitmap.getHeight() == height && //被复用的图像宽高必须等于 解码后的图像宽高 inSampleSize == 1){// 图像的 BitmapFactory.Options.inSampleSize 设置为 1 //符合要求 inBitmap = bitmap; iterator.remove(); } }else if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT){ /* 在 AndroID 4.4(API 级别 19)及以上的版本中 , 只要被解码后的 Bitmap 对象的字节大小 , 小于等于 inBitmap 的字节大小 , 就可以复用成功 ; 解码后的乳香可以是缩小后的 , 即 BitmapFactory.Options.inSampleSize 可以大于1 ; */ // 首先要计算图像的内存占用 , 先要计算出图像的宽高 , 如果图像需要缩放 , 计算缩放后的宽高 if(inSampleSize > 1){ wIDth = wIDth / inSampleSize ; height = height / inSampleSize; } // 计算内存占用 , 默认 ARGB_8888 格式 int byteInMemory = wIDth * height * 4;; if(bitmap.getConfig() == Bitmap.Config.ARGB_8888){ // 此时每个像素占 4 字节 byteInMemory = wIDth * height * 4; }else if(bitmap.getConfig() == Bitmap.Config.RGB_565){ // 此时每个像素占 2 字节 byteInMemory = wIDth * height * 2; } // 如果解码后的图片内存小于等于被复用的内存大小 , 可以复用 if(byteInMemory <= bitmap.getAllocationByteCount()){ //符合要求 inBitmap = bitmap; iterator.remove(); } } }else if( bitmap == null ){ // 如果 bitmap 为空 , 直接从复用 Bitmap 集合中移除 iterator.remove(); } } return inBitmap; } /* 下面的 3 个方法是提供给用户用于 *** 作 LruCache 的接口 */ /** * 将键值对放入 LruCache 中 * @param key * @param value */ public voID putBitmapTolruCache(String key, Bitmap value){ mLruCache.put(key, value); } /** * 从 LruCache 中获取 Bitmap 对象 * @param key * @return */ public Bitmap getBitmapFromLruCache(String key){ return mLruCache.get(key); } /** * 清除 LruCache 缓存 */ public voID clearLruCache(){ mLruCache.evictAll(); }}
总结
以上是内存溢出为你收集整理的Android Bitmap优化方案实战全部内容,希望文章能够帮你解决Android Bitmap优化方案实战所遇到的程序开发问题。
如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)