android Bitmap内存优化(一) Bitmap 详解

来源:互联网 发布:linux ntpclient 编辑:程序博客网 时间:2024/05/21 19:50

概述

现在的手机应用基本上离不开图片,然后,图片在手机中的存在方式大概为两种形式,一种是 webapp 中嵌套在 html 页面中的图片,一种是作为本地资源,解析之后,显示在 ImageView 等组件上,我们今天要优化的当然是后者那种用法。说到优化,自然联系到 Bitmap 对象了。

Bitmap 类

Bitmap 根据 api 的介绍

首先了解一下 Bitmap 类里面的两个嵌套枚举类

Bitmap.Config 类

这里写图片描述

对这这个枚举类的简单的解释如下

ALPHA_8 会将每一个像素存储为单个的透明原色,只用八位存储了透明度
ARGB_4444 过时的,因为严重影响了图片质量,所以被弃用,用ARGB_888代替
ARGB_8888 每个像素点被存储为4个字节
RGB_565 每个像素点被存储为2个字节,只存储 RGB 三原色,5位存储红原色(32种编码),5位存储蓝原色(三十二种编码),6位存储绿原色(64种编码)

那么 ARGB_8888 和 RGB_565 有什么不同呢?
其实我们直接从名称上可以看到 ARGB_8888 代表的意思是,A是透明度,R是红原色,G绿原色,B是蓝原色,每一位都8位(一个字节)编码,所以有256种色值。所以 RGB_565 的话,是没有透明度的,而且,RGB 三原色的编码位数分别为5,6,5也即是各有 32种,64种,32种色值。

那个总结一下就是:
对图片显示质量要求不高,尽量用 RGB_565,经过试验大概能压缩个一般的

那么我们怎么使用这个类呢?

