内存优化二Bitmap优化

Posted 小图包

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了内存优化二Bitmap优化相关的知识,希望对你有一定的参考价值。

Bitmap基础知识

一张图片Bitmap所占用的内存 = 图片长度 x 图片宽度 x 一个像素点占用的字节数 
而Bitmap.Config,正是指定单位像素占用的字节数的重要参数。 

其中,A代表透明度;R代表红色;G代表绿色;B代表蓝色。

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个字节

一张图片Bitmap所占用的内存 = 图片长度 x 图片宽度 x 一个像素点占用的字节数

根据以上的算法,可以计算出图片占用的内存,以100*100像素的图片为例
 

 BitmapFactory解析Bitmap的原理

BitmapFactory提供的解析Bitmap的静态工厂方法有以下五种:

Bitmap decodeFile(...)
Bitmap decodeResource(...)
Bitmap decodeByteArray(...)
Bitmap decodeStream(...)
Bitmap decodeFileDescriptor(...)

其中常用的三个:decodeFile、decodeResource、decodeStream。 
decodeFile和decodeResource其实最终都是调用decodeStream方法来解析Bitmap

decodeFile方法代码:

   public static Bitmap decodeFile(String pathName, Options opts) 
        Bitmap bm = null;
        InputStream stream = null;
        try 
            stream = new FileInputStream(pathName);
            bm = decodeStream(stream, null, opts);
         catch (Exception e) 
            /*  do nothing.
                If the exception happened on open, bm will be null.
            */
            Log.e("BitmapFactory", "Unable to decode stream: " + e);
         finally 
            if (stream != null) 
                try 
                    stream.close();
                 catch (IOException e) 
                    // do nothing here
                
            
        

decodeResource方法的代码:

public static Bitmap decodeResource(Resources res, int id, Options opts) 
        Bitmap bm = null;
        InputStream is = null; 

        try 
            final TypedValue value = new TypedValue();
            is = res.openRawResource(id, value);

            bm = decodeResourceStream(res, value, is, null, opts);
         catch (Exception e) 
            /*  do nothing.
                If the exception happened on open, bm will be null.
                If it happened on close, bm is still valid.
            */
         finally 
            try 
                if (is != null) is.close();
             catch (IOException e) 
                // Ignore
            
        

        if (bm == null && opts != null && opts.inBitmap != null) 
            throw new IllegalArgumentException("Problem decoding into existing bitmap");
        

        return bm;
    

decodeStream的逻辑如下:

 public static Bitmap decodeStream(InputStream is, Rect outPadding, Options opts) 
        // we don't throw in this case, thus allowing the caller to only check
        // the cache, and not force the image to be decoded.
        if (is == null) 
            return null;
        

        Bitmap bm = null;

        Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "decodeBitmap");
        try 
            if (is instanceof AssetManager.AssetInputStream) 
                final long asset = ((AssetManager.AssetInputStream) is).getNativeAsset();
                bm = nativeDecodeAsset(asset, outPadding, opts);
             else 
                bm = decodeStreamInternal(is, outPadding, opts);
            

            if (bm == null && opts != null && opts.inBitmap != null) 
                throw new IllegalArgumentException("Problem decoding into existing bitmap");
            

            setDensityFromOptions(bm, opts);
         finally 
            Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS);
        

        return bm;
    
private static Bitmap decodeStreamInternal(InputStream is, Rect outPadding, Options opts) 
        // ASSERT(is != null);
        byte [] tempStorage = null;
        if (opts != null) tempStorage = opts.inTempStorage;
        if (tempStorage == null) tempStorage = new byte[DECODE_BUFFER_SIZE];
        return nativeDecodeStream(is, tempStorage, outPadding, opts);
    

从上面的代码可以看出,decodeStream的代码最终会调用以下两个native方法之一

ativeDecodeAsset()
nativeDecodeStream()

这两个native方法只是对应decodeFile和decodeResource、decodeStream来解析的,像decodeByteArray、decodeFileDescriptor也有专门的native方法负责解析Bitmap。

decodeFile、decodeResource的区别在于他们方法的调用路径不同:
 

decodeResource在解析时多调用了一个decodeResourceStream方法,而这个decodeResourceStream方法代码如下:

public static Bitmap decodeResourceStream(Resources res, TypedValue value,
            InputStream is, Rect pad, Options opts) 

        if (opts == null) 
            opts = new Options();
        

        if (opts.inDensity == 0 && value != null) 
            final int density = value.density;
            if (density == TypedValue.DENSITY_DEFAULT) 
                opts.inDensity = DisplayMetrics.DENSITY_DEFAULT;
             else if (density != TypedValue.DENSITY_NONE) 
                opts.inDensity = density;
            
        

        if (opts.inTargetDensity == 0 && res != null) 
            opts.inTargetDensity = res.getDisplayMetrics().densityDpi;
        

        return decodeStream(is, pad, opts);
    

