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;
}
网友评论