Android之Bitmap深入理解 一

前言:

        Bitmap是位图文件,它将图像定义为由点(像素)组成,每个点可以由多中色彩表示,包括2、4、8、16、24和32位色彩,例如一张1536*2048分辨率的32位真彩图片,其所占存储字节数为1536*248*(32/8)=12582912个字节,12582912/1024=12288kb,12288/1024=12MB。

        上面说的可能有点抽象,先来看张图:


我们演示用的就是上面这张图,上面的计算也是基于这张图的,在写代码之前你得确定这张图片是放在哪一个资源文件的目录下面,首先得确定测试用的手机是多少倍率的,获取手机的倍率有多中方式,我这里就直接用Android中API来获取了:

float density = getResources().getDisplayMetrics().density;

我这里返回的是2,那我就放在mipmap-xhdpi文件夹下面,这样的话图片加载后就不会进行缩放了,如果放在mipmap-mdpi下面,那么加载后就会放大四倍(长宽各放大2倍),意味着占用的内存也多了四倍,如果放在mipmap-xxhdpi,那么是原来的4/9(长宽各2/3),意味着占用内存的减少了5/9,这里说下关于倍率的计算,比如现在我手机的dpi是2,那么会先到mipmap-xhdpi下去找,如果没有找到,就会去更高的dpi中找,如果高的dpi中没有,那么就会去低的dpi中查找,找到之后再根据比例去换算,这也是Android为我们做的屏幕适配,关于各文件夹下的dpi如下:

            mipmap-mdpi        1

            mipmap-hdpi         1.5

            mipmap-xhdpi        2

            mipmap-xxhdpi      3

说了这么多,下面就用代码来验证一下:

private void bitmapTest() {
    Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.img_20150612_172900);
    int byteCount = bitmap.getByteCount();
    Bitmap.Config config = bitmap.getConfig();
    int width = bitmap.getWidth();
    int height = bitmap.getHeight();
    DisplayMetrics dm = getResources().getDisplayMetrics();
    Log.e(TAG, "onCreate: count = "+byteCount+"  "+width+"   "+height+"  "+config+"  "+bitmap.getDensity()+"  "+bitmap.getRowBytes()+"   "+dm.density);
}

我这里就是用了一个点击事件就调用了这个方法,输出的日志:

onCreate: count = 12582912  1536   2048  ARGB_8888  320  6144   2.0

byteCount输出的就是字节数,12582912和我们上面算的一样。

内存消耗截图(这个是在Android studio 3.0中的Android Profiler中查看的):


从这张图中看出,点击之前和点击之后的内存增加了12MB,正好就是我们前面算的这张图的大小。

        现在我们再把这张图放到mipmap-mdpi文件夹下,输出日志:

onCreate: count = 50331648  3072   4096  ARGB_8888  320  12288   2.0
可以看到长和宽各增加了2倍,图片的内存自然也就增加了4倍。

        再来看下内存消耗图:


从这张图中看出,点击之前和点击之后的内存增加了105.5MB-57.5MB=48MB。

        现在我们再把这张图放到mipmap-xxhdpi文件夹下,输出日志:

onCreate: count = 5591040  1024   1365  ARGB_8888  320  4096   2.0

可以看一下长1536/3*2=1024,确实是原来的2/3.

        再来看下内存消耗图:


从这张图中看出,点击之前和点击之后的内存增加了20.1MB-14.8MB=5.3MB,说明这张图片占用了5.3MB,原图的大小时12MB,12*4/9=5.3MB,这就证明前面说的是没错的。

        从这里我们应该可以得出,要使图片占用的内存比较小,那么可以控制图片的宽和高,这是一种方法,还有另一种方法,那就是使用Bitmap.Config中的参数,这次我们还是把图片放在mipmap-xhdpi文件夹下面,只不过这次创建Bitmap的时候多了一个参数,来看下代码:

private void bitmapTest() {
    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inPreferredConfig = Bitmap.Config.ARGB_4444;
    Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.img_20150612_172900,options);
    int byteCount = bitmap.getByteCount();
    Bitmap.Config config = bitmap.getConfig();
    int width = bitmap.getWidth();
    int height = bitmap.getHeight();
    DisplayMetrics dm = getResources().getDisplayMetrics();
    Log.e(TAG, "onCreate: count = "+byteCount+"  "+width+"   "+height+"  "+config+"  "+bitmap.getDensity()+"  "+bitmap.getRowBytes()+"   "+dm.density);
}

接着就让我们来看下它的输出日志:

onCreate: count = 6291456  1536   2048  ARGB_4444  320  3072   2.0

我们把在前面输出的也放到这来对比一下:

onCreate: count = 12582912  1536   2048  ARGB_8888  320  6144   2.0

