博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Android之Bitmap深入理解(BitmapFactory)二
阅读量:4180 次
发布时间:2019-05-26

本文共 8935 字,大约阅读时间需要 29 分钟。

        上一遍我们介绍了Bitmap,这一篇我们准备来讲一讲BitmapFactory,BitmapFactory主要还是服务于Bitmap,所以这一篇还是归结到Bitmap,这样也有利于我们理解Bitmap。

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

        我们先来看下Options的一些属性说明:

public static class Options {    public Options() {        inDither = false;        inScaled = true;        inPremultiplied = true;    }    /**     *对bitmap内存的复用,前提是这个bitmap是mutable的     */    public Bitmap inBitmap;    /**     * 如果设置为true,将返回一个mutable的bitmap代替immutable的bitmap,可用于修改BitmapFactory加载而来的bitmap.     */    @SuppressWarnings({
"UnusedDeclaration"}) // used in native code public boolean inMutable; /** * 如果将这个值置为true,那么在解码的时候将不会返回bitmap,只会返回这个 * bitmap的尺寸。这个属性的目的是,如果你只想知道一个bitmap的尺寸, * 但又不想将其加载到内存时。这是一个非常有用的属性。 */ public boolean inJustDecodeBounds; /** * 这个值是一个int,当它小于1的时候,将会被当做1处理,如果大于1,那么就会按照比 * 例(1 / inSampleSize)缩小bitmap的宽和高、降低分辨率,大于1时这个值将会被处置为2的倍数。 * 例如,width=100,height=100,inSampleSize=4,那么就会将bitmap处理为, * width=25,height=25,宽高降为1 / 4,像素数降为1 / 16。 */ public int inSampleSize; /** *这个值是设置色彩模式,默认值是ARGB_8888,在这个模式下,一个像素点占用4bytes空间, * 一般对透明度不做要求的话,一般采用RGB_565模式,这个模式下一个像素点占用2bytes。 */ public Bitmap.Config inPreferredConfig = Bitmap.Config.ARGB_8888; /** * 这个值和抖动解码有关,默认值为false,表示不采用抖动解码。 */ public boolean inDither; /** *当前bitmap的像素密度,配合inTargetDensity使用,inScaled决定是否使用 */ public int inDensity; /** *要被画出来的目标像素密度,配合inDensity使用,inScaled决定是否有用 */ public int inTargetDensity; /** *表示设备的实际像素密度(对应的是DisplayMetrics中的densityDpi,不是density)。 */ public int inScreenDensity; /** * 设置这个Bitmap是否可以被缩放,false表示不可以缩放,默认值是true,表示可以被缩放。 * 当为true时,根据inDensity和inTargetDensity比值进行缩放,inTargetDensity更大是就是放大处理 */ public boolean inScaled; /** *返回的是Bitmap的宽,这个返回值与inJustDecodeBounds有关,如果inJustDecodeBounds为true, * 就是图片本身的宽,图片不会加载进内存,如果为false,会将图片根据设置的缩放加载进内存, * 返回的是缩放后的高,加载的时候发生错误返回-1 */ public int outWidth; /** * 返回的是Bitmap的高,这个返回值与inJustDecodeBounds有关,如果inJustDecodeBounds为true, * 就是图片本身的高,图片不会加载进内存,如果为false,会将图片根据设置的缩放加载进内存, * 返回的是缩放后的高,加载的时候发生错误返回-1 */ public int outHeight;}

对于这些属性的使用我们直接用列子来讲解,首先要说的是inJustDecodeBounds:

private void bitmapTest() {    BitmapFactory.Options options = new BitmapFactory.Options();    options.inJustDecodeBounds = true;    Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.img_20150612_172900,options);    Log.d(TAG, "bitmapTest: bitmap width = "+options.outWidth+"   height = "+options.outHeight+"    (bitmap == null) = "+(bitmap == null));}

这里用的图片是  中的图片。我们来看下这里输出:

bitmapTest: bitmap width = 1536   height = 2048    (bitmap == null) = true

可以看到,这里返回的bitmap为null,只是给我们返回了bitmap的宽和高。接下来我们再来看看inSampleSize

private void bitmapTest() {    BitmapFactory.Options options = new BitmapFactory.Options();    options.inSampleSize = 2;    Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.img_20150612_172900,options);    Log.d(TAG, "bitmapTest: bitmap width = "+options.outWidth+"   height = "+options.outHeight+"    (bitmap == null) = "+(bitmap == null));}

bitmapTest: bitmap width = 768   height = 1024    (bitmap == null) = false

可以看到bitmap不为null,并且图片的宽和高都缩小为原来的一半,平时说到的大图加载用到的其实就是这两个属性。这里就说一下大图加载的流程:

        1、根据inJustDecodeBounds = true属性拿到图片的宽(a1)和高(b1);

        2、获取展示图片区域的大小,根据图片区域的宽(a2)和高(b2);

      3、获取(a1/a2)和(b1/b2)的值,inSampleSize取二者中较大的值,同时inJustDecodeBounds = false,这样拿到的图片就是缩小后的图片,不会造成很大的内存消耗。

        我们来看一个例子:

private void bitmapTest() {    BitmapFactory.Options options = new BitmapFactory.Options();    options.inJustDecodeBounds = true;    Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.img_20150612_172900,options);    int width = options.outWidth;    int picWidth = 100;    int actWidth = (int) Math.ceil((float)width/(float) picWidth);    options.inJustDecodeBounds = false;    options.inSampleSize = actWidth;    Bitmap bitmap1 = BitmapFactory.decodeResource(getResources(), R.mipmap.img_20150612_172900, options);    Log.d(TAG, "bitmapTest: bitmap width = "+options.outWidth+"   height = "+options.outHeight+"    "+actWidth+"    bitmap1 bytes = "+bitmap1.getByteCount());}

bitmapTest: bitmap width = 96   height = 128    16    bitmap1 bytes = 49152
那我们再来手动计算一下看看对不对,原图大小为:1536*2048*(32/8)=12582912个字节,有讲到,这里我们假设的宽为100,高就没有计算,这里算出来的比值是15.36,由于不让图片超出显示范围,我们取16,所以图片的宽和高都缩小了16,所以实际加载出来的图片大小为:(1536/16)*(2048/16)*(32/8)=49152个字节,和上面获取的值是一样的。

        我们在讲上面的这两个属性的时候,顺带就把outWidth和outHeight也弄明白了,接下来就该来看看inDensity,inTargetDensity以及inScaled这三个属性是怎样的作用了,我们从上面Options的构造函数中看到,inScaled是默认为true的,这样我们就先来看看其他的两个属性配合的作用:

private void bitmapTest() {    Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.img_20150612_172900);    BitmapFactory.Options options = new BitmapFactory.Options();    options.inDensity = 1;    options.inTargetDensity = 2;    Bitmap bitmap1 = BitmapFactory.decodeResource(getResources(), R.mipmap.img_20150612_172900,options);    Log.d(TAG, "bitmapTest: bitmap width = "+bitmap.getWidth()+"   height = "+bitmap.getHeight()+"    bitmap bytes = "+bitmap.getByteCount());    Log.d(TAG, "bitmapTest: bitmap1 width = "+bitmap1.getWidth()+"   height = "+bitmap1.getHeight()+"    bitmap1 bytes = "+bitmap1.getByteCount());}

bitmapTest: bitmap width = 1536   height = 2048    bitmap bytes = 12582912bitmapTest: bitmap1 width = 3072   height = 4096    bitmap1 bytes = 50331648

是不是看明白了什么,在设置了inDensity和inTargetDensity参数后,加载图片宽和高都翻倍了,如果你设置inDensity=2,inTargetDensity=1,那么加载的图片宽和高就减半了,是不是觉得和inSampleSize有点像,不过inSampleSize不能放大图片,这也是他们的区别,那如果我们把设置inScaled=false,来看下输出结果:

bitmapTest: bitmap width = 1536   height = 2048    bitmap bytes = 12582912bitmapTest: bitmap width = 1536   height = 2048    bitmap bytes = 12582912

结果是一样的,这就是说inScaled=false会使得inDensity和inTargetDensity的设置无效。

        再来看看inPreferredConfig这个属性的作用:

private void testBitmapStreamAssets(){        try {            InputStream open = getAssets().open("img_20150612_172900.jpg");            BitmapFactory.Options options = new BitmapFactory.Options();//            options.inPreferredConfig = Bitmap.Config.RGB_565;            options.inPreferredConfig = Bitmap.Config.ARGB_8888;            Bitmap bitmap = BitmapFactory.decodeStream(open,null,options);            img.setImageBitmap(bitmap);            Log.d(TAG, "testBitmapStreamAssets: bitmap width = "+bitmap.getWidth()+"  "+bitmap.getHeight()+"   "+(bitmap.getConfig())+"  byte count = "+bitmap.getByteCount());        } catch (IOException e) {            e.printStackTrace();        }    }

testBitmapStreamAssets: bitmap width = 1536  2048   RGB_565  byte count = 6291456testBitmapStreamAssets: bitmap width = 1536  2048   ARGB_8888  byte count = 12582912

在分别设置上面的两个属性后,从输出的日志中可以看出,RGB_565比ARGB_8888的内存占用量少了一半,这是因为RGB_565一个像素占两个字节,而ARGB_8888一个像素占用四个字节,这个属性的作用就在这了。

        最后我们再来看看Bitmap这个类中构建bitmap的方法:

这里构建bitmap大致可以分为四种,一是byte数组,二是文件中,三是资源文件中,四是从流中,从这些方法的参数大致可以这么划分,但是看过源码之后,你就会发现,除byte数组外,其它两种最后用的还是流。在这里,我们就分析一个方法的创建流程decodeResource(Resource,int),其他的都是类似的:

public static Bitmap decodeResource(Resources res, int id) {    return decodeResource(res, id, null);}
public static Bitmap decodeResource(Resources res, int id, Options opts) {    Bitmap bm = null;    InputStream is = null;    try {        final TypedValue value = new TypedValue();        //根据传入的id拿到对应的流文件,这个value我是这样理解的,他会记录这个        // 资源文件是从哪个倍率下的文件下拿到的,以便可以根据我们的手机的像素密度进行缩放        is = res.openRawResource(id, value);        //这个方法即使传入的流创建bitmap        bm = decodeResourceStream(res, value, is, null, opts);    } catch (Exception e) {        /*  do nothing.            If the exception happened on open, bm will be null.            If it happened on close, bm is still valid.        */    } finally {        try {            if (is != null) is.close();        } catch (IOException e) {            // Ignore        }    }    if (bm == null && opts != null && opts.inBitmap != null) {        throw new IllegalArgumentException("Problem decoding into existing bitmap");    }    return bm;}

这个方法里就是拿到对应流和对应资源文件的参数,以便我们从流中创建bitmap的时候进行缩放,好了,我们在跟着代码进去:

public static Bitmap decodeResourceStream(Resources res, TypedValue value,                                          InputStream is, Rect pad, Options opts) {    if (opts == null) {        opts = new Options();    }    if (opts.inDensity == 0 && value != null) {        final int density = value.density;        if (density == TypedValue.DENSITY_DEFAULT) {            opts.inDensity = DisplayMetrics.DENSITY_DEFAULT;        } else if (density != TypedValue.DENSITY_NONE) {            opts.inDensity = density;        }    }    if (opts.inTargetDensity == 0 && res != null) {        opts.inTargetDensity = res.getDisplayMetrics().densityDpi;    }    return decodeStream(is, pad, opts);}

这个方法还是比较好理解的,就是根据我们传入的value获取对应的像素密度作为inDensity,然后以屏幕的像素密度作为目标像素密度inTargetDensity,这样我们创建的bitmap就是我们所需要的,这也是Android为我们屏幕适配做的,到这就差不多了,参数配置完后,剩下的就是去加载图片,感兴趣的可以自己去看看。

        

你可能感兴趣的文章
Vista/Win7 UAC兼容程序开发指南
查看>>
IOS程序开发框架
查看>>
安装jdk的步骤
查看>>
简述JAVA运算符
查看>>
简易ATM源代码及运行结果
查看>>
简述Java中的简单循环
查看>>
用JAVA实现各种乘法表
查看>>
for双重循环实现图形
查看>>
Java类和对象基础
查看>>
简述Java继承和多态
查看>>
Java中Arrays工具类的用法
查看>>
简述JAVA抽象类和接口
查看>>
JAVA常用基础类
查看>>
简述Java异常处理
查看>>
简述Java集合框架
查看>>
jQuery+ajax实现省市区(县)下拉框三级联动
查看>>
Spring中的AOP 面向切面编程
查看>>
简述Spring中的JDBC框架
查看>>
MyBatis 动态SQL
查看>>
Spring MVC体系结构和处理请求控制器
查看>>