作者:郭孝星
微博:郭孝星的新浪微博
邮箱:[email protected]
博客:http://blog.csdn.net/allenwells
Github:https://github.com/AllenWells
【Android应用开发技术:图像处理】章节列表
Bitmap经常会消耗大量内存而导致程序崩溃,常见的异常如下所示:java.lang.OutofMemoryError:bitmap size extends VM budget,因此为了保证程序的稳定性,我们应该小心处理程序中加载Bitmap的操作。
我们在加载Bitmap时通常会遇到以下问题:
- 移动设备的系统资源有限。Android设备对于单个程序至少需要16MB的内存。Android Compatibility Definition Document, Section 3.7. Virtual Machine Compatibility给出了对于不同大小与密度的屏幕的最低内存需求。程序应该在这个最低内存限制下最优化程序的效率。当然,大多数设备的都有更高的限制需求。
- Bitmap会消耗很多内存,特别是对于类似照片等内容更加丰富的图片。例如:Galaxy Nexus的照相机能够拍摄2592x1936 pixels (5 MB)的图片。 如果bitmap的配置是使用ARGB_8888(ARGB_8888是从Android 2.3开始的默认配置),那么加载这张照片到内存会大概需要19MB( 2592*1936*4 bytes) 的内存, 这样的话会迅速消耗掉设备的整个内存。
- Android App的UI通常会在一次操作中立即加载许多张Bitmap。例如:在ListView, GridView 与ViewPager等组件中通常会需要一次加载许多张Bitmap,而且需要预先加载一些没有在屏幕上显示的内容,为用户滑动时显示做准备。
一 加载大尺寸位图
图片有不同的形状与大小。在大多数情况下它们的实际大小都比需要呈现出来的要大很多。考虑到程序是在有限的内存下工作,我们通常会用低分辨率的缩略图的形式来显示图片,低分辨率的版本应该与我们的UI大小匹配。高分辨率的图片会占用大量内存资源,并且在快读滑动图片时会导致附加的效率问题。
下面我们来介绍以下如何加载一个低分辨率的图片到内存中并解析出高分辨率的图片。
1.1 读取位图的尺寸与类型
BitmapFactory类提供了一些decode的方法,如下所示:
- public static Bitmap decodeFile(String pathName, Options opts)
/**
* Decode a file path into a bitmap. If the specified file name is null,
* or cannot be decoded into a bitmap, the function returns null.
*
* @param pathName complete path name for the file to be decoded.
* @param opts null-ok; Options that control downsampling and whether the
* image should be completely decoded, or just is size returned.
* @return The decoded bitmap, or null if the image data could not be
* decoded, or, if opts is non-null, if opts requested only the
* size be returned (in opts.outWidth and opts.outHeight)
*/
public static Bitmap decodeFile(String pathName, Options opts);
- public static Bitmap decodeResource(Resources res, int id, Options opts)
/**
* Synonym for opening the given resource and calling
* {@link #decodeResourceStream}.
*
* @param res The resources object containing the image data
* @param id The resource id of the image data
* @param opts null-ok; Options that control downsampling and whether the
* image should be completely decoded, or just is size returned.
* @return The decoded bitmap, or null if the image data could not be
* decoded, or, if opts is non-null, if opts requested only the
* size be returned (in opts.outWidth and opts.outHeight)
*/
public static Bitmap decodeResource(Resources res, int id, Options opts);
- public static Bitmap decodeByteArray(byte[] data, int offset, int length, Options opts)
/**
* Decode an immutable bitmap from the specified byte array.
*
* @param data byte array of compressed image data
* @param offset offset into imageData for where the decoder should begin
* parsing.
* @param length the number of bytes, beginning at offset, to parse
* @param opts null-ok; Options that control downsampling and whether the
* image should be completely decoded, or just is size returned.
* @return The decoded bitmap, or null if the image data could not be
* decoded, or, if opts is non-null, if opts requested only the
* size be returned (in opts.outWidth and opts.outHeight)
*/
public static Bitmap decodeByteArray(byte[] data, int offset, int length, Options opts);
- public static Bitmap decodeStream(InputStream is, Rect outPadding, Options opts)
/**
* Decode an input stream into a bitmap. If the input stream is null, or
* cannot be used to decode a bitmap, the function returns null.
* The stream's position will be where ever it was after the encoded data
* was read.
*
* @param is The input stream that holds the raw data to be decoded into a
* bitmap.
* @param outPadding If not null, return the padding rect for the bitmap if
* it exists, otherwise set padding to [-1,-1,-1,-1]. If
* no bitmap is returned (null) then padding is
* unchanged.
* @param opts null-ok; Options that control downsampling and whether the
* image should be completely decoded, or just is size returned.
* @return The decoded bitmap, or null if the image data could not be
* decoded, or, if opts is non-null, if opts requested only the
* size be returned (in opts.outWidth and opts.outHeight)
*
* <p class="note">Prior to {@link android.os.Build.VERSION_CODES#KITKAT},
* if {@link InputStream#markSupported is.markSupported()} returns true,
* <code>is.mark(1024)</code> would be called. As of
* {@link android.os.Build.VERSION_CODES#KITKAT}, this is no longer the case.</p>
*/
public static Bitmap decodeStream(InputStream is, Rect outPadding, Options opts)
- public static Bitmap decodeFileDescriptor(FileDescriptor fd, Rect outPadding, Options opts)
/**
* Decode a bitmap from the file descriptor. If the bitmap cannot be decoded
* return null. The position within the descriptor will not be changed when
* this returns, so the descriptor can be used again as-is.
*
* @param fd The file descriptor containing the bitmap data to decode
* @param outPadding If not null, return the padding rect for the bitmap if
* it exists, otherwise set padding to [-1,-1,-1,-1]. If
* no bitmap is returned (null) then padding is
* unchanged.
* @param opts null-ok; Options that control downsampling and whether the
* image should be completely decoded, or just its size returned.
* @return the decoded bitmap, or null
*/
public static Bitmap decodeFileDescriptor(FileDescriptor fd, Rect outPadding, Options opts);
上述方法用来从不同的资源中创建一个Bitmap. 根据图片数据源来选择合适的decode方法. 那些方法在构造位图的时候会尝试分配内存,因此会容易导致OutOfMemory的异常。每一种decode方法都提供了通过BitmapFactory.Options来设置一些附加的标记来指定decode的选项。设置inJustDecodeBounds属性为true可以在decoding的时候避免内存的分配,它会返回一个null的bitmap,但是outWidth, outHeight与outMimeType 还是可以获取。这个技术可以允许我们在构造bitmap之前先读取图片的尺寸与类型。
举例
在不分配内存的情况下获取图片的尺寸与类型。
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.id.myimage, options);
int imageHeight = options.outHeight;
int imageWidth = options.outWidth;
String imageType = options.outMimeType;
1.2 加载按比例缩小的图片到内存中
当通过以上的步骤我们已经获得了图片的尺寸,这些数据可以让我们决定是直接加载整个图片到内存中还是加载一个缩小的版本到内存中,我们通常会做以下的考量:
- 评估加载完整图片所需要耗费的内存。
- 程序在加载这张图片时会涉及到其他内存需求。
- 呈现这张图片的组件的尺寸大小。
- 屏幕大小与当前设备的屏幕密度。
举例
根据目标图片大小来计算Sample图片大小。
public static int calculateInSampleSize(
BitmapFactory.Options options, int reqWidth, int reqHeight) {
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
//设置inSampleSize为2的幂,之所以会这样做是因为decoder最终会对非2的幂进行向下处理,
//直到获取最靠近2的幂的树。
while ((halfHeight / inSampleSize) > reqHeight
&& (halfWidth / inSampleSize) > reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
使用calculateInSampleSize()方法。
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
int reqWidth, int reqHeight) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
//设置options.inJustDecodeBounds为true
options.inJustDecodeBounds = true;
//传递options的值
BitmapFactory.decodeResource(res, resId, options);
//计算inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
//设置 options.inJustDecodeBounds为false
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
}
载一个任意大小的图片并显示为100*100 pixel的缩略图形式。
mImageView.setImageBitmap(
decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100));
二 非UI线程处理图片
对于BitmapFactory的decode方法,当数据源是网络或者磁盘时,这些方法都不应该在主UI线程中执行,否则可能会由于诸多因数(例如:磁盘读取数据的速度、图片的大小、CPU的速度等)导致ANR异常。
2.1 使用AsyncTask
As