Android图像介绍——Bitmap和BitmapFactory

一、 Bitmap 基本介绍

Bitmap也称为位图,是图片在内存中的表现形式,任何图片(JPEG, PNG, WEBP…)加载到内存后都是一个Bitmap对象。Bitmap实际是像素点的集合,假设它的宽高为width和height,那么该Bitmap就包含width*height个像素,它在内存中占用的内存就是(width*height*单个像素内存)。

为了减小图片在磁盘上所占的空间,将Bitmap保存到磁盘上时会进行压缩,图片的文件格式实际代表的是不同的压缩方式与压缩率,而将磁盘上的文件加载到内存中时就要进行解压缩。

1.1 图片格式介绍

常见的静态图片格式为JPEG、PNG和WEBP,它们有着不同的压缩方式,保存到本地后所占用的空间大小也不一样。

JPEG是一种有损压缩格式,以24位颜色压缩存储单个位图,但是不支持透明度。使用JPEG进行压缩时需要选择适当的压缩率,避免图片质量太差。

PNG是一种无损压缩格式,支持所有的颜色,由于是无损压缩,PNG一般用于APP图标这类对线条或者清晰度有要求的图片。由于PNG所占空间较大,目前一般将PNG转为WEBP使用。

WEBP支持无损压缩和有损压缩,并且他的无损压缩率优于PNG,有损压缩率优于JPEG,同时它支持所有颜色,并支持多帧动画,唯一的缺点是压缩效率不如JPEG和PNG。

1.2 Bitmap色深

Bitmap的本质就是像素点的集合,它通过描述每个像素的ARGB信息来形成整张图片,其中A表示透明度通道,RGB表示红绿蓝3种颜色通道,每个通道的值都在0-255之间,因此8bit可以完整地表示1个通道,那么4x8=32bit可以表示一个完整的像素。但如果每个Bitmap都使用32bit来表示一个像素的话,对内存来说是一个较大的负担,因此对于质量要求不高的Bitmap来说,可以采用较少的内存去表示一个像素。

色深指的是每一个像素点用多少bit来存储ARGB值,色深越大,图片的色彩越丰富,一般来说色深有8bit、16bit、32bit等,Bitmap.Config中的色深配置如下。

ALPHA_8: 这种方案只存储透明度通道,色深为8bit,使用场景特殊,比如设置遮盖效果等。 ARGB_8888: ARGB每个通道值采8bit来表示,色深为32bit,每个像素点需要4byte的内存来存储数据,图片质量高,所占内存大。 ARGB_4444: ARGB每个通道都是4位,色深为16bit,由于这种配置下的图片质量较差,Android官方已经将其标为弃用。 RGB_565: 色深为16bit,RGB通道值分别占5、6、5位,但是没有存储A通道值,所以不支持透明度,可用于对清晰度要求不高的照片。 RGBA_F16: 色深为64bit,该配置主要用于广色域与HDR内容。 HARDWARE: 这是一种特殊配置,该配置下,Bitmap会被存储在显存中,并且Bitmap是不可变的。这种配置仅适用于,当Bitmap唯一的操作就是将自己绘制在屏幕上时。

我们可以根据对图片质量的要求创建不同色深的Bitmap,如果对显示质量有较高的要求可以使用ARGB_8888,一般来说使用RGB_565即可,这也可以减少OOM的概率。

二、 Bitmap 

与Bitmap相关的类有2个,分别是Bitmap类和BitmapFactory类。

android.graphics.Bitmap

bitmap是Android系统中的图像处理的重要类之一,通过bitmap我们可以获取到图片的相关信息,bitmap文件图像效果好就需要占用越大存储空间;

android.graphics.BitmapFactory

BitmapFactory是一个创建Bitmap的工具类,为我们提供了从文件、流、byte数组中创建数组,在创建的时候,还为我们提供了一个内部类Options作为参数来控制Bitmap的创建,比如控制Bitmap的长和宽、像素的大小,是否只获取图片的一些信息(不加载图片数据,返回图片宽和高),是否在内存中复用等。

2.1 Bitmap的主要方法

方法描述

int getWidth()

获取位图的宽度

int getHeight()

获取位图的高度

boolean isMutable()

图片是否可修改

int getScaledWidth(Canvas canvas)

获取指定密度转换后的图像的宽度

int getScaledHeight(Canvas canvas)

获取指定密度转换后的图像的高度

boolean compress(CompressFormat format, int quality, OutputStream stream)

按指定的图片格式以及画质,将图片转换为输出流。

static Bitmap createBitmap(Bitmap src)

以src为原图生成不可变得新图像

public static Bitmap createScaledBitmap(Bitmap src, int dstWidth, int dstHeight, boolean filter)

以src为原图,创建新的图像,指定新图像的高宽以及是否可变。

static Bitmap createBitmap(int width, int height, Config config) 

创建指定格式、大小的位图

public static Bitmap createBitmap(Bitmap source, int x, int y, int width, int height)

以source为原图,创建新的图片,指定起始坐标以及新图像的高宽。

2.2 BitmapFactory的主要方法

方法描述

static Bitmap decodeFile(String pathName)

static Bitmap decodeFile(String pathName, Options opts)

从文件读取图片

static Bitmap decodeStream(InputStream is)

static Bitmap decodeStream(InputStream is, Rect outPadding, Options opts)

从输入流读取图片

static Bitmap decodeResource(Resources res, int id)

static Bitmap decodeResource(Resources res, int id, Options opts)

从资源文件读取图片

static Bitmap decodeByteArray(byte[] data, int offset, int length)

static Bitmap decodeByteArray(byte[] data, int offset, int length, Options opts)

从数组读取图片

static Bitmap decodeFileDescriptor(FileDescriptor fd)

static Bitmap decodeFileDescriptor(FileDescriptor fd, Rect outPadding, Options opts)

从文件读取文件 与decodeFile不同的是这个直接调用JNI函数进行读取 效率比较高

2.3 BitmapFactory的内部Option 参数类

内部类Options作为参数来控制Bitmap的创建,比如控制Bitmap的长和宽、像素的大小,是否只获取图片的一些信息(不加载图片数据,返回图片宽和高),是否在内存中复用等。

参数描述

boolean inJustDecodeBounds

如果设置为true,不获取图片,不分配内存,但会返回图片的高度宽度信息。如果将这个值置为true,那么在解码的时候将不会返回bitmap,只会返回这个bitmap的尺寸。这个属性的目的是,如果你只想知道一个bitmap的尺寸,但又不想将其加载到内存时。这是一个非常有用的属性。

int inSampleSize

图片缩放的倍数, 这个值是一个int,当它小于1的时候,将会被当做1处理,如果大于1,那么就会按照比例(1 / inSampleSize)缩小bitmap的宽和高、降低分辨率,大于1时这个值将会被处置为2的倍数。例如,width=100,height=100,inSampleSize=2,那么就会将bitmap处理为,width=50,height=50,宽高降为1 / 2,像素数降为1 / 4。

int outWidth

获取图片的宽度值

int outHeight

获取图片的高度值 ,表示这个Bitmap的宽和高,一般和inJustDecodeBounds一起使用来获得Bitmap的宽高,但是不加载到内存。

int inDensity

用于位图的像素压缩比

int inTargetDensity

用于目标位图的像素压缩比(要生成的位图)

byte[] inTempStorage

创建临时文件,将图片存储

boolean inScaled

设置为true时进行图片压缩,从inDensity到inTargetDensity

boolean inDither

如果为true,解码器尝试抖动解码

Bitmap.Config inPreferredConfig

设置解码器,这个值是设置色彩模式,默认值是ARGB_8888,在这个模式下,一个像素点占用4bytes空间,一般对透明度不做要求的话,一般采用RGB_565模式,这个模式下一个像素点占用2bytes。

String outMimeType

设置解码图像

boolean inPurgeable

当存储Pixel的内存空间在系统内存不足时是否可以被回收

boolean inInputShareable

inPurgeable为true情况下才生效,是否可以共享一个InputStream

boolean inPreferQualityOverSpeed

为true则优先保证Bitmap质量其次是解码速度

boolean inMutable

配置Bitmap是否可以更改,比如:在Bitmap上隔几个像素加一条线段

int inScreenDensity

当前屏幕的像素密度

2.4 色深枚举变量

Bitmap.C枚举变量 (位图位数越高代表其可以存储的颜色信息越多,图像越逼真,占用内存越大)

色深枚举变量描述

Bitmap.Config ALPHA_8

代表8位Alpha位图 每个像素占用1byte内存

Bitmap.Config ARGB_4444

代表16位ARGB位图 每个像素占用2byte内存

Bitmap.Config ARGB_8888

代表32位ARGB位图 每个像素占用4byte内存

Bitmap.Config RGB_565

代表8位RGB位图 每个像素占用2byte内存

三、Bitmap加载方式

3.1 Resource资源加载

public static Bitmap readBitmapFromResource(Resources resources, int resourcesId, int width, int height) {
    BitmapFactory.Options options = new BitmapFactory.Options();
    // 该属性默认为false, 为true时不会将图片加载到内存中, 而是只计算宽高
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeResource(resources, resourcesId, options);
   // 获得Bitmap的宽高
    float srcWidth = options.outWidth;
    float srcHeight = options.outHeight;
    int inSampleSize = 1;

    // 计算采样率
    if (srcHeight > height || srcWidth > width) {
        if (srcWidth > srcHeight) {
            inSampleSize = Math.round(srcHeight / height);
        } else {
            inSampleSize = Math.round(srcWidth / width);
        }
    }

    // 设置inJustDecodeBounds为false, 将图片加载到内存
    options.inJustDecodeBounds = false;
    //设置加载图片的采样率
    options.inSampleSize = inSampleSize;

    return BitmapFactory.decodeResource(resources, resourcesId, options);
}

ImageView是显示Bitmap的载体,一般情况下ImageView的宽高会小于Bitmap,如果将一个完整的Bitmap加载到偏小的ImageView中会浪费内存。关于这个问题,Android官方提供了一个优化方法。

该方法通过比较ImageView与Bitmap的大小并计算采样率,最终将采样后的小图加载到内存中。

如果使用Glide这类图片加载框架将Bitmap加载到ImageView中,框架会自动帮我们完成采样的工作。不过如果你需要获取某个Bitmap的缩略图,上述方法还是有用武之地的。

3.2 从本地(SDcard)文件读取

方式一:

public static Bitmap readBitmapFromFile(String filePath, int width, int height) {
    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeFile(filePath, options);
    float srcWidth = options.outWidth;
    float srcHeight = options.outHeight;
    int inSampleSize = 1;

    if (srcHeight > height || srcWidth > width) {
        if (srcWidth > srcHeight) {
            inSampleSize = Math.round(srcHeight / height);
        } else {
            inSampleSize = Math.round(srcWidth / width);
        }
    }

    options.inJustDecodeBounds = false;
    options.inSampleSize = inSampleSize;

    return BitmapFactory.decodeFile(filePath, options);

方式二 (效率高于方式一):

public static Bitmap readBitmapFromFileDescriptor(String filePath, int width, int height) {
    try {
        FileInputStream fis = new FileInputStream(filePath);
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeFileDescriptor(fis.getFD(), null, options);
        float srcWidth = options.outWidth;
        float srcHeight = options.outHeight;
        int inSampleSize = 1;


        if (srcHeight > height || srcWidth > width) {
            if (srcWidth > srcHeight) {
                inSampleSize = Math.round(srcHeight / height);
            } else {
                inSampleSize = Math.round(srcWidth / width);
            }
        }


        options.inJustDecodeBounds = false;
        options.inSampleSize = inSampleSize;


        return BitmapFactory.decodeFileDescriptor(fis.getFD(), null, options);
    } catch (Exception ex) {
    }
    return null;
}

3.3 从输入流中读取

public static Bitmap readBitmapFromInputStream(InputStream inputStream){
        return BitmapFactory.decodeStream(inputStream);
    }


    public static Bitmap readBitmapFromInputStream(InputStream inputStream, int reqWidth, int reqHeight){


        byte[] buffer = inputStreamToBytes(inputStream);
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = false;
        BitmapFactory.decodeByteArray(buffer,0,buffer.length,options);
        float srcWidth = options.outWidth;
        float srcHeight = options.outHeight;
        int inSampleSize = 1;


        if (srcHeight > reqHeight || srcWidth > reqWidth) {
            if (srcWidth > srcHeight) {
                inSampleSize = Math.round(srcHeight / reqHeight);
            } else {
                inSampleSize = Math.round(srcWidth / reqWidth);
            }
        }


        options.inJustDecodeBounds = false;
        options.inSampleSize = inSampleSize;


        Log.i("MainActivity", "readBitmapFromInputStream Bitmap srcWidth=" + srcWidth +
                " srcHeight=" + srcHeight + " inSampleSize=" + inSampleSize);
        return BitmapFactory.decodeByteArray(buffer,0,buffer.length,options);
    }


    public static byte[] inputStreamToBytes(InputStream inputStream) {
        ByteArrayOutputStream  byteArrayOutputStream = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024];


        try {
            int num = inputStream.read(buffer);
            while (num != -1) {
                byteArrayOutputStream.write(buffer, 0, num);
                num = inputStream.read(buffer);
            }
            byteArrayOutputStream.flush();


        } catch (IOException e) {
            e.printStackTrace();
        }
        return byteArrayOutputStream.toByteArray();
    }

