Skip to content

Instantly share code, notes, and snippets.

@s1ntoneli
Last active March 7, 2018 04:57
Show Gist options
  • Select an option

  • Save s1ntoneli/85ab0caba7d034e0bbcde22315dd7dd7 to your computer and use it in GitHub Desktop.

Select an option

Save s1ntoneli/85ab0caba7d034e0bbcde22315dd7dd7 to your computer and use it in GitHub Desktop.
Bitmap 处理技巧

不加载进内存,获取 bitmap 信息

BitmapFactory这个类提供了多个解析方法(decodeByteArray, decodeFile, decodeResource等)用于创建Bitmap对象。 每一种解析方法都提供了一个可选的BitmapFactory.Options参数,将这个参数的inJustDecodeBounds属性设置为true就可以让解析方法禁止为bitmap分配内存,返回值也不再是一个Bitmap对象,而是null。虽然Bitmap是null了,但是BitmapFactory.Options的outWidth、outHeight和outMimeType属性都会被赋值。这个技巧让我们可以在加载图片之前就获取到图片的长宽值和MIME类型,从而根据情况对图片进行压缩。

BitmapFactory.Options options = new BitmapFactory.Options();  
options.inJustDecodeBounds = true;  
BitmapFactory.decodeResource(getResources(), R.id.myimage, options);  
int imageHeight = options.outHeight;  
int imageWidth = options.outWidth;  
String imageType = options.outMimeType;  

进行图片尺寸压缩

通过设置BitmapFactory.Options中inSampleSize的值就可以实现。 通过目标尺寸获取合适的 inSampleSize 值。

public static int calculateInSampleSize(BitmapFactory.Options options,  
        int reqWidth, int reqHeight) {  
    // 源图片的高度和宽度  
    final int height = options.outHeight;  
    final int width = options.outWidth;  
    int inSampleSize = 1;  
    if (height > reqHeight || width > reqWidth) {  
        // 计算出实际宽高和目标宽高的比率  
        final int heightRatio = Math.round((float) height / (float) reqHeight);  
        final int widthRatio = Math.round((float) width / (float) reqWidth);  
        // 选择宽和高中最小的比率作为inSampleSize的值,这样可以保证最终图片的宽和高  
        // 一定都会大于等于目标的宽和高。  
        inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;  
    }  
    return inSampleSize;  
}  

获取压缩后的bitmap


public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,  
        int reqWidth, int reqHeight) {  
    // 第一次解析将inJustDecodeBounds设置为true,来获取图片大小  
    final BitmapFactory.Options options = new BitmapFactory.Options();  
    options.inJustDecodeBounds = true;  
    BitmapFactory.decodeResource(res, resId, options);  
    // 调用上面定义的方法计算inSampleSize值  
    options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);  
    // 使用获取到的inSampleSize值再次解析图片  
    options.inJustDecodeBounds = false;  
    return BitmapFactory.decodeResource(res, resId, options);  
}  

分块加载

使用 BitmapRegionDecoder 分块加载图片。

使用 LruCache 处理大量图片

// 获取到可用内存的最大值,使用内存超出这个值会引起OutOfMemory异常。  
    // LruCache通过构造函数传入缓存值,以KB为单位。  
    int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);  
    // 使用最大可用内存值的1/8作为缓存的大小。  
    int cacheSize = maxMemory / 8;  
    mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {  
        @Override  
        protected int sizeOf(String key, Bitmap bitmap) {  
            // 重写此方法来衡量每张图片的大小,默认返回图片数量。  
            return bitmap.getByteCount() / 1024;  
        }  
    };  

bitmap的回收

Android3.0之前,Bitmap的像素级数据是存储在native内存上,而Bitmap对象本身是存储在Java虚拟机堆中。在native内存中的像素数据的释放是不可预知的,native内存的增加也会算在堆上,从而容易使用程序崩溃。对于一张确定不再使用的bitmap,要调用recycle()方法并把对象设置为null值。所以对于Android3.0之前的手机,通过DDMS观看Heap信息的时候不显示native部分分配的内存大小,如图所示,加载了一张7M多的图片,但是显示分配Allocated才2M多。但是native分配的内存大小是算在heap上的,所以当heap大小显示的不是HeapMaxSize的时候,也有可能OOM。

Android3.0之后,Bitmap像素数据和它对象本身都存储在Java虚拟机的堆内存中,受GC管理的内存,可以通过GC回收。因此调用recycle()并不会加速bitmap的像素级内存的回收。

为了更有效的利用内存,Android3.0起引入BitmapFactory.Options.inBitmap,如果设置了该属性,那么当使用了带有该 Options 参数的 decode 方法在加载内容时,decode 方法会尝试重用一个已经存在的位图。这就意味着位图内存已经被重用了,从而性能得到了改善,并且移除了内存的分配和解除分配。在Android4.4之前,可重用bitmap的条件是宽,高相等,并且计算出来的inSampleSize为1。到了Android4.4,可重用的条件变为新bitmap的大小应该小于或等于被重用bitmap的getAllocationByteCount值。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment