Android图片的加载与压缩

目录

  • 图片加载
    • 图片压缩
    • Bitmap(位图)的压缩
      • 色位深度
      • 图片文件与Bitmap
    • 压缩--位图读取
    • 压缩--位图写入
      • 对compress的使用反思
      • 注意事项

图片加载

在了解图片压缩前,先简单介绍常用的几种图片加载 方式,在BitmapFactory类中提供了decodeFiledecodeResourcedecodeStreamdecodeByteArraydecodeFileDescriptor几种静态方法,通过使用这些静态方法可以将具体的文件、流、比特数组、文件描述符加载成为Bitmap对象。

图片压缩

老实说“图片压缩”这个词,太过于含糊;

  1. 在运行过程中考虑到Bitmap内存问题,则需要在对Bitmap(位图对象)是进行压缩(通常通过设置色阶深度或Bitmap高宽来实现)。

  2. 在传输或存储过程中为了降低图片文件(最终的.jpg.jpeg)大小,会通过舍弃部分图片信息进行图片的压缩(通常指质量压缩),也就是有损压缩,然后减少文件的大小。
    前一种压缩和后一种压缩往往被混淆一谈。

Bitmap(位图)的压缩

在介绍Bitmap的压缩前,应该了解Bitmap的内存占用,这样才能更好地去理解Bitmap的压缩。

色位深度

首先,需要了解色阶深度。
屏幕上的每个像素点,在内存中都有一块空间用来描述该像素点的颜色信息。同样每张显示在屏幕上的图像,都有一个对应的Bitmap对象用来存储图像上每个像素点的信息,所以在Bitmap中有一块大小为:(图像像素高 x 图像像素宽 x 描述单个像素点需要的位)的空间去存储图像中所有像素点的信息。描述单个像素点需要的位数空间指的就是色位深度。Bitmap中使用Bitmap.Config来决定色位深度,目前Config有四个值:ALPHA_8、RGB_565、ARGB_4444、ARGB_8888。
(A:透明度 、R:红、G:绿、B:蓝)。

Config 每个像素占用的字节 说明
ALPHA_8 1 bytes 每个像素仅仅储存透明度通道
RGB_565 2 bytes 每个像素的RGB通道会保存,透明度不会保存,红色通道5位,有25=32种表现形式;绿色通道6位,有26=64种表现形式;蓝色通道5位,有2^5=32种表现形式
ARGB_4444 2 bytes 每个像素的ARGB通道都会保存,透明度/红色/绿色/蓝色通道4位,有2^4=16种表现形式
ARGB_8888 4 bytes 每个像素的ARGB通道都会保存,透明度/红色/绿色/蓝色通道8位,有2^8=256种表现形式

图片文件与Bitmap

Android中常用的图片文件通常以.png.jpeg.jpg格式进行存储。png文件是一种无损压缩的位图图像存储格式, 它利用特殊的编码方法标记重复出现的数据,而jpgjpeg则是有损压缩的位图图像存储格式。在每个像素点的描述上png图片要多一个alpa透明通道,当时所以相同高宽和清晰度的图像,保存为png文件的文件大小不一定要比jpgjpeg文件要大。
此处强烈推荐《你的bitmap究竟有多大》
通过调用BitmapFactory的图片文件解析加载函数可将图片文件加载进入内存当中成为Bitmap对象,由于pngjpgjpeg图片文件均是Bitmap压缩以后写入的文件,所以通常Bitmap对象的大小大于文件大小。

压缩–位图读取

由于Bitmap所占内存的大小主要取决于图片宽度图片高度色位深度图片所在文件夹设备像素密度,故而在加载图片时为避免OOM的产生,可通过缩减宽高与减少色位深度的bit位数来达到压缩Bitmap所需空间。

  1. 通过设置加载选项BitmapFactory.OptionsinSampleSize实现高宽等比缩小达到压缩效果,在第一次解析码时,并不需要将文件全部解码成Bitmap对象,这里只需要Bitmap的原始高宽,所以Options.isJustDecodeBounds设置为true。在计算得到合适的比例后,再设置采样率参数inSampleSize来得到适当大小的bitmap对象;
    /**
    * 取样(尺寸等比)压缩,由于reqWidth和reqHeight通常取
    * ImageView的大小,为了使图片将ImageView填充满避免出现
   * 黑色空白区域,故而选取小比例作为压缩比值
    *
    * @param resources
    * @param resId
    * @param reqWidth request width
    * @param reqHeight request height
    * */
   public static Bitmap compressInSampleSize(Resources resources, int resId, int >reqWidth, int reqHeight) {
       BitmapFactory.Options options = new BitmapFactory.Options();
       options.inJustDecodeBounds = true;
       Bitmap bitmap = BitmapFactory.decodeResource(resources, resId, options);
       options.inSampleSize = getInSampleSize(bitmap, reqWidth, reqHeight);
       options.inJustDecodeBounds = false;
       bitmap.recycle();
       Bitmap resultBitmap = BitmapFactory.decodeResource(resources, resId, options);
       return resultBitmap;
   }
   private static int getInSampleSize(Bitmap bitmap, int reqWidth, int reqHeight){
       int bitmapWidth = bitmap.getWidth();
       int bitmapHeight = bitmap.getHeight();
       int inSampleSize = 1;
       //若希望更改比例选择策略,将'&&'改为'||'即可
       while (bitmapWidth/inSampleSize > reqWidth && bitmapHeight/inSampleSize > reqHeight){
           inSampleSize *= 2;
       }
       return inSampleSize;
   }
  1. 通过设置色位深度减少Bitmap的占用内存
    /**
   * 选择色阶深度加载图片
   *
   * @param resources
   * @param resId
   * @param config Color order depth
   * */
  public static Bitmap compressInPixColorBit(Resources resources, int resId, Bitmap.Config config) {
      BitmapFactory.Options options = new BitmapFactory.Options();
      options.inPreferredConfig = config == null? Bitmap.Config.RGB_565 : config;
      Bitmap resultBitmap = BitmapFactory.decodeResource(resources, resId, options);
      return resultBitmap;
  }

高宽等比缩小与设置色位深度实现内存占用降低可以联合使用,使Bitmap占用内存更低,但同时也意味着更多信息的缺失,例如:如果采用RGB_565则图片将没有透明度通道。

压缩–位图写入

网上很多博客均将质量压缩作为图片加载防止OOM的一种解决方案,然而这是对“质量压缩”的误解。他们给出的质量压缩方案,通常如下:

    /**
     * 质量压缩
     *
     * @param bitmap
     * @param targetSize target size equal to targetSize(kb)
     * */
    public static Bitmap compressInQuality(Bitmap bitmap, int targetSize) {
        ByteArrayOutputStream resultBitmapStream = new ByteArrayOutputStream();
        int quality = 100;
        do{
            resultBitmapStream.reset();
            bitmap.compress(Bitmap.CompressFormat.JPEG, quality, resultBitmapStream);
            quality -= 10;
        }while (resultBitmapStream.toByteArray().length/1024 > targetSize  && quality > 0);
        bitmap.recycle();
        return BitmapFactory.decodeStream(new ByteArrayInputStream(resultBitmapStream.toByteArray()));
    }

然后,如果把这个理解成“可以按质量quality压缩bitmap对象”便是大错特错。直接使用这样的方案来试图降低Bitmap非但达不到效果,还会使内存提前用完。

对compress的使用反思

  1. 为何compress这样处理得到的OutputStream,再经过解析后不能达到降低内存的效果内?
    compress是按照压缩算法(算法根据第一个参数而定,PNG则是采用无损压缩算法,JPEG则是采用有损压缩算法)对bitmap对象中的信息进行压缩然后写入OutputStream,具体压缩率则取决于quality(0~100,当采用无损压缩PNG时,此参数被忽略),但是此时的bitmap对象任然是原来的bitmap对象,当使用decodeStreamcompress产生的OutputStream进行解析时,解析得到的新Bitmap高宽与原来的Bitmap高宽相等,不选择色位深度的话,则时默认色位深度,新的Bitmap的内存占用甚至更高,所以这种处理方式会在内存中存在两个bitmap对象,还多申请了一份ByteArrayOutputStream空间。
  2. 那质量压缩compress什么时候用呢?
    compress只是将一个bitmap对象的信息压缩进一个OutputStream,如果选择有损压缩则在压缩的过程中还会选择性放弃一些图片信息,无损压缩则会保留图片的全部信息,但是无损压缩后的文件任然会比Bitmap在内存中占用的内存大小要小。所以,在网络传输或者在存储时使用compress显然是可行的。同时,如果Bitmap是再加载过程中使用了压缩,在compress使用完成以后,由于Bitmap所含信息减少,质量压缩得到的数据流将会变得更小。对数据流进行解析的得到的Bitmap的内存占用也随之降低。

注意事项

  • 图片加载需要考虑是否需要缓存
  • 在对文件流进行解析时,应考虑是否一步解析,如果需要多步读取流,应考虑到文件流的有序性,经过读取后,流的起始位置发生变化,导致再次读取为null,此时建议使用文件描述符进行操作
  • 位图压缩读取与压缩写入为IO操作,不应在主线程中进行

你可能感兴趣的:(Android,Android基础)