要注意,使用输入流来加载Bitmap不能调用两次BitmapFactory.decodeStream(inputStream),这是由于调用一次后输入inputStream会被清空。

因此如果需要根据reqWidth和reqHeight加载需要将inputStream转换为二进制数据,再用数据加载bitmap。

3.4 Assets资源加载方式

public static Bitmap readBitmapFromAssetsFile(Context context, String filePath) {
    Bitmap image = null;
    AssetManager am = context.getResources().getAssets();
    try {
        InputStream is = am.open(filePath);
        image = BitmapFactory.decodeStream(is);
        is.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
    return image;
}

3.5 二进制数据读取图片

public static Bitmap readBitmapFromByteArray(byte[] data, int width, int height) {
    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeByteArray(data, 0, data.length, options);
    float srcWidth = options.outWidth;
    float srcHeight = options.outHeight;
    int inSampleSize = 1;

    if (srcHeight > height || srcWidth > width) {
        if (srcWidth > srcHeight) {
            inSampleSize = Math.round(srcHeight / height);
        } else {
            inSampleSize = Math.round(srcWidth / width);
        }
    }

    options.inJustDecodeBounds = false;
    options.inSampleSize = inSampleSize;

    return BitmapFactory.decodeByteArray(data, 0, data.length, options);
}

四、Bitmap | Drawable | InputStream | Byte[ ] 之间进行转换

4.1 Drawable转化成Bitmap

//drawable convert bitmap
public static Bitmap drawableToBitmap(Drawable drawable) {


    Bitmap bitmap = null;
    if (drawable instanceof BitmapDrawable) {
        BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable;
        if (bitmapDrawable.getBitmap() != null) {
            return bitmapDrawable.getBitmap();
        }
    }


    if ((drawable.getIntrinsicWidth() <= 0) || (drawable.getIntrinsicHeight() <= 0)) {
        bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
    }
    else {
        bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
    }


    Canvas canvas = new Canvas(bitmap);
    drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
    drawable.draw(canvas);
    return bitmap;
}

drawable的获取方式:Drawable drawable = getResources().getDrawable(R.drawable.ic_launcher);

4.2 Bitmap转换成Drawable

//bitmap convert drawable
public static Drawable bitmapToDrawable(Context context, Bitmap bitmap) {
    return new BitmapDrawable(context.getResources(), bitmap);
}

4.3 Bitmap转换成byte[]

public byte[] bitmapToBytes(Bitmap bm) {
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    bm.compress(Bitmap.CompressFormat.PNG, 100, baos);
    return baos.toByteArray();
}

五、Bitmap简单操作

5.1 将Bitmap保存为本地文件

public static void writeBitmapToFile(String filePath, Bitmap b, int quality) {
    try {
        File desFile = new File(filePath);
        FileOutputStream fos = new FileOutputStream(desFile);
        BufferedOutputStream bos = new BufferedOutputStream(fos);
        b.compress(Bitmap.CompressFormat.JPEG, quality, bos);
        bos.flush();
        bos.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

5.2 图片压缩

private static Bitmap compressImage(Bitmap image) {
    if (image == null) {
        return null;
    }
    ByteArrayOutputStream baos = null;
    try {
        baos = new ByteArrayOutputStream();
        image.compress(Bitmap.CompressFormat.JPEG, 100, baos);
        byte[] bytes = baos.toByteArray();
        ByteArrayInputStream isBm = new ByteArrayInputStream(bytes);
        Bitmap bitmap = BitmapFactory.decodeStream(isBm);
        return bitmap;
    } catch (OutOfMemoryError e) {
    } finally {
        try {
          if (baos != null) {
            baos.close();
          }
        } catch (IOException e) {
        }
    }
    return null;
}

5.3 设置

    public static Bitmap rotateBitmap(Bitmap bitmap, float rotateDegree){
        Matrix matrix = new Matrix();
        matrix.postRotate(rotateDegree);
        Bitmap newBitmap = Bitmap.createBitmap(
                bitmap,0,0,bitmap.getWidth(),bitmap.getHeight(),matrix,true);
        return newBitmap;
    }

5.4 通过图片id获得Bitmap

    Bitmap bitmap=BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher);

5.5 通过 assest 获取 获得Drawable bitmap

    InputStream in = this.getAssets().open("ic_launcher"); 
    Drawable da = Drawable.createFromStream(in, null); 
    Bitmap mm = BitmapFactory.decodeStream(in);

5.6 通过 sdcard 获得 bitmap

    Bitmap bit = BitmapFactory.decodeFile("/sdcard/android.jpg");

5.7 view转Bitmap

public static Bitmap convertViewToBitmap(View view, int bitmapWidth, int bitmapHeight){
    Bitmap bitmap = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888);
    view.draw(new Canvas(bitmap));
    return bitmap;
}

5.8 将控件转换为bitmap

public static Bitmap convertViewToBitMap(View view){
    // 打开图像缓存
    view.setDrawingCacheEnabled(true);
    // 必须调用measure和layout方法才能成功保存可视组件的截图到png图像文件
    // 测量View大小
    view.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
            MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
    // 发送位置和尺寸到View及其所有的子View
    view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight());
    // 获得可视组件的截图
    Bitmap bitmap = view.getDrawingCache();
    return bitmap;
}

