介绍

Bitmap是Android系统中的图像处理的最重要类之一。用它可以获取图像文件信息,进行图像剪切、旋转、缩放等操作,并可以指定格式保存图像文件。本文从应用的角度,着重介绍怎么用Bitmap来实现这些功能。

Bitmap的存储可以说包括两个部分,像素以及长,宽,颜色等描述信息。像素是Bitmap最占用内存的地方,长宽和像素位数是用来描述图片的。 
Bitmap官方API文档:

Bitmap.CompressFormat
 Specifies the known formats a bitmap can be compressed into 
 enum Bitmap.Config Possible bitmap configurations.

其中CompressFormat有JPE、GPN、GWEBP;Config如下:

Bitmap.Config ALPHA_8
 Each pixel is stored as a single translucency (alpha) channel.  
Bitmap.Config ARGB_4444 This field was deprecated in API level 13. Because of the poor quality 
 of this configuration, it is advised to use ARGB_8888 instead. 
Bitmap.Config ARGB_8888 Each pixel is stored on 4 bytes. 
Bitmap.Config RGB_565 
 Each pixel is stored on 2 bytes and only the RGB channels are encoded: 
 red is stored with 5 bits of precision (32 possible values), green is 
 stored with 6 bits of precision (64 possible values) and blue is 
 stored with 5 bits of precision.

ARGB—Alpha,Red,Green,Blue 
一种色彩模式,也就是RGB色彩模式附加上Alpha(透明度)通道

图片缩放

在展示高分辨率图片的时候,最好先将图片进行压缩,从而防止OOM的出现。主要有两种方法:

  • inSampleSize(采样率) 
    优点:效率较高,解析速度快 
    缺点:采样率inSampleSize的取值只能是2的次方数(如:inSampleSize=15,实际取值为8),因此该方法不能精确的指定图片的大小

int inSampleSize
 If set to a value > 1, requests the decoder to subsample the original 
 image, returning a smaller image to save memory. The sample size is 
 the number of pixels in either dimension that correspond to a single 
 pixel in the decoded bitmap. For example, inSampleSize == 4 returns an 
 image that is 1/4 the width/height of the original, and 1/16 the 
 number of pixels. Any value <= 1 is treated the same as 1. Note: the 
 decoder uses a final value based on powers of 2, any other value will 
 be rounded down to the nearest power of 2.
  • Matrix 
    优点:可以精确地指定图片的缩放大小 
    缺点:是在原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);  
}
public static int calculateInSampleSize(BitmapFactory.Options options,
        int reqWidth, int reqHeight) {
    if (reqWidth == 0 || reqHeight == 0) {
        return 1;
    }
    final int height = options.outHeight;
    final int width = options.outWidth;
    Log.d(TAG, "origin, w= " + width + " h=" + height);
    int inSampleSize = 1;
    //保证最终图片的宽和高一定都会大于等于目标的宽和高
    if (height > reqHeight || width > reqWidth) {
        final int halfHeight = height / 2;
        final int halfWidth = width / 2;
        while ((halfHeight / inSampleSize) >= reqHeight
                && (halfWidth / inSampleSize) >= reqWidth) {
            inSampleSize *= 2;
        }
    }
    Log.d(TAG, "sampleSize:" + inSampleSize);
    return inSampleSize;
}
//主程序调用示例
mImageView.setImageBitmap(decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100)); 

Matrix缩放方法

public static Bitmap zoomImage(Bitmap bgimage, double newWidth,
        double newHeight) {
    // 获取这个图片的宽和高
    float width = bgimage.getWidth();
    float height = bgimage.getHeight();
    // 创建操作图片用的matrix对象
    Matrix matrix = new Matrix();
    // 计算宽高缩放率
    float scaleWidth = ((float) newWidth) / width;
    float scaleHeight = ((float) newHeight) / height;
    // 缩放图片动作
    matrix.postScale(scaleWidth, scaleHeight);
    Bitmap bitmap = Bitmap.createBitmap(bgimage, 0, 0, (int) width,
            (int) height, matrix, true);
    Log.v("缩放后的bitmap占用内存:", bitmap.getByteCount() / 1024 + "k");
    return bitmap;
}

图片缓存

使用内存缓存技术来对图片进行缓存,从而在加载很多图片的时候可以提高响应速度和流畅性。 
LruCach适合用来缓存图片,它的主要算法原理是把最近使用的对象用强引用存储在 LinkedHashMap中,并且把最近最少使用的对象在缓存值达到预设定值之前从内存中移除。

不推荐使用软引用或弱引用方式来缓存,因为从 Android 2.3 (API Level 9)开始,垃圾回收器会更倾向于回收持有软引用或弱引用的对象,这让软引用和弱引用变得不再可靠

private LruCache<String, Bitmap> mMemoryCache;  
@Override  
protected void onCreate(Bundle savedInstanceState) {  
    // 获取到可用内存的最大值,使用内存超出这个值会引起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;  
        }  
    };  
}  
public void addBitmapToMemoryCache(String key, Bitmap bitmap) {  
    if (getBitmapFromMemCache(key) == null) {  
        mMemoryCache.put(key, bitmap);  
    }  
}  
public Bitmap getBitmapFromMemCache(String key) {  
    return mMemoryCache.get(key);  
}  
public void loadBitmap(int resId, ImageView imageView) {  
    final String imageKey = String.valueOf(resId);  
    final Bitmap bitmap = getBitmapFromMemCache(imageKey);  
    if (bitmap != null) {  
        imageView.setImageBitmap(bitmap);  
    } else {  
        imageView.setImageResource(R.drawable.image_placeholder);  
        BitmapWorkerTask task = new BitmapWorkerTask(imageView);  
        task.execute(resId);  
    }  
}  
class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {  
    // 在后台加载图片
    @Override  
    protected Bitmap doInBackground(Integer... params) {  
        final Bitmap bitmap = decodeSampledBitmapFromResource(  
                getResources(), params[0], 100, 100);  
        addBitmapToMemoryCache(String.valueOf(params[0]), bitmap);  
        return bitmap;  
    }  

参考

官方文档 
https://developer.android.com/training/displaying-bitmaps/load-bitmap.html 
https://developer.android.com/reference/android/graphics/Bitmap.html