Android之Bitmap深入理解(BitmapFactory)二

        上一遍我们介绍了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));
}

这里用的图片是 Android之Bitmap深入理解 一 中的图片。我们来看下这里输出:

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个字节,Android之Bitmap深入理解 一有讲到,这里我们假设的宽为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 = 12582912
bitmapTest: 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 = 12582912
bitmapTest: 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 = 6291456
testBitmapStreamAssets: 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为我们屏幕适配做的,到这就差不多了,参数配置完后,剩下的就是去加载图片,感兴趣的可以自己去看看。

        


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值