public static Bitmap getBitmapFromView(View view){
    Bitmap returnedBitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(), Bitmap.Config.ARGB_8888);
    Canvas canvas = new Canvas(returnedBitmap);
    Drawable bgDrawable = view.getBackground();
    if (bgDrawable != null)
        bgDrawable.draw(canvas);
    else
        canvas.drawColor(Color.WHITE);
    view.draw(canvas);
    return returnedBitmap;
}

5.9 放大缩小图片

    public static Bitmap zoombitmap(Bitmap bitmap, int reqWidth, int reqHeight){
        int width = bitmap.getWidth();
        int height = bitmap.getHeight();
        float scaleWidth = (float)(reqWidth)/width;
        float scaleHeight = (float)(reqHeight)/height;


        Matrix matrix = new Matrix();
        matrix.postScale(scaleWidth,scaleHeight);
        Bitmap newBitmap = Bitmap.createBitmap(
                bitmap,0,0,bitmap.getWidth(),bitmap.getHeight(),matrix,true);
        return newBitmap;
    }

5.10 获得圆角图片的方法

    public static Bitmap getRoundedCornerBitmap(Bitmap bitmap, float roundPx){
        Bitmap output = Bitmap.createBitmap(
                bitmap.getWidth(),bitmap.getHeight(), Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(output);


        int color = 0xff424242;
        Paint paint = new Paint();
        Rect rect = new Rect(0,0,bitmap.getWidth(),bitmap.getHeight());
        RectF rectf = new RectF(rect);


        paint.setAntiAlias(true);
        canvas.drawARGB(0,0,0,0);
        paint.setColor(color);
        canvas.drawRoundRect(rectf,roundPx,roundPx,paint);


        paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
        canvas.drawBitmap(bitmap,rect,rect,paint);
        return output;
    }

5.11 对 bitmap 进行裁剪

    public static Bitmap clipBitMap(Bitmap bitmap, int x, int y,int reqWidth, int reqHeight){
        Bitmap newBitmap = Bitmap.createBitmap(
                bitmap, x, y, reqWidth, reqHeight);
        return  newBitmap;
    }

六、Bitmap的内存优化详解

        在Android应用里,最耗费内存的就是图片资源。而且在Android系统中,读取位图Bitmap时,分给虚拟机中的图片的堆栈大小只有8M,如果超出了,就会出现OutOfMemory异常。所以,对于图片的内存优化,是Android应用开发中比较重要的内容。

6.1 要及时回收Bitmap的内存

        Bitmap类有一个方法recycle(),从方法名可以看出意思是回收。这里就有疑问了,Android系统有自己的垃圾回收机制,可以不定期的回收掉不使用的内存空间,当然也包括Bitmap的空间。那为什么还需要这个方法呢?

        Bitmap类的构造方法都是私有的,所以开发者不能直接new出一个Bitmap对象,只能通过BitmapFactory类的各种静态方法来实例化一个Bitmap。仔细查看BitmapFactory的源代码可以看到,生成Bitmap对象最终都是通过JNI调用方式实现的。所以,加载Bitmap到内存里以后,是包含两部分内存区域的。简单的说,一部分是Java部分的,一部分是C部分的。这个Bitmap对象是由Java部分分配的,不用的时候系统就会自动回收了,但是那个对应的C可用的内存区域,虚拟机是不能直接回收的,这个只能调用底层的功能释放。所以需要调用recycle()方法来释放C部分的内存。从Bitmap类的源代码也可以看到,recycle()方法里也的确是调用了JNI方法了的。

        那如果不调用recycle(),是否就一定存在内存泄露呢?也不是的。Android的每个应用都运行在独立的进程里,有着独立的内存,如果整个进程被应用本身或者系统杀死了,内存也就都被释放掉了,当然也包括C部分的内存。

        Android对于进程的管理是非常复杂的。简单的说,Android系统的进程分为几个级别,系统会在内存不足的情况下杀死一些低优先级的进程,以提供给其它进程充足的内存空间。在实际项目开发过程中,有的开发者会在退出程序的时候使用Process.killProcess(Process.myPid())的方式将自己的进程杀死,但是有的应用仅仅会使用调用Activity.finish()方法的方式关闭掉所有的Activity。

        释放Bitmap的示例代码片段:

if(bitmap != null && !bitmap.isRecycled()){ 
    // 回收并且置为null
    bitmap.recycle(); 
    bitmap = null; 
} 
System.gc();

        从上面的代码可以看到,bitmap.recycle()方法用于回收该Bitmap所占用的内存,接着将bitmap置空,最后使用System.gc()调用一下系统的垃圾回收器进行回收,可以通知垃圾回收器尽快进行回收。这里需要注意的是,调用System.gc()并不能保证立即开始进行回收过程,而只是为了加快回收的到来。

        如何调用recycle()方法进行回收已经了解了,那什么时候释放Bitmap的内存比较合适呢?一般来说,如果代码已经不再需要使用Bitmap对象了,就可以释放了。释放内存以后,就不能再使用该Bitmap对象了,如果再次使用,就会抛出异常。所以一定要保证不再使用的时候释放。比如,如果是在某个Activity中使用Bitmap,就可以在Activity的onStop()或者onDestroy()方法中进行回收。

6.2 捕获异常

        为了避免应用在分配Bitmap内存的时候出现OutOfMemory异常以后Crash掉,需要特别注意实例化Bitmap部分的代码。通常,在实例化Bitmap的代码中,一定要对OutOfMemory异常进行捕获。

Bitmap bitmap = null;
try {
    // 实例化Bitmap
    bitmap = BitmapFactory.decodeFile(path);
} catch (OutOfMemoryError e) {
    //
}
if (bitmap == null) {
    // 如果实例化失败 返回默认的Bitmap对象
    return defaultBitmapMap;
}

        这里对初始化Bitmap对象过程中可能发生的OutOfMemory异常进行了捕获。如果发生了OutOfMemory异常,应用不会崩溃,而是得到了一个默认的Bitmap图。

        注意:很多开发者会习惯性的在代码中直接捕获Exception。但是对于OutOfMemoryError来说,这样做是捕获不到的。因为OutOfMemoryError是一种Error,而不是Exception。在此仅仅做一下提醒,避免写错代码而捕获不到OutOfMemoryError。

6.3 缓存通用的Bitmap对象

        有时候,可能需要在一个Activity里多次用到同一张图片。比如一个Activity会展示一些用户的头像列表,而如果用户没有设置头像的话,则会显示一个默认头像,而这个头像是位于应用程序本身的资源文件中的。

        如果有类似上面的场景,就可以对同一Bitmap进行缓存。如果不进行缓存,尽管看到的是同一张图片文件,但是使用BitmapFactory类的方法来实例化出来的Bitmap,是不同的Bitmap对象。缓存可以避免新建多个Bitmap对象,避免内存的浪费。

        在Android应用开发过程中,也会经常使用缓存的技术。这里所说的缓存有两个级别,一个是硬盘缓存,一个是内存缓存。比如说,在开发网络应用过程中,可以将一些从网络上获取的数据保存到SD卡中,下次直接从SD卡读取,而不从网络中读取,从而节省网络流量。这种方式就是硬盘缓存。再比如,应用程序经常会使用同一对象,也可以放到内存中缓存起来,需要的时候直接从内存中读取。这种方式就是内存缓存。

6.4 压缩图片

        如果图片像素过大,使用BitmapFactory类的方法实例化Bitmap的过程中,需要大于8M的内存空间,就必定会发生OutOfMemory异常。这个时候该如何处理呢?如果有这种情况,则可以将图片缩小,以减少载入图片过程中的内存的使用,避免异常发生。

        使用BitmapFactory.Options设置inSampleSize就可以缩小图片。属性值inSampleSize表示缩略图大小为原始图片大小的几分之一。即如果这个值为2,则取出的缩略图的宽和高都是原始图片的1/2,图片的大小就为原始大小的1/4。

        如果知道图片的像素过大,就可以对其进行缩小。那么如何才知道图片过大呢?

        使用BitmapFactory.Options设置inJustDecodeBounds为true后,再使用decodeFile()等方法,并不会真正的分配空间,即解码出来的Bitmap为null,但是可计算出原始图片的宽度和高度,即options.outWidth和options.outHeight。通过这两个值,就可以知道图片是否过大了。

BitmapFactory.Options opts = new BitmapFactory.Options();
// 设置inJustDecodeBounds为true
opts.inJustDecodeBounds = true;
// 使用decodeFile方法得到图片的宽和高
BitmapFactory.decodeFile(path, opts);
// 打印出图片的宽和高
Log.d("example", opts.outWidth + "," + opts.outHeight);

        在实际项目中,可以利用上面的代码,先获取图片真实的宽度和高度,然后判断是否需要跑缩小。如果不需要缩小,设置inSampleSize的值为1。如果需要缩小,则动态计算并设置inSampleSize的值,对图片进行缩小。需要注意的是,在下次使用BitmapFactory的decodeFile()等方法实例化Bitmap对象前,别忘记将opts.inJustDecodeBound设置回false。否则获取的bitmap对象还是null。

        注意:如果程序的图片的来源都是程序包中的资源,或者是自己服务器上的图片,图片的大小是开发者可以调整的,那么一般来说,就只需要注意使用的图片不要过大,并且注意代码的质量,及时回收Bitmap对象,就能避免OutOfMemory异常的发生。

        如果程序的图片来自外界,这个时候就特别需要注意OutOfMemory的发生。一个是如果载入的图片比较大,就需要先缩小;另一个是一定要捕获异常,避免程序Crash。

  • 21
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值