其中对Options进行处理了,在得到opts.inDensity属性的前提下,如果我们没有对该属性设定值,那么将opts.inDensity = DisplayMetrics.DENSITY_DEFAULT;赋定这个默认的Density值,这个默认值为160,为标准的dpi比例,即在Density=160的设备上1dp=1px,这个方法中还有这么一行

opts.inTargetDensity = res.getDisplayMetrics().densityDpi;

对opts.inTargetDensity进行了赋值,该值为当前设备的densityDpi值,所以说在decodeResourceStream方法中主要做了两件事:

1.对opts.inDensity赋值,没有则赋默认值160 
2.对opts.inTargetDensity赋值,没有则赋当前设备的densityDpi值

之后参数将传入decodeStream方法,该方法中在调用native方法进行解析Bitmap后会调用这个方法setDensityFromOptions(bm, opts);:
 

private static void setDensityFromOptions(Bitmap outputBitmap, Options opts) 
        if (outputBitmap == null || opts == null) return;

        final int density = opts.inDensity;
        if (density != 0) 
            outputBitmap.setDensity(density);
            final int targetDensity = opts.inTargetDensity;
            if (targetDensity == 0 || density == targetDensity || density == opts.inScreenDensity) 
                return;
            

            byte[] np = outputBitmap.getNinePatchChunk();
            final boolean isNinePatch = np != null && NinePatch.isNinePatchChunk(np);
            if (opts.inScaled || isNinePatch) 
                outputBitmap.setDensity(targetDensity);
            
         else if (opts.inBitmap != null) 
            // bitmap was reused, ensure density is reset
            outputBitmap.setDensity(Bitmap.getDefaultDensity());
        
    

主要就是把刚刚赋值过的两个属性inDensity和inTargetDensity给Bitmap进行赋值,不过并不是直接赋给Bitmap就完了,中间有个判断,当inDensity的值与inTargetDensity或与设备的屏幕Density不相等时,则将应用inTargetDensity的值,如果相等则应用inDensity的值。

所以总结来说,setDensityFromOptions方法就是把inTargetDensity的值赋给Bitmap,不过前提是opts.inScaled = true;

进过上面的分析,结论如下:

在不配置Options的情况下: 
1.decodeFile、decodeStream在解析时不会对Bitmap进行一系列的屏幕适配,解析出来的将是原始大小的图 
2.decodeResource在解析时会对Bitmap根据当前设备屏幕像素密度densityDpi的值进行缩放适配操作,使得解析出来的Bitmap与当前设备的分辨率匹配,达到一个最佳的显示效果,并且Bitmap的大小将比原始的大
 

经过上面的分析,我们可以得出Bitmap优化的思路: 
1、BitmapConfig的配置 
2、使用decodeFile、decodeResource、decodeStream进行解析Bitmap时,配置inDensity和inTargetDensity,两者应该相等,值可以等于屏幕像素密度*0.75f 
3、使用inJustDecodeBounds预判断Bitmap的大小及使用inSampleSize进行压缩 
4、Bitmap的回收
 

所以我们根据以上的思路,我们将Bitmap优化的策略总结为以下4种: 
1.对图片质量进行压缩 
2.对图片尺寸进行压缩 

3.采样率压缩
4.使用libjpeg.so库进行压缩

Bitamp质量压缩

通过算法抠掉(同化)了图片中的一些某个些点附近相近的像素,达到降低质量介绍文件大小的目的。减小了图片质量,缩小文件大小。
通过Bitmap.compress(Bitmap.CompressFormat.JPEG, options, baos);方式降低图片质量

     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;  
        

尺寸压缩

public static void compressBitmapToFileBySize(Bitmap bmp,File file)
        //压缩尺寸倍数,值越大,图片的尺寸就越小
        int ratio = 4;
        //压缩Bitmap到对应尺寸
        Bitmap result = Bitmap.createBitmap(bmp.getWidth()/ratio, bmp.getHeight()/ratio, Bitmap.Config.ARGB_8888);
        
        Canvas canvas = new Canvas(result);
        RectF rect = new RectF(0, 0, bmp.getWidth()/ratio, bmp.getHeight()/ratio);
        canvas.drawBitmap(bmp, null, rect , null);
        
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        //把压缩后的数据存放到baos中
        result.compress(Bitmap.CompressFormat.JPEG, 100, baos);
        try 
            FileOutputStream fos = new FileOutputStream(file);
            fos.write(baos.toByteArray());
            fos.flush();
            fos.close();
         catch (Exception e) 
            e.printStackTrace();
        
        
   

采样率压缩