public static Bitmap streamBitmap(Context context,int resourseId){    BitmapFactory.Options  options = new BitmapFactory.Options();    options.inPreferredConfig = Bitmap.Config.RGB_565;    // 5.0(api 20)以下版本,2.3.3 (api 10)以上 版本 才有用,设置为 true 的时候,在系统内存低的时候会将 bitmap 存储在内存的像素数组回收    // 在你需要重新访问像素数组的时候,BitmapFactory 的 decoder 会重新去 decode出来    // 即使这个字段能防止 daivik 虚拟机内存溢出,但是严重影响了 UI绘制的性能,所以不建议使用    options.inInputShareable = true;    options.inPurgeable = true;    // 使用    Bitmap bitmap = null;    // 使用这个方式获得一个 Bitmap 效率要高一点    InputStream is = context.getResources().openRawResource(resourseId);    bitmap = BitmapFactory.decodeStream(is,null,options);    return bitmap;}

分别使用 RGB_565 和 ARGB_8888 解码出来的 Bitmap 的大小
这个是 RGB_565 的解码
这里写图片描述

这个是 ARGB_8888 的解码

这里写图片描述

可见大小接近压缩了一半。

Bitmap.CompressFormate 枚举类

概述:定义 Bitmap 以文件存储形式的压缩格式

这里写图片描述

这个类主的用途是在压缩图片的时候,设置压缩格式

  • PNG 无损压缩格式,有透明度
  • JPEG 有损压缩格式,无透明度
  • WEBP 有损压缩格式,goolge 提出来替换 JEPG 的一种图片格式,压缩率为 JPEG 的三分之二,主要用于网络(节省流量)

压缩图片的发生情景主要在这种情景,图片太大,加载进内存特别占空间,同时对图片显示质量要求不算很高的情况下,可以考虑压缩图片。

 public static Bitmap streamBitmap(Context context,int resourseId){    BitmapFactory.Options  options = new BitmapFactory.Options();//        options.inPreferredConfig = Bitmap.Config.RGB_565;    options.inPreferredConfig = Bitmap.Config.ARGB_8888;    // 5.0(api 20)以下版本,2.3.3 (api 10)以上 版本 才有用,设置为 true 的时候,在系统内存低的时候会将 bitmap 存储在内存的像素数组回收    // 在你需要重新访问像素数组的时候,BitmapFactory 的 decoder 会重新去 decode出来    // 即使这个字段能防止 daivik 虚拟机内存溢出,但是严重影响了 UI绘制的性能,所以不建议使用    options.inInputShareable = true;    options.inPurgeable = true;    // 使用    Bitmap bitmap = null;    Bitmap mBitmap = null;    // 使用这个方式获得一个 Bitmap 效率要高一点    InputStream is = context.getResources().openRawResource(resourseId);    bitmap = BitmapFactory.decodeStream(is, null, options);    mBitmap = BitmapFactory.decodeResource(context.getResources(),resourseId,options);    return mBitmap;}//    public static Bitmappublic static Bitmap  copmressBitmap(Context context){    BitmapFactory.Options options = new BitmapFactory.Options();    Bitmap bitmap;    // 设置为 true 的话, 图片不会被加载进内存,调用 BitmapFactory 的 decode() 方法之后,返回一个null 的bitmap对象    // 但是会返回 outXXX 字段( outWidth outHeigth 字段) 你可以在这里获取 bitmap 的大小     // 图片的原始分辨率为 1024 * 768    options.inJustDecodeBounds = true;    Bitmap mBitmap =BitmapFactory.decodeResource(context.getResources(), R.drawable.placeholder);    int width = mBitmap.getWidth();    int heigth  = mBitmap.getHeight();    // 因为返回2 所以高和宽会被压缩为原来的 1/2 像素数量变为原来 1/16    // 压缩是为了考虑低端机同时可以避免使用多套图片,减缩 apk 的大小    options.inSampleSize = calcCompressSize(width,heigth,512,384);    options.inJustDecodeBounds = false;    options.inPreferredConfig = Bitmap.Config.ARGB_8888;    bitmap = BitmapFactory.decodeResource(context.getResources(),R.drawable.placeholder,options);    return bitmap;}private static int calcCompressSize(int outWidth,int outHeight, int reqwidth, int reqheight) {    if(outWidth>reqwidth || outHeight> reqheight){        // 压缩比例        int widthRatio = Math.round((float)outWidth/(float)reqwidth);        int heigthRatio = Math.round((float)outHeight/(float)reqheight);        return widthRatio<heigthRatio ? heigthRatio:widthRatio; // 这样就会 返回 2    }    return 1;}

下面是压缩之后的效果图(上面一张是没有压缩的,宽和高都是 wrap_content )
这里写图片描述

然后下面给出压缩的内存结果

这里写图片描述

count 值是没有压缩之后的 compressCount 是压缩比为 2 压缩之后的,明显内存为原来的 1/4了

然后在有右图的 watches 里面可以看到具体的 bitmap 大小。

然而,这个 Bitmap.CompressFormat 的用法主要是这样

    bitmap.compress(Bitmap.CompressFormat.JPEG,new ByteArrayOutputStream());

在将文件保存为本地的时候,设置存储格式

其实这里细心的同学可能发现,这个 BItmap 的size究竟和什么有关呢?明明图片大小是 1024 * 768 ,不压缩解析出来大小却是 2048 * 1536 (放大一倍),同时我们嗨呀考虑到显示图片的 ImageView 本身是有大小的,所以最佳情况是 ImageView 的大小和 Bitmap 的大小是一样的,这个话题我们后面会做研究,这里先提出来。

Bitmap 类

我们简单的学习一下 Bitmap 的源码

由于 Bitmap 的构造方法是不带修饰符的,所以直接被 new 出来,根据 api 的解释:、

/**私有的构造方法,接受一个已经存在的 native bitmap 指针 * Private constructor that must received an already allocated native bitmap * int (pointer). */@SuppressWarnings({"UnusedDeclaration"}) // called from JNIBitmap(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;    // we delete this in our finalizer    mNativeBitmap = nativeBitmap;    mNinePatchChunk = ninePatchChunk;    mNinePatchInsets = ninePatchInsets;    if (density >= 0) {        mDensity = density;    }    int nativeAllocationByteCount = buffer == null ? getByteCount() : 0;    mFinalizer = new BitmapFinalizer(nativeBitmap, nativeAllocationByteCount);}

构造方法中只是进行了一系列的赋值,从数据域的 api 解释,来了解一下这个构造方法和 Bitmap 对象

/**标志着 Bitmap 被创建的时候,像素是否已知的 * Indicates that the bitmap was created for an unknown pixel density. * * @see Bitmap#getDensity() * @see Bitmap#setDensity(int) */public static final int DENSITY_NONE = 0;/** * Note:  mNativeBitmap is used by FaceDetector_jni.cpp * Don't change/rename without updating FaceDetector_jni.cpp *  * @hide */public final long mNativeBitmap;/** 读写的缓冲数组 * Backing buffer for the Bitmap. * Made public for quick access from drawing methods -- do NOT modify * from outside this class * * @hide */@SuppressWarnings("UnusedDeclaration") // native code onlypublic byte[] mBuffer;@SuppressWarnings({"FieldCanBeLocal", "UnusedDeclaration"}) // Keep to finalize native resourcesprivate final BitmapFinalizer mFinalizer; // bitmap 对象回收器 // 决定 bitmap 的像素点是否可以改变的private final boolean mIsMutable;/**标志 bitmap 的内容是否被预先计算的 * Represents whether the Bitmap's content is requested to be pre-multiplied. * Note that isPremultiplied() does not directly return this value, because * isPremultiplied() may never return true for a 565 Bitmap or a bitmap * without alpha. * * setPremultiplied() does directly set the value so that setConfig() and * setPremultiplied() aren't order dependent, despite being setters. * * The native bitmap's premultiplication state is kept up to date by * pushing down this preference for every config change. */private boolean mRequestPremultiplied; // 和 .9 有关的 byte 数组private byte[] mNinePatchChunk; // may be nullprivate NinePatch.InsetStruct mNinePatchInsets; // may be nullprivate int mWidth; // bitmap 的宽private int mHeight; // bitmap 的高private boolean mRecycled; // bitmap 对象是否被回收了// Package-scoped for fast access.int mDensity = getDefaultDensity();  // desity 值,默认等于屏幕的 desityprivate static volatile Matrix sScaleMatrix;private static volatile int sDefaultDensity = -1;

根据 api 的说明,这个构造方法只能被 jni 的代码调用,我们平时都是通过 BitmapFactory 的一系列的 decodeXXXX() 方法获得一个 Bitmap 对象

我们可以看到有四个 nativeDecodeXXXXX() 的方法,其实全部我们可以访问的方法都是调用对应的 native 方法的,( decodeFile() decodeStream() decodeResourse() 方法都是调用 decodeStram()方法的,所以它们没有对应的 native()方法,之前提到过 decodeResourse() 方法要比 decodeStream() 方法要慢,就是因为这个原因 )

我们来看一下 decodeStream()方法

public static Bitmap decodeStream(InputStream is, Rect outPadding, Options opts) {    // we don't throw in this case, thus allowing the caller to only check    // the cache, and not force the image to be decoded. 输入流对象为空    if (is == null) {        return null;    }    Bitmap bm = null;    //     // 缓存跟踪工具    Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "decodeBitmap");    try {   // 判断是否 asset 资源        if (is instanceof AssetManager.AssetInputStream) {            final long asset = ((AssetManager.AssetInputStream) is).getNativeAsset();            bm = nativeDecodeAsset(asset, outPadding, opts);        } else {            bm = decodeStreamInternal(is, outPadding, opts);        }        if (bm == null && opts != null && opts.inBitmap != null) {            throw new IllegalArgumentException("Problem decoding into existing bitmap");        }        setDensityFromOptions(bm, opts);    } finally {        Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS);    }    return bm;}

( 最终我还是决心打开 fuck source code )
这边涉及到 JNI 和 NDK 的知识我会在后续的章节补上。

0 0
原创粉丝点击