美文网首页计算机网络学习Android开发探索性能优化
Android玩转Bitmap随意裁剪支持碎片复用

Android玩转Bitmap随意裁剪支持碎片复用

作者: 生椰拿铁锤 | 来源:发表于2018-02-28 22:23 被阅读409次

    Bitmap碎片复用任意操作开源库:https://github.com/Dawish/BitmapKit

    一、Bitmap庐山真面目

    Bitmap 并不是一个图片。最开始接触Android的人可能以为 Bitmap 就一个图片,比如我,哈哈哈,我最开始接触Android时就是如此认为。

    一句话总结:

    Bitmap 是一个实现了 Parcelable 接口的 final 类,不能用 new 关键字来创建一个Bitmap,
    Bitmap中的 java 功能方法基本都是调用native实现的。

    Bitmap 这个单词分开看就是 bit 和 map ,就好像我们java中的HashMap这样,只是一个特殊的数据保存类。

    Android中的Bitmap类是Android界面绘制数据存储类,我们的 Canvas 把绘制的东西保存在Bitmap中,然后交给底层去渲染,由于需要在内存中传递Bitmap,所以这也是实现了Parcelable 接口的原因。

    二、Bitmap复用

    2.1 不同版本存储区别:

    Android 2.3.3及以前版本,bitmap的像素点数据是保存在native memory,而bitmap对象是保存在Dalvik heap, 从Android 3.0开始,像素点数据与bitmap对象一起存储在Dalvik heap中。

    前两天看到最新的Android O把Bitmap的像素点数据又放在了native memory中了。
    可以参考文章:https://www.cnblogs.com/xiaji5572/p/7794083.html

    2.2 不同版本复用区别:

    在Android 3.0开始引入了inBitmap设置,通过设置这个参数,在图片加载的时候可以使用之前已经创建了的Bitmap,但是需要大小一样,以便节省内存,避免再次创建一个Bitmap。在Android4.4,新增了允许inBitmap设置的图片与需要加载的图片的大小不同的情况,只要inBitmap的图片比当前需要加载的图片 就好了。

    2.3 Bitmap复用注意点:

    Bitmap复用首选需要其 mIsMutable 属性为 true , mIsMutable 的表面意思为:易变的

    Bitmap中的意思为: 控制bitmap的setPixel方法能否使用,也就是外界能否修改bitmap的像素。mIsMutable 属性为 true 那么就可以修改Bitmap的像素数据,这样也就可以实现Bitmap对象的复用了。

    那么在创建一个Bitmap的时候这个 mIsMutable 属性怎么赋值成 true 呢?

    1. 使用 Bitmap 的 createBitmap 方法创建一个新的Bitmap时 mIsMutable 属性 默认为 true
    源码:

        public static Bitmap createBitmap(@Nullable DisplayMetrics display, int width, int height,
                @NonNull Config config, boolean hasAlpha, @NonNull ColorSpace colorSpace) {
    
               ......略
    
            // nullptr color spaces have a particular meaning in native and are interpreted as sRGB
            // (we also avoid the unnecessary extra work of the else branch)
            if (config != Config.ARGB_8888 || colorSpace == ColorSpace.get(ColorSpace.Named.SRGB)) {
                
                /**********第八个mutable属性直接赋值为true**********/
                bm = nativeCreate(null, 0, width, width, height, config.nativeInt, true, null, null);
            } else {
                if (!(colorSpace instanceof ColorSpace.Rgb)) {
                    throw new IllegalArgumentException("colorSpace must be an RGB color space");
                }
                ColorSpace.Rgb rgb = (ColorSpace.Rgb) colorSpace;
                ColorSpace.Rgb.TransferParameters parameters = rgb.getTransferParameters();
                if (parameters == null) {
                    throw new IllegalArgumentException("colorSpace must use an ICC "
                            + "parametric transfer function");
                }
    
                ColorSpace.Rgb d50 = (ColorSpace.Rgb) ColorSpace.adapt(rgb, ColorSpace.ILLUMINANT_D50);
    
               /**********第八个mutable属性直接赋值为true**********/
                bm = nativeCreate(null, 0, width, width, height, config.nativeInt, true,
                        d50.getTransform(), parameters);
            }
    
               ......略
    
    }
    

    2. 使用BitmapFactory结合 Options 去decode一个Bitmap
    在设置解码属性Options的时候可以将 inMutable 设置为true

     1 final BitmapFactory.Options options = new BitmapFactory.Options();
     2         //size必须为1 否则是使用inBitmap属性会报异常
     3         options.inSampleSize = 1;
     4         //这个属性一定要在用在src Bitmap decode的时候 不然你再使用哪个inBitmap属性去decode时候会在c++层面报异常
     5         //BitmapFactory: Unable to reuse an immutable bitmap as an image decoder target.
     6         options.inMutable = true;
                //获得一个Bitmap对象inBitmap2 
     7         inBitmap2 = BitmapFactory.decodeFile(path1,options);
                //使用对象inBitmap2 
     8         iv.setImageBitmap(inBitmap2);
                //使用完了对象inBitmap2 后拿去复用此对象解码新的Bitmap
     9         options.inBitmap = inBitmap2;
    10         long start=System.currentTimeMillis();
    11         iv2.setImageBitmap(BitmapFactory.decodeFile(path2,options));
    12         iv3.setImageBitmap(BitmapFactory.decodeFile(path3,options));
    13         iv4.setImageBitmap(BitmapFactory.decodeFile(path4,options));
    

    这种复用的方式都写在注释里了。

    2.4 Bitmap复用实现:

    1. BitmapFactory解码图片时使用缓存的bitmap
    在上面的代码中可以看到,从缓存中获取一个可以被复用的bitmap在使用BitmapFactory去解码是把options.inBitmap赋值成这个bitmap,这样就把这个bitmap复用了。

    2. 需要一个空的bitmap时可以从缓存中获取
    可以把用完了的bitmap放在 LruCache 缓存中,比如我们在使用 Canvas 需要射中一个 bitmap,这样我们就可以从 LruCache 缓存获取一个可以被复用的 bitmap 设置给 Canvas

    github上已经有一个开源的bitmap复用项目 https://github.com/amitshekhariitbhu/GlideBitmapPool,使用非常简单。

    GlideBitmapPool开源项目用法大致如下:

    Using Glide Bitmap Pool in your application

    Add this in your build.gradle

    compile 'com.amitshekhar.android:glide-bitmap-pool:0.0.1'
    

    Then initialize it in onCreate() Method of application class, :

    GlideBitmapPool.initialize(10 * 1024 * 1024); // 10mb max memory size
    

    Decoding the bitmap from file path

    Bitmap bitmap = GlideBitmapFactory.decodeFile(filePath);
    

    Decoding the bitmap from resources

    Bitmap bitmap = GlideBitmapFactory.decodeResource(getResources(), R.drawable.testImage);
    

    Decoding the down sample bitmap

    Bitmap bitmap = GlideBitmapFactory.decodeFile(filePath,100,100);
    

    Making the bitmap available for recycle or reuse

    GlideBitmapPool.putBitmap(bitmap);
    

    Getting the empty bitmap from the pool

    Bitmap bitmap = GlideBitmapPool.getBitmap(width, height, config);
    

    Clearing or Trimming Memory

    GlideBitmapPool.clearMemory();
    GlideBitmapPool.trimMemory(level);
    

    三、Bitmap碎片复用的情况下任意裁剪

    这里说的碎片复用就是在图片的裁剪过程中创建丢弃大量的Bitmap对象,如果不对这些Bitmap进行复用会造成多余的内存浪费,造成内存抖动

    3.1 Bitmap裁剪保留下部分:

    说明 前后效果对比
    裁剪保留下部分,取一半高度 TIM图片20180228220755.png 裁剪后: Screenshot_2018-02-28-21-59-16-052_BitmapKit.png

    裁剪代码:

        /**
         * 裁剪一定高度保留下面
         * @param srcBitmap
         * @param needHeight
         * @param recycleSrc 是否回收原图
         * @return
         */
        @DebugLog
        public static Bitmap cropBitmapBottom(Bitmap srcBitmap, int needHeight, boolean recycleSrc) {
    
            Log.d("danxx", "cropBitmapBottom before h : "+srcBitmap.getHeight());
    
            /**裁剪保留下部分的第一个像素的Y坐标*/
            int needY = srcBitmap.getHeight() - needHeight;
    
            /**裁剪关键步骤*/
            Bitmap cropBitmap = Bitmap.createBitmap(srcBitmap,0,needY,srcBitmap.getWidth(),needHeight);
    
            Log.d("danxx", "cropBitmapBottom after h : "+cropBitmap.getHeight());
    
            /**回收之前的Bitmap*/
            if (recycleSrc && srcBitmap != null && !srcBitmap.equals(cropBitmap) && !srcBitmap.isRecycled()) {
                GlideBitmapPool.putBitmap(srcBitmap);
            }
    
            return cropBitmap;
        }
    

    3.2 Bitmap裁剪保留左部分:

    说明 前后效果对比
    裁剪保留左部分,取一半宽度 TIM图片20180228220755.png 裁剪后: Screenshot_2018-02-28-21-59-34-089_BitmapKit.png

    裁剪代码:

        /**
         * 裁剪一定高度保留左边
         * @param srcBitmap
         * @param needWidth
         * @return
         */
        @DebugLog
        public static Bitmap cropBitmapLeft(Bitmap srcBitmap, int needWidth, boolean recycleSrc) {
    
            Log.d("danxx", "cropBitmapLeft before w : "+srcBitmap.getWidth());
    
            /**裁剪关键步骤*/
            Bitmap cropBitmap = Bitmap.createBitmap(srcBitmap, 0, 0, needWidth, srcBitmap.getHeight());
    
            Log.d("danxx", "cropBitmapLeft after w : "+cropBitmap.getWidth());
    
            /**回收之前的Bitmap*/
            if (recycleSrc && srcBitmap != null && !srcBitmap.equals(cropBitmap) && !srcBitmap.isRecycled()) {
                GlideBitmapPool.putBitmap(srcBitmap);
            }
    
            return cropBitmap;
        }
    

    3.3 Bitmap裁剪保留右部分:

    说明 前后效果对比
    裁剪保留右部分,取一半宽度 TIM图片20180228220755.png 裁剪后: Screenshot_2018-02-28-22-00-03-095_BitmapKit.png

    裁剪代码:

        /**
         * 裁剪一定高度保留左边
         * @param srcBitmap
         * @param needWidth
         * @return
         */
        @DebugLog
        public static Bitmap cropBitmapRight(Bitmap srcBitmap, int needWidth, boolean recycleSrc) {
    
            Log.d("danxx", "cropBitmapRight before w : "+srcBitmap.getWidth());
    
            int needX = srcBitmap.getWidth() - needWidth;
    
            /**裁剪关键步骤*/
            Bitmap cropBitmap = Bitmap.createBitmap(srcBitmap, needX, 0, needWidth, srcBitmap.getHeight());
    
            Log.d("danxx", "cropBitmapRight after w : "+cropBitmap.getWidth());
    
            /**回收之前的Bitmap*/
            if (recycleSrc && srcBitmap != null && !srcBitmap.equals(cropBitmap) && !srcBitmap.isRecycled()) {
                GlideBitmapPool.putBitmap(srcBitmap);
            }
    
            return cropBitmap;
        }
    
    

    3.4 Bitmap裁剪保留上部分:

    说明 前后效果对比
    裁剪保留上部分,取一半高度 TIM图片20180228220755.png 裁剪后: Screenshot_2018-02-28-21-59-49-769_BitmapKit.png

    裁剪代码:

       /**
         * 裁剪一定高度保留下面
         * @param srcBitmap
         * @param needHeight
         * @param recycleSrc 是否回收原图
         * @return
         */
        @DebugLog
        public static Bitmap cropBitmapTop(Bitmap srcBitmap, int needHeight, boolean recycleSrc) {
    
            Log.d("danxx", "cropBitmapBottom before h : "+srcBitmap.getHeight());
    
            /**裁剪保留上部分的第一个像素的Y坐标*/
            int needY = 0;
    
            /**裁剪关键步骤*/
            Bitmap cropBitmap = Bitmap.createBitmap(srcBitmap,0,needY,srcBitmap.getWidth(),needHeight);
    
            Log.d("danxx", "cropBitmapBottom after h : "+cropBitmap.getHeight());
    
            /**回收之前的Bitmap*/
            if (recycleSrc && srcBitmap != null && !srcBitmap.equals(cropBitmap) && !srcBitmap.isRecycled()) {
                GlideBitmapPool.putBitmap(srcBitmap);
            }
    
            return cropBitmap;
        }
    
    
    

    3.5 Bitmap指定参数任意裁剪:

    说明 前后效果对比
    指定参数任意裁剪 TIM图片20180228220755.png 裁剪后: Screenshot_2018-02-28-22-00-13-606_BitmapKit.png

    裁剪代码:

        /**
         * 自定义裁剪,根据第一个像素点(左上角)X和Y轴坐标和需要的宽高来裁剪
         * @param srcBitmap
         * @param firstPixelX
         * @param firstPixelY
         * @param needWidth
         * @param needHeight
         * @param recycleSrc
         * @return
         */
        @DebugLog
        public static Bitmap cropBitmapCustom(Bitmap srcBitmap, int firstPixelX, int firstPixelY, int needWidth, int needHeight, boolean recycleSrc) {
    
            Log.d("danxx", "cropBitmapRight before w : "+srcBitmap.getWidth());
            Log.d("danxx", "cropBitmapRight before h : "+srcBitmap.getHeight());
    
            if(firstPixelX + needWidth > srcBitmap.getWidth()){
                needWidth = srcBitmap.getWidth() - firstPixelX;
            }
    
            if(firstPixelY + needHeight > srcBitmap.getHeight()){
                needHeight = srcBitmap.getHeight() - firstPixelY;
            }
    
            /**裁剪关键步骤*/
            Bitmap cropBitmap = Bitmap.createBitmap(srcBitmap, firstPixelX, firstPixelY, needWidth, needHeight);
    
            Log.d("danxx", "cropBitmapRight after w : "+cropBitmap.getWidth());
            Log.d("danxx", "cropBitmapRight after h : "+cropBitmap.getHeight());
    
    
            /**回收之前的Bitmap*/
            if (recycleSrc && srcBitmap != null && !srcBitmap.equals(cropBitmap) && !srcBitmap.isRecycled()) {
                GlideBitmapPool.putBitmap(srcBitmap);
            }
    
            return cropBitmap;
        }
    
    

    Bitmap碎片复用任意操作开源库:https://github.com/Dawish/BitmapKit

    相关文章

      网友评论

      • DavyJones1:为什么不能new?
        生椰拿铁锤:@DavyJones1 从表面看,bitmap提供的唯一的一个构造方法并不是public的,所以你没办法new,这个构造方法是called from jni,是在ndk中被调用的,java中不能调用bitmap的构造方法。其实是bitmap的创建是在ndk层,只能调用提供的native方法去c层创建bitmap。不知道我的解释能否说清楚!

      本文标题:Android玩转Bitmap随意裁剪支持碎片复用

      本文链接:https://www.haomeiwen.com/subject/mwnjzxtx.html