/**
* 设置图片的采样率,降低图片像素
* @param filePath
* @param file
*/
  public static void compressBitmap(String filePath, File file)
            // 数值越高,图片像素越低
            int inSampleSize = 8;
            BitmapFactory.Options options = new BitmapFactory.Options();
            options.inJustDecodeBounds = false;
            //options.inJustDecodeBounds = true;//为true的时候不会真正加载图片,而是得到图片的宽高信息。
            //采样率,必须是2的倍数
            options.inSampleSize = inSampleSize;
            Bitmap bitmap = BitmapFactory.decodeFile(filePath, options);

            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            // 把压缩后的数据存放到baos中
            bitmap.compress(Bitmap.CompressFormat.JPEG, 100 ,baos);
            try 
                if(file.exists())
                
                    file.delete();
                
                else 
                    file.createNewFile();
                
                FileOutputStream fos = new FileOutputStream(file);
                fos.write(baos.toByteArray());
                fos.flush();
                fos.close();
             catch (Exception e) 
                e.printStackTrace();
            

libjpeg.so库压缩

libjpeg是广泛使用的开源JPEG图像库,安卓也依赖libjpeg来压缩图片。但是安卓并不是直接封装的libjpeg,而是基于了另一个叫Skia的开源项目来作为的图像处理引擎。Skia是谷歌自己维护着的一个大而全的引擎,各种图像处理功能均在其中予以实现,并且广泛的应用于谷歌自己和其它公司的产品中(如:Chrome、Firefox、 android等)。Skia对libjpeg进行了良好的封装,基于这个引擎可以很方便为操作系统、浏览器等开发图像处理功能


Bitmap图片复用机制

Android提供的图片复用机制
inBitmap图片复用的思路是:当生成的Bitmap不再使用时,将不再使用的Bitmap缓存起来,需要生成新Bitmap时,不再分配新内存,而是,直接拿符合条件缓存的Bitmap修改其数据,之后,将新生成的Bitmap给业务方使用,这样就避免了新Bitmap的创建。

根据官方的建议,在 Android 2.3 及以下的版本中建议使用 recycle() 回收内存,防止 OOM. 但是,使用这个方法的前提是需要确保这个位图不再被使用,否则回收之后再使用将会导致运行时错误。所以,官方的建议是通过引用计数的方式统计位图的引用,只有当位图不再被引用的时候再真正调用该方法进行回收。

官方文档参考:https://developer.android.com/topic/performance/graphics/manage-memory

在 Android 3.0 上面引入了 BitmapFactory.Options.inBitmap 字段。如果设置了此选项,那么采用 Options 对象的解码方法会在加载内容时尝试重复使用现有位图。这样可以复用现有的 Bitmap,减少对象创建,从而减少发生 GC 的概率。不过,inBitmap 的使用方式存在某些限制。特别是在 Android 4.4(API 级别 19)之前,系统仅支持大小相同的位图。在 Android 4.4 之后的版本,只要内存大小不小于需求的 Bitmap 都可以复用。即 BitmapFactory.Options.inSampleSize 可以大于1 
下面是google关于inBitmap的介绍视频
​​​​​​​​​​​​​​https://www.youtube.com/watch?v=_ioFW3cyRV0&index=17&list=PLWz5rJ2EKKc9CBxr3BVjPTPoDPLdPIFCE


需要注意的是inBitmap只能在3.0以后使用。2.3上,bitmap的数据是存储在native的内存区域,并不是在Dalvik的内存堆上。

使用inBitmap,在4.4之前,只能重用相同大小的bitmap的内存区域,而4.4之后你可以重用任何bitmap的内存区域,只要这块内存比将要分配内存的bitmap大就可以。这里最好的方法就是使用LRUCache来缓存bitmap,后面来了新的bitmap,可以从cache中按照api版本找到最适合重用的bitmap,来重用它的内存区域。

  BitmapFactory.Options options = new BitmapFactory.Options();
        // 只解码出 outxxx参数 比如 宽、高
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeResource(resources,id,options);
        //根据宽、高进行缩放
        int w = options.outWidth;
        int h = options.outHeight;
        //设置缩放系数
        options.inSampleSize = calcuteInSampleSize(w,h,maxW,maxH);
        if (!hasAlpha)
            options.inPreferredConfig = Bitmap.Config.RGB_565;
        
        options.inJustDecodeBounds = false;
        //设置成能复用
        options.inMutable=true;
        options.inBitmap=reusable;

使用图片缓存

android中有一个LruCache是基于最记最少使用算法实现的一个线程安全的数据缓存类,当超出设定的缓存容量时,优先淘汰最近最少使用的数据LruCache的LRU缓存策略是利用LinkedHashMap来实现的,并通过封装get/put等相关方法来实现控制缓存大小以及淘汰元素,但不支持为null的key和value。 我们可以使用JakeWharton提供的一个开源库github.com/JakeWharton… 来实现我们图片缓存的逻辑


​​​​​​​

 

以上是关于内存优化二Bitmap优化的主要内容,如果未能解决你的问题,请参考以下文章

内存优化二Bitmap优化

Android内存优化五:Bitmap优化

Android 进阶——性能优化之Bitmap位图内存管理及优化概述

Android 进阶——性能优化之Bitmap位图内存管理及优化概述

Android 进阶——性能优化之Bitmap位图内存管理及优化概述

Android 进阶——性能优化之Bitmap位图内存管理及优化概述