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 的知识我会在后续的章节补上。
- android Bitmap内存优化(一) Bitmap 详解
- Android Bitmap内存优化
- Android Bitmap内存优化
- android内存优化--Bitmap
- Android 内存优化 Bitmap
- Bitmap详解与Bitmap的内存优化
- Android中Bitmap内存优化
- Android内存优化之Bitmap
- Android开发之Bitmap的内存优化详解
- Android性能优化:Bitmap详解&你的Bitmap占多大内存?
- 内存优化---Bitmap优化
- Android对Bitmap的内存优化
- Android中对Bitmap的内存优化
- android bitmap的内存分配和优化
- Android内存优化 代码和bitmap
- Bitmap内存优化123
- Bitmap 的内存优化
- Bitmap的内存优化
- POJ 1330 ---(线段树在线LCA 与 tarjan离线LCAs)
- TextView和EditText的总结 (收藏)
- Jetty学习(三)--jetty部署spring mvc项目
- hdu 5419 Victor and Toys 线段树成段更新
- Ubuntu下安装搜狗输入法
- android Bitmap内存优化(一) Bitmap 详解
- 找出二叉树中某两个结点的第一个公共祖先
- poj1080
- opencv学习(七)(opencv3.0.0+VS2012+win7)打开摄像头并且进行边缘检测加上了滑动条
- python二进制处理详述
- Java之旅hibernate(1)——初始
- 会说话的tom猫
- [CQOI2013]新Nim游戏
- c++ stl 2