我们可以看到Bitmap.Config由ARGB_8888变成ARGB_4444后,内存直接减小了一半,这是由于设置了ARGB_4444后,一个像素只占用了2个字节,而ARGB_8888占用了4个字节,在不设置这个参数的时候,默认使用的ARGB_8888,不过ARGB_8888在API 19后就已经不推荐使用了,因为图片显示的效果不好,而且对于设置成ARGB_4444,有些手机是没有效果的,比如我自己用的是小米5 note plus的,设置ARGB_4444后最后得到的还是ARGB_8888,目前还不知道原因,不过我现在这台测试机是没有问题的。

        现在再来看下Bitmap.Config,这是一个枚举类:

public enum Config {
    /**
     * 只保存了透明通道,就是一个像素占用一个字节
     */
    ALPHA_8     (1),
    /**
     *没有保存透明通道,但是保存red、green、blue三个通道,一个像素占用两个字节,其中的
     * 高5位bits表示red通道,其后的6位bits表示green通道,低5位bits表示的是blue通道
     */
    RGB_565     (3),
    /**
     *一个像素占用两个字节,总共alpha(透明)、red、green、blue四个通道,
     * 一个通道占用4个bit,api 19后推荐使用使用ARGB_8888             
     */
    @Deprecated
    ARGB_4444   (4),
    /**
     * 一个像素占用了4个字节,包括alpha(透明)、red、green、blue四个通道,
     * 每个通道占用了8个bits
     */
    ARGB_8888   (5);

    final int nativeInt;

    private static Config sConfigs[] = {
            null, ALPHA_8, null, RGB_565, ARGB_4444, ARGB_8888
    };

    Config(int ni) {
        this.nativeInt = ni;
    }

    static Config nativeToConfig(int ni) {
        return sConfigs[ni];
    }
}

这个参数主要就是设置图片的一个像素占用多少个字节,这个还是要根据需求去设,是看中内存还是看重图片清晰度。

            来看看BItmap的构造函数:

// called from JNI
Bitmap(long nativeBitmap, byte[] buffer, int width, int height, int density,
        boolean isMutable, boolean requestPremultiplied,
        byte[] ninePatchChunk, NinePatch.InsetStruct ninePatchInsets) {
    if (nativeBitmap == 0) {
        throw new RuntimeException("internal error: native bitmap is 0");
    }

    mWidth = width;
    mHeight = height;
    mIsMutable = isMutable;
    mRequestPremultiplied = requestPremultiplied;
    mBuffer = buffer;

    mNinePatchChunk = ninePatchChunk;
    mNinePatchInsets = ninePatchInsets;
    if (density >= 0) {
        mDensity = density;
    }

    mNativePtr = nativeBitmap;
    mFinalizer = new BitmapFinalizer(nativeBitmap);
    int nativeAllocationByteCount = (buffer == null ? getByteCount() : 0);
    mFinalizer.setNativeAllocationByteCount(nativeAllocationByteCount);
}

这个构造方法是由系统通过jni自己调用,我们一般用不到,传进来的width和height就是图片的宽和高,buffer就是实际存储图片数据的集合,这也就反应出图片在内存中就是一个byte数组,通过下面的代码可以验证buffer存储的就是图片数据:

private void bitmapTest1() {
    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inPreferredConfig = Bitmap.Config.ARGB_4444;
    Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.img_20150612_172900,options);

    Class<? extends Bitmap> aClass = bitmap.getClass();
    byte[] bytes = null;
    try {
        Field mBuffer = aClass.getDeclaredField("mBuffer");
        mBuffer.setAccessible(true);
        bytes = (byte[]) mBuffer.get(bitmap);
    } catch (Exception e) {
        e.printStackTrace();
    }
    for (int i = 0; i < bytes.length; i++) {
        bytes[i] = (byte)(bytes[i]+1);
    }
    img.setImageBitmap(bitmap);

    int byteCount = bitmap.getByteCount();
    Bitmap.Config config = bitmap.getConfig();
    int width = bitmap.getWidth();
    int height = bitmap.getHeight();
    DisplayMetrics dm = getResources().getDisplayMetrics();
    Log.e(TAG, "onCreate: count = "+byteCount+"  "+width+"   "+height+"  "+config+"  "+bitmap.getDensity()+"  "+bitmap.getRowBytes()+"   "
            +dm.density+"  "+bitmap.isMutable());
}

这里主要就是通过反射拿到buffer这个数组,然后对其进行修改,效果如下:


可以看到,对拿到的byte数组每个数只是加了1,这图就差不多完全变样了惊恐

总结:

        对于图片的加载,我们要控制图片内存的占用量,可以通过两点来控制:

            1、控制加载图片的宽和高;

            2、设置图片一个像素所占的字节数,字节数越多就越清晰,占用内存就越大。


         到这里,Bitmap就讲的差不多了,下一遍再来说说BitmapFactory,在实际开发中我们用的比较多的也是这个类,因为这个类就是Android为我们提供创建Bitmap的工厂类。


        
        Android之Bitmap深入理解(BitmapFactory)二



  

猜你喜欢

转载自blog.csdn.net/tangedegushi/article/details/80227465