Android Bitmap优化方案实战

Android Bitmap优化方案实战,第1张

概述一、Bitmap优化的策略1、根据不同的密度的设备将图片资源放置再不同的drawable文件夹中。注释:比当前设备密度低的文件夹中搜到图片,那么在ImageView(宽高在wrap_content状态下)中显示的图片将会被放大.图片放大也就意味着所占内存也开始增多;而在高密度文件夹中搜到图片,图片在

一、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 BitmapinBitmap

如果设置,则采用Options对象的解码方法将在加载内容时尝试重用此位图。

public intinDensity

用于位图的像素密度。

public booleaninDither

在API级别24中Build.VERSION_CODES.N,该字段已弃用。从开始,此字段将被忽略。在Build.VERSION_CODES.M下面,如果抖动为真,则解码器将尝试对解码图像进行抖动。

public booleaninInputShareable

在API级别21中已弃用该字段。从Build.VERSION_CODES.LOLLIPOP至今,此字段将被忽略。在Build.VERSION_CODES.KITKAT下面,此字段与inPurgeable结合使用。如果inPurgeable为false,则忽略此字段。如果inPurgeable为true,则此字段确定位图是否可以共享对输入数据(输入流,数组等)的引用,或者是否必须进行深拷贝。

public boolean@L_403_9@

如果设置为true,则解码器将返回null(无位图),但是 out...

public booleaninMutable

如果设置,则解码方法将始终返回可变的位图,而不是不变的位图。

public booleaninPreferQualityOverSpeed

在API级别24中Build.VERSION_CODES.N,该字段已弃用。从开始,此字段将被忽略。输出将始终是高质量的。在Build.VERSION_CODES.M下面,如果inPreferQualityOverSpeed设置为true,则解码器将尝试以较高的解码质量来重建重构的图像,即使是以牺牲解码速度为代价的。当前,该字段仅影响JPEG解码,在这种情况下,将使用更准确但稍慢的IDCT方法代替。

public ColorSpaceinPreferredColorSpace

 

如果非零,则解码器将尝试解码到该颜色空间中。

public Bitmap.ConfiginPreferredConfig

如果非零,则解码器将尝试解码为该内部配置。

public booleaninPremultiplied

如果为true(默认设置),则生成的位图的颜色通道将被Alpha通道预乘。

public booleaninPurgeable

在API级别21中已弃用该字段。从Build.VERSION_CODES.LOLLIPOP至今,此字段将被忽略。在Build.VERSION_CODES.KITKAT下面,如果将其设置为true,则结果位图将分配其像素,以便在系统需要回收内存时可以将其清除。在那种情况下,当需要再次访问像素时(例如绘制位图,调用getPixels()),它们将被自动重新解码。

为了进行重新解码,位图必须可以通过共享对输入的引用或对其进行复制来访问编码数据。此区别由ininputShareable控制。如果是这样,则位图可能会保留对输入的浅引用。如果这是错误的,则位图将显式创建输入数据的副本,并保留该副本。即使允许共享,实现也可能会决定对输入数据进行深度复制。

尽管inPurgeable可以帮助避免大的Dalvik堆分配(从API级别11开始),但是它牺牲了性能可预测性,因为视图系统尝试绘制的任何图像都可能会导致解码延迟,从而导致丢帧。因此,大多数应用应避免使用inPurgeable来实现快速流畅的UI。为了最小化Dalvik堆分配,请改用该inBitmap标志。

注意:与BitmapFactory.decodeResource(android.content.res.Resources, int, android.graphics.BitmapFactory.Options)或一起使用时,将忽略此标志BitmapFactory.decodeFile(java.lang.String, android.graphics.BitmapFactory.Options)

 

public intinSampleSize

如果设置为大于1的值,则请求解码器对原始图像进行二次采样,返回较小的图像以节省内存。

public booleaninScaled

设置此标志时,如果inDensity和 inTargetDensity不为0,则位图将inTargetDensity在加载时缩放以匹配,而不是每次绘制到Canvas时都依赖于图形系统对其进行缩放。

public intinScreenDensity

正在使用的实际屏幕的像素密度。

public intinTargetDensity

该位图将被绘制到的目标像素密度。

public byte[]inTempStorage

用于解码的临时存储。

public booleanmCancel

在API级别24中Build.VERSION_CODES.N,此字段已弃用requestCancelDecode()。指示已在此对象上调用cancel的标志。如果中间人想先解码边界然后解码图像,这将很有用。在那种情况下,中间人可以在边界解码和图像解码之间进行检查,以查看 *** 作是否被取消。

public ColorSpaceoutColorSpace

如果知道,解码后的位图将具有的色彩空间。

public Bitmap.ConfigoutConfig

如果知道,解码位图将具有的配置。

public intoutHeight

位图的最终高度。

public StringoutMimeType

如果知道,则将该字符串设置为解码图像的模仿类型。

public intoutWidth

位图的结果宽度。

三、等比例压缩

采样率压缩其原理其实也是缩放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优化方案实战所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/web/1048296.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-25
下一篇 2022-05-25

发表评论

登录后才能评论

评论列表(0条)

保存