我有一个ListView,每行上有两个图像按钮。当用户单击列表行时,它将启动一个新活动。我不得不建立我自己的标签,因为相机布局的问题。为结果启动的活动是一个映射。如果我点击我的按钮来启动图像预览(从SD卡加载图像),应用程序从活动返回到ListView活动到结果处理程序,重新启动我的新活动,这只不过是一个图像小部件。
ListView上的图像预览是用光标和ListAdapter完成的。这使得它非常简单,但我不确定我如何可以把一个调整大小的图像(即较小的位大小而不是像素作为图像按钮的src)。我只是调整了从手机摄像头拍下来的图片的大小。
问题是,当它试图返回并重新启动第二个活动时,我得到一个OutOfMemoryError。
是否有一种方法,我可以轻松地构建列表适配器行,在那里我可以调整大小的飞行(位)?
这将是可取的,因为我还需要对每行中的小部件/元素的属性做出一些更改,因为我无法选择一行与触摸屏,因为焦点问题。(我会用滚轮。)
我知道我可以做带外调整并保存我的图像,但这不是我真正想做的,但一些示例代码会很好。
当我在ListView上禁用图像时,它又可以正常工作了。
供你参考:我是这样做的:
String[] from = new String[] { DBHelper.KEY_BUSINESSNAME, DBHelper.KEY_ADDRESS,
DBHelper.KEY_CITY, DBHelper.KEY_GPSLONG, DBHelper.KEY_GPSLAT,
DBHelper.KEY_IMAGEFILENAME + ""};
int[] to = new int[] { R.id.businessname, R.id.address, R.id.city, R.id.gpslong,
R.id.gpslat, R.id.imagefilename };
notes = new SimpleCursorAdapter(this, R.layout.notes_row, c, from, to);
setListAdapter(notes);
其中R.id.imagefilename是一个ButtonImage。
这是我的LogCat:
01-25 05:05:49.877: ERROR/dalvikvm-heap(3896): 6291456-byte external allocation too large for this process.
01-25 05:05:49.877: ERROR/(3896): VM wont let us allocate 6291456 bytes
01-25 05:05:49.877: ERROR/AndroidRuntime(3896): Uncaught handler: thread main exiting due to uncaught exception
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): java.lang.OutOfMemoryError: bitmap size exceeds VM budget
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.graphics.BitmapFactory.nativeDecodeStream(Native Method)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:304)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.graphics.BitmapFactory.decodeFile(BitmapFactory.java:149)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.graphics.BitmapFactory.decodeFile(BitmapFactory.java:174)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.graphics.drawable.Drawable.createFromPath(Drawable.java:729)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.ImageView.resolveUri(ImageView.java:484)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.ImageView.setImageURI(ImageView.java:281)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.SimpleCursorAdapter.setViewImage(SimpleCursorAdapter.java:183)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.SimpleCursorAdapter.bindView(SimpleCursorAdapter.java:129)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.CursorAdapter.getView(CursorAdapter.java:150)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.AbsListView.obtainView(AbsListView.java:1057)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.ListView.makeAndAddView(ListView.java:1616)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.ListView.fillSpecific(ListView.java:1177)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.ListView.layoutChildren(ListView.java:1454)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.AbsListView.onLayout(AbsListView.java:937)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.view.View.layout(View.java:5611)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1119)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.LinearLayout.layoutHorizontal(LinearLayout.java:1108)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.LinearLayout.onLayout(LinearLayout.java:922)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.view.View.layout(View.java:5611)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.FrameLayout.onLayout(FrameLayout.java:294)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.view.View.layout(View.java:5611)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1119)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.LinearLayout.layoutVertical(LinearLayout.java:999)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.LinearLayout.onLayout(LinearLayout.java:920)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.view.View.layout(View.java:5611)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.FrameLayout.onLayout(FrameLayout.java:294)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.view.View.layout(View.java:5611)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.view.ViewRoot.performTraversals(ViewRoot.java:771)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.view.ViewRoot.handleMessage(ViewRoot.java:1103)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.os.Handler.dispatchMessage(Handler.java:88)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.os.Looper.loop(Looper.java:123)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.app.ActivityThread.main(ActivityThread.java:3742)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at java.lang.reflect.Method.invokeNative(Native Method)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at java.lang.reflect.Method.invoke(Method.java:515)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:739)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:497)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at dalvik.system.NativeStart.main(Native Method)
01-25 05:10:01.127: ERROR/AndroidRuntime(3943): ERROR: thread attach failed
我也有一个新的错误时显示的图像:
22:13:18.594: DEBUG/skia(4204): xxxxxxxxxxx jpeg error 20 Improper call to JPEG library in state %d
22:13:18.604: INFO/System.out(4204): resolveUri failed on bad bitmap uri:
22:13:18.694: ERROR/dalvikvm-heap(4204): 6291456-byte external allocation too large for this process.
22:13:18.694: ERROR/(4204): VM won't let us allocate 6291456 bytes
22:13:18.694: DEBUG/skia(4204): xxxxxxxxxxxxxxxxxxxx allocPixelRef failed
我有同样的问题,并通过避免BitmapFactory.decodeStream或decodeFile函数来解决它,而是使用BitmapFactory.decodeFileDescriptor
decodeFileDescriptor看起来像调用了不同于decodeStream/decodeFile的本地方法。
不管怎样,这样做是有效的(注意,我添加了一些选项,就像上面的一些选项一样,但这并不是造成不同的原因。关键是调用BitmapFactory.decodeFileDescriptor而不是decodeStream或decodeFile):
private void showImage(String path) {
Log.i("showImage","loading:"+path);
BitmapFactory.Options bfOptions=new BitmapFactory.Options();
bfOptions.inDither=false; //Disable Dithering mode
bfOptions.inPurgeable=true; //Tell to gc that whether it needs free memory, the Bitmap can be cleared
bfOptions.inInputShareable=true; //Which kind of reference will be used to recover the Bitmap data after being clear, when it will be used in the future
bfOptions.inTempStorage=new byte[32 * 1024];
File file=new File(path);
FileInputStream fs=null;
try {
fs = new FileInputStream(file);
} catch (FileNotFoundException e) {
//TODO do something intelligent
e.printStackTrace();
}
try {
if(fs!=null) bm=BitmapFactory.decodeFileDescriptor(fs.getFD(), null, bfOptions);
} catch (IOException e) {
//TODO do something intelligent
e.printStackTrace();
} finally{
if(fs!=null) {
try {
fs.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
//bm=BitmapFactory.decodeFile(path, bfOptions); This one causes error: java.lang.OutOfMemoryError: bitmap size exceeds VM budget
im.setImageBitmap(bm);
//bm.recycle();
bm=null;
}
我认为在decodeStream/decodeFile中使用的本机函数有问题。我已经确认使用decodeFileDescriptor时调用了不同的本机方法。此外,我读到的是“图像(位图)不是以标准的Java方式分配的,而是通过本机调用;分配是在虚拟堆之外完成的,但是
不算数!”
Android培训课程“高效显示位图”提供了一些很好的信息,用于理解和处理异常“java.lang.OutOfMemoryError:加载位图时,位图大小超过虚拟机预算”。
读取位图尺寸和类型
BitmapFactory类提供了几种解码方法(decodeByteArray(), decodeFile(), decodeResource()等),用于从各种来源创建位图。根据您的图像数据源选择最合适的解码方法。这些方法试图为构造的位图分配内存,因此很容易导致OutOfMemory异常。每种类型的解码方法都有额外的签名,允许您通过BitmapFactory指定解码选项。选择类。在解码时,将inJustDecodeBounds属性设置为true,避免内存分配,位图对象返回null,但设置outidth, outHeight和outMimeType。这种技术允许您在构造(和内存分配)位图之前读取图像数据的尺寸和类型。
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;
为了避免java.lang.OutOfMemory异常,在解码位图之前请检查位图的尺寸,除非您完全相信源代码能够为您提供可预测大小的图像数据,并且这些图像数据可以很好地适应可用内存。
将缩小的版本加载到内存中
既然知道了图像尺寸,就可以使用它们来决定是否应该将完整的图像加载到内存中,还是应该加载子采样版本。以下是一些需要考虑的因素:
估计在内存中加载完整图像的内存使用情况。
给定应用程序的任何其他内存需求,您愿意用于加载此映像的内存量。
图像要加载到的目标ImageView或UI组件的尺寸。
屏幕大小和当前设备的密度。
例如,如果最终将在ImageView中以128x96像素的缩略图显示,则不值得将1024x768像素的图像加载到内存中。
要告诉解码器对图像进行子采样,将较小的版本加载到内存中,在BitmapFactory中将inSampleSize设置为true。选择对象。例如,使用inSampleSize为4进行解码的分辨率为2048x1536的图像将生成大约512x384的位图。将其加载到内存中需要0.75MB,而不是完整图像的12MB(假设位图配置为ARGB_8888)。下面是一个基于目标宽度和高度的2的幂来计算样本大小值的方法:
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;
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
// height and width larger than the requested height and width.
while ((halfHeight / inSampleSize) > reqHeight
&& (halfWidth / inSampleSize) > reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
注意:由于解码器使用的是2的幂
最后的值,舍入到最接近的2的幂,按照
inSampleSize文档。
要使用此方法,首先将inJustDecodeBounds设置为true进行解码,传递选项,然后使用新的inSampleSizevalue和injustdecodeboundsset设置为false '进行解码:
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;
BitmapFactory.decodeResource(res, resId, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
}
这个方法可以很容易地将任意大小的位图加载到显示100x100像素缩略图的ImageView中,如下面的示例代码所示:
mImageView.setImageBitmap(
decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100));
您可以按照类似的过程来解码来自其他来源的位图,方法是根据需要替换适当的BitmapFactory.decode*方法。
最近我看到了很多关于OOM异常和缓存的问题。开发人员指南中有一篇关于这方面的非常好的文章,但有些人往往无法以合适的方式实现它。
因此,我编写了一个示例应用程序,演示了Android环境中的缓存。这个实现还没有得到一个OOM。
在答案的末尾可以找到源代码的链接。
要求:
Android API 2.1或更高版本(我根本无法在API 1.6中为应用程序获得可用内存-这是API 1.6中唯一不能工作的代码段)
Android支持包
特点:
如果方向改变,使用单例保存缓存
将分配的应用程序内存的八分之一用于缓存(如果需要可以修改)
大的位图缩放(您可以定义您想要允许的最大像素)
控制在下载位图之前是否有可用的互联网连接
确保每行只实例化一个任务
如果你扔掉ListView,它就不会下载中间的位图
这不包括:
磁盘缓存。这应该很容易实现-只要指向一个不同的任务,从磁盘获取位图
示例代码:
正在下载的图像是来自Flickr的图像(75x75)。然而,不管你想处理什么图像url,如果它超过最大值,应用程序就会缩小它。在这个应用程序中,url只是一个字符串数组。
LruCache有一个处理位图的好方法。但是,在这个应用程序中,我将一个LruCache实例放在我创建的另一个缓存类中,以便使应用程序更加可行。
Cache.java的关键内容(loadBitmap()方法是最重要的):
public Cache(int size, int maxWidth, int maxHeight) {
// Into the constructor you add the maximum pixels
// that you want to allow in order to not scale images.
mMaxWidth = maxWidth;
mMaxHeight = maxHeight;
mBitmapCache = new LruCache<String, Bitmap>(size) {
protected int sizeOf(String key, Bitmap b) {
// Assuming that one pixel contains four bytes.
return b.getHeight() * b.getWidth() * 4;
}
};
mCurrentTasks = new ArrayList<String>();
}
/**
* Gets a bitmap from cache.
* If it is not in cache, this method will:
*
* 1: check if the bitmap url is currently being processed in the
* BitmapLoaderTask and cancel if it is already in a task (a control to see
* if it's inside the currentTasks list).
*
* 2: check if an internet connection is available and continue if so.
*
* 3: download the bitmap, scale the bitmap if necessary and put it into
* the memory cache.
*
* 4: Remove the bitmap url from the currentTasks list.
*
* 5: Notify the ListAdapter.
*
* @param mainActivity - Reference to activity object, in order to
* call notifyDataSetChanged() on the ListAdapter.
* @param imageKey - The bitmap url (will be the key).
* @param imageView - The ImageView that should get an
* available bitmap or a placeholder image.
* @param isScrolling - If set to true, we skip executing more tasks since
* the user probably has flinged away the view.
*/
public void loadBitmap(MainActivity mainActivity,
String imageKey, ImageView imageView,
boolean isScrolling) {
final Bitmap bitmap = getBitmapFromCache(imageKey);
if (bitmap != null) {
imageView.setImageBitmap(bitmap);
} else {
imageView.setImageResource(R.drawable.ic_launcher);
if (!isScrolling && !mCurrentTasks.contains(imageKey) &&
mainActivity.internetIsAvailable()) {
BitmapLoaderTask task = new BitmapLoaderTask(imageKey,
mainActivity.getAdapter());
task.execute();
}
}
}
您不需要在Cache.java文件中编辑任何内容,除非您想实现磁盘缓存。
java的关键部分:
public void onScrollStateChanged(AbsListView view, int scrollState) {
if (view.getId() == android.R.id.list) {
// Set scrolling to true only if the user has flinged the
// ListView away, hence we skip downloading a series
// of unnecessary bitmaps that the user probably
// just want to skip anyways. If we scroll slowly it
// will still download bitmaps - that means
// that the application won't wait for the user
// to lift its finger off the screen in order to
// download.
if (scrollState == SCROLL_STATE_FLING) {
mIsScrolling = true;
} else {
mIsScrolling = false;
mListAdapter.notifyDataSetChanged();
}
}
}
// Inside ListAdapter...
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
View row = convertView;
final ViewHolder holder;
if (row == null) {
LayoutInflater inflater = getLayoutInflater();
row = inflater.inflate(R.layout.main_listview_row, parent, false);
holder = new ViewHolder(row);
row.setTag(holder);
} else {
holder = (ViewHolder) row.getTag();
}
final Row rowObject = getItem(position);
// Look at the loadBitmap() method description...
holder.mTextView.setText(rowObject.mText);
mCache.loadBitmap(MainActivity.this,
rowObject.mBitmapUrl, holder.mImageView,
mIsScrolling);
return row;
}
getView()经常被调用。如果我们没有实现检查,以确保我们不会在每行启动无限数量的线程,那么在那里下载图像通常不是一个好主意。Cache.java检查rowObject。mBitmapUrl已经在一个任务,如果它是,它不会开始另一个任务。因此,我们很可能不会超过AsyncTask池的工作队列限制。
下载:
您可以从https://www.dropbox.com/s/pvr9zyl811tfeem/ListViewImageCache.zip下载源代码。
最后一句话:
我已经测试了这个几个星期了,我还没有得到一个OOM异常。我已经在模拟器、我的Nexus One和Nexus s上测试了这个功能。我测试了包含高清图像的图像url。唯一的瓶颈是下载需要更多的时间。
只有一种可能的情况下,我可以想象OOM将会出现,那就是如果我们下载许多非常大的图像,在它们被缩放并放入缓存之前,将同时占用更多的内存并导致OOM。但无论如何,这都不是一个理想的情况,而且很可能不可能以更可行的方式解决。
在评论中报告错误!: -)
这似乎是与社区分享我的加载和处理图像的实用工具类的合适地方,欢迎您自由使用和修改它。
package com.emil;
import java.io.IOException;
import java.io.InputStream;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
/**
* A class to load and process images of various sizes from input streams and file paths.
*
* @author Emil http://stackoverflow.com/users/220710/emil
*
*/
public class ImageProcessing {
public static Bitmap getBitmap(InputStream stream, int sampleSize, Bitmap.Config bitmapConfig) throws IOException{
BitmapFactory.Options options=ImageProcessing.getOptionsForSampling(sampleSize, bitmapConfig);
Bitmap bm = BitmapFactory.decodeStream(stream,null,options);
if(ImageProcessing.checkDecode(options)){
return bm;
}else{
throw new IOException("Image decoding failed, using stream.");
}
}
public static Bitmap getBitmap(String imgPath, int sampleSize, Bitmap.Config bitmapConfig) throws IOException{
BitmapFactory.Options options=ImageProcessing.getOptionsForSampling(sampleSize, bitmapConfig);
Bitmap bm = BitmapFactory.decodeFile(imgPath,options);
if(ImageProcessing.checkDecode(options)){
return bm;
}else{
throw new IOException("Image decoding failed, using file path.");
}
}
public static Dimensions getDimensions(InputStream stream) throws IOException{
BitmapFactory.Options options=ImageProcessing.getOptionsForDimensions();
BitmapFactory.decodeStream(stream,null,options);
if(ImageProcessing.checkDecode(options)){
return new ImageProcessing.Dimensions(options.outWidth,options.outHeight);
}else{
throw new IOException("Image decoding failed, using stream.");
}
}
public static Dimensions getDimensions(String imgPath) throws IOException{
BitmapFactory.Options options=ImageProcessing.getOptionsForDimensions();
BitmapFactory.decodeFile(imgPath,options);
if(ImageProcessing.checkDecode(options)){
return new ImageProcessing.Dimensions(options.outWidth,options.outHeight);
}else{
throw new IOException("Image decoding failed, using file path.");
}
}
private static boolean checkDecode(BitmapFactory.Options options){
// Did decode work?
if( options.outWidth<0 || options.outHeight<0 ){
return false;
}else{
return true;
}
}
/**
* Creates a Bitmap that is of the minimum dimensions necessary
* @param bm
* @param min
* @return
*/
public static Bitmap createMinimalBitmap(Bitmap bm, ImageProcessing.Minimize min){
int newWidth, newHeight;
switch(min.type){
case WIDTH:
if(bm.getWidth()>min.minWidth){
newWidth=min.minWidth;
newHeight=ImageProcessing.getScaledHeight(newWidth, bm);
}else{
// No resize
newWidth=bm.getWidth();
newHeight=bm.getHeight();
}
break;
case HEIGHT:
if(bm.getHeight()>min.minHeight){
newHeight=min.minHeight;
newWidth=ImageProcessing.getScaledWidth(newHeight, bm);
}else{
// No resize
newWidth=bm.getWidth();
newHeight=bm.getHeight();
}
break;
case BOTH: // minimize to the maximum dimension
case MAX:
if(bm.getHeight()>bm.getWidth()){
// Height needs to minimized
min.minDim=min.minDim!=null ? min.minDim : min.minHeight;
if(bm.getHeight()>min.minDim){
newHeight=min.minDim;
newWidth=ImageProcessing.getScaledWidth(newHeight, bm);
}else{
// No resize
newWidth=bm.getWidth();
newHeight=bm.getHeight();
}
}else{
// Width needs to be minimized
min.minDim=min.minDim!=null ? min.minDim : min.minWidth;
if(bm.getWidth()>min.minDim){
newWidth=min.minDim;
newHeight=ImageProcessing.getScaledHeight(newWidth, bm);
}else{
// No resize
newWidth=bm.getWidth();
newHeight=bm.getHeight();
}
}
break;
default:
// No resize
newWidth=bm.getWidth();
newHeight=bm.getHeight();
}
return Bitmap.createScaledBitmap(bm, newWidth, newHeight, true);
}
public static int getScaledWidth(int height, Bitmap bm){
return (int)(((double)bm.getWidth()/bm.getHeight())*height);
}
public static int getScaledHeight(int width, Bitmap bm){
return (int)(((double)bm.getHeight()/bm.getWidth())*width);
}
/**
* Get the proper sample size to meet minimization restraints
* @param dim
* @param min
* @param multipleOf2 for fastest processing it is recommended that the sample size be a multiple of 2
* @return
*/
public static int getSampleSize(ImageProcessing.Dimensions dim, ImageProcessing.Minimize min, boolean multipleOf2){
switch(min.type){
case WIDTH:
return ImageProcessing.getMaxSampleSize(dim.width, min.minWidth, multipleOf2);
case HEIGHT:
return ImageProcessing.getMaxSampleSize(dim.height, min.minHeight, multipleOf2);
case BOTH:
int widthMaxSampleSize=ImageProcessing.getMaxSampleSize(dim.width, min.minWidth, multipleOf2);
int heightMaxSampleSize=ImageProcessing.getMaxSampleSize(dim.height, min.minHeight, multipleOf2);
// Return the smaller of the two
if(widthMaxSampleSize<heightMaxSampleSize){
return widthMaxSampleSize;
}else{
return heightMaxSampleSize;
}
case MAX:
// Find the larger dimension and go bases on that
if(dim.width>dim.height){
return ImageProcessing.getMaxSampleSize(dim.width, min.minDim, multipleOf2);
}else{
return ImageProcessing.getMaxSampleSize(dim.height, min.minDim, multipleOf2);
}
}
return 1;
}
public static int getMaxSampleSize(int dim, int min, boolean multipleOf2){
int add=multipleOf2 ? 2 : 1;
int size=0;
while(min<(dim/(size+add))){
size+=add;
}
size = size==0 ? 1 : size;
return size;
}
public static class Dimensions {
int width;
int height;
public Dimensions(int width, int height) {
super();
this.width = width;
this.height = height;
}
@Override
public String toString() {
return width+" x "+height;
}
}
public static class Minimize {
public enum Type {
WIDTH,HEIGHT,BOTH,MAX
}
Integer minWidth;
Integer minHeight;
Integer minDim;
Type type;
public Minimize(int min, Type type) {
super();
this.type = type;
switch(type){
case WIDTH:
this.minWidth=min;
break;
case HEIGHT:
this.minHeight=min;
break;
case BOTH:
this.minWidth=min;
this.minHeight=min;
break;
case MAX:
this.minDim=min;
break;
}
}
public Minimize(int minWidth, int minHeight) {
super();
this.type=Type.BOTH;
this.minWidth = minWidth;
this.minHeight = minHeight;
}
}
/**
* Estimates size of Bitmap in bytes depending on dimensions and Bitmap.Config
* @param width
* @param height
* @param config
* @return
*/
public static long estimateBitmapBytes(int width, int height, Bitmap.Config config){
long pixels=width*height;
switch(config){
case ALPHA_8: // 1 byte per pixel
return pixels;
case ARGB_4444: // 2 bytes per pixel, but depreciated
return pixels*2;
case ARGB_8888: // 4 bytes per pixel
return pixels*4;
case RGB_565: // 2 bytes per pixel
return pixels*2;
default:
return pixels;
}
}
private static BitmapFactory.Options getOptionsForDimensions(){
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds=true;
return options;
}
private static BitmapFactory.Options getOptionsForSampling(int sampleSize, Bitmap.Config bitmapConfig){
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = false;
options.inDither = false;
options.inSampleSize = sampleSize;
options.inScaled = false;
options.inPreferredConfig = bitmapConfig;
return options;
}
}
我认为避免OutOfMemoryError的最好方法是面对它并理解它。
我做了一个应用程序,故意引起OutOfMemoryError,并监控内存使用情况。
在对这个App做了大量的实验之后,我得到了以下结论:
在Honey Comb之前,我先谈谈SDK版本。
位图存储在原生堆中,但它会自动被垃圾收集,调用recycle()是不必要的。
如果{VM堆大小}+{已分配的本机堆内存}>={设备的VM堆大小限制},并且您正在尝试创建位图,则会抛出OOM。
注意:计算的是虚拟机堆大小,而不是虚拟机已分配内存。
VM堆大小在增长后永远不会缩小,即使分配的VM内存减少了。
所以你必须保持VM内存的峰值尽可能低,以保持VM堆大小不会变得太大,以节省可用的内存位图。
手动调用system .gc()是没有意义的,系统会在尝试增加堆大小之前先调用它。
原生堆大小也永远不会缩小,但它不计入OOM,所以不用担心。
然后,让我们谈谈SDK从蜂巢开始。
位图存储在VM堆中,OOM不计算本机内存。
OOM的条件要简单得多:{虚拟机堆大小}>={设备的虚拟机堆大小限制}。
所以你有更多的可用内存来创建位图与相同的堆大小限制,OOM不太可能被抛出。
以下是我对垃圾收集和内存泄漏的一些观察。
你可以自己在App中看到它。如果一个Activity执行了一个AsyncTask,而这个AsyncTask在Activity被销毁后仍然在运行,那么这个Activity在AsyncTask完成之前不会被垃圾收集。
这是因为AsyncTask是一个匿名内部类的实例,它持有一个Activity的引用。
如果任务在后台线程的IO操作中被阻塞,调用AsyncTask.cancel(true)将不会停止执行。
回调也是匿名的内部类,所以如果项目中的静态实例持有它们并且不释放它们,内存就会泄漏。
如果你计划一个重复或延迟的任务,例如定时器,你不调用取消()和清除()在onPause(),内存将被泄漏。
这里有很好的答案,但我想要一个完全可用的类来解决这个问题。所以我做了一个。
这是我的BitmapHelper类,是OutOfMemoryError证明:-)
import java.io.File;
import java.io.FileInputStream;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
public class BitmapHelper
{
//decodes image and scales it to reduce memory consumption
public static Bitmap decodeFile(File bitmapFile, int requiredWidth, int requiredHeight, boolean quickAndDirty)
{
try
{
//Decode image size
BitmapFactory.Options bitmapSizeOptions = new BitmapFactory.Options();
bitmapSizeOptions.inJustDecodeBounds = true;
BitmapFactory.decodeStream(new FileInputStream(bitmapFile), null, bitmapSizeOptions);
// load image using inSampleSize adapted to required image size
BitmapFactory.Options bitmapDecodeOptions = new BitmapFactory.Options();
bitmapDecodeOptions.inTempStorage = new byte[16 * 1024];
bitmapDecodeOptions.inSampleSize = computeInSampleSize(bitmapSizeOptions, requiredWidth, requiredHeight, false);
bitmapDecodeOptions.inPurgeable = true;
bitmapDecodeOptions.inDither = !quickAndDirty;
bitmapDecodeOptions.inPreferredConfig = quickAndDirty ? Bitmap.Config.RGB_565 : Bitmap.Config.ARGB_8888;
Bitmap decodedBitmap = BitmapFactory.decodeStream(new FileInputStream(bitmapFile), null, bitmapDecodeOptions);
// scale bitmap to mathc required size (and keep aspect ratio)
float srcWidth = (float) bitmapDecodeOptions.outWidth;
float srcHeight = (float) bitmapDecodeOptions.outHeight;
float dstWidth = (float) requiredWidth;
float dstHeight = (float) requiredHeight;
float srcAspectRatio = srcWidth / srcHeight;
float dstAspectRatio = dstWidth / dstHeight;
// recycleDecodedBitmap is used to know if we must recycle intermediary 'decodedBitmap'
// (DO NOT recycle it right away: wait for end of bitmap manipulation process to avoid
// java.lang.RuntimeException: Canvas: trying to use a recycled bitmap android.graphics.Bitmap@416ee7d8
// I do not excatly understand why, but this way it's OK
boolean recycleDecodedBitmap = false;
Bitmap scaledBitmap = decodedBitmap;
if (srcAspectRatio < dstAspectRatio)
{
scaledBitmap = getScaledBitmap(decodedBitmap, (int) dstWidth, (int) (srcHeight * (dstWidth / srcWidth)));
// will recycle recycleDecodedBitmap
recycleDecodedBitmap = true;
}
else if (srcAspectRatio > dstAspectRatio)
{
scaledBitmap = getScaledBitmap(decodedBitmap, (int) (srcWidth * (dstHeight / srcHeight)), (int) dstHeight);
recycleDecodedBitmap = true;
}
// crop image to match required image size
int scaledBitmapWidth = scaledBitmap.getWidth();
int scaledBitmapHeight = scaledBitmap.getHeight();
Bitmap croppedBitmap = scaledBitmap;
if (scaledBitmapWidth > requiredWidth)
{
int xOffset = (scaledBitmapWidth - requiredWidth) / 2;
croppedBitmap = Bitmap.createBitmap(scaledBitmap, xOffset, 0, requiredWidth, requiredHeight);
scaledBitmap.recycle();
}
else if (scaledBitmapHeight > requiredHeight)
{
int yOffset = (scaledBitmapHeight - requiredHeight) / 2;
croppedBitmap = Bitmap.createBitmap(scaledBitmap, 0, yOffset, requiredWidth, requiredHeight);
scaledBitmap.recycle();
}
if (recycleDecodedBitmap)
{
decodedBitmap.recycle();
}
decodedBitmap = null;
scaledBitmap = null;
return croppedBitmap;
}
catch (Exception ex)
{
ex.printStackTrace();
}
return null;
}
/**
* compute powerOf2 or exact scale to be used as {@link BitmapFactory.Options#inSampleSize} value (for subSampling)
*
* @param requiredWidth
* @param requiredHeight
* @param powerOf2
* weither we want a power of 2 sclae or not
* @return
*/
public static int computeInSampleSize(BitmapFactory.Options options, int dstWidth, int dstHeight, boolean powerOf2)
{
int inSampleSize = 1;
// Raw height and width of image
final int srcHeight = options.outHeight;
final int srcWidth = options.outWidth;
if (powerOf2)
{
//Find the correct scale value. It should be the power of 2.
int tmpWidth = srcWidth, tmpHeight = srcHeight;
while (true)
{
if (tmpWidth / 2 < dstWidth || tmpHeight / 2 < dstHeight)
break;
tmpWidth /= 2;
tmpHeight /= 2;
inSampleSize *= 2;
}
}
else
{
// Calculate ratios of height and width to requested height and width
final int heightRatio = Math.round((float) srcHeight / (float) dstHeight);
final int widthRatio = Math.round((float) srcWidth / (float) dstWidth);
// Choose the smallest ratio as inSampleSize value, this will guarantee
// a final image with both dimensions larger than or equal to the
// requested height and width.
inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
}
return inSampleSize;
}
public static Bitmap drawableToBitmap(Drawable drawable)
{
if (drawable instanceof BitmapDrawable)
{
return ((BitmapDrawable) drawable).getBitmap();
}
Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);
return bitmap;
}
public static Bitmap getScaledBitmap(Bitmap bitmap, int newWidth, int newHeight)
{
int width = bitmap.getWidth();
int height = bitmap.getHeight();
float scaleWidth = ((float) newWidth) / width;
float scaleHeight = ((float) newHeight) / height;
// CREATE A MATRIX FOR THE MANIPULATION
Matrix matrix = new Matrix();
// RESIZE THE BIT MAP
matrix.postScale(scaleWidth, scaleHeight);
// RECREATE THE NEW BITMAP
Bitmap resizedBitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, false);
return resizedBitmap;
}
}
使用这个概念,这将帮助你,之后设置imagebitmap在图像视图
public static Bitmap convertBitmap(String path) {
Bitmap bitmap=null;
BitmapFactory.Options bfOptions=new BitmapFactory.Options();
bfOptions.inDither=false; //Disable Dithering mode
bfOptions.inPurgeable=true; //Tell to gc that whether it needs free memory, the Bitmap can be cleared
bfOptions.inInputShareable=true; //Which kind of reference will be used to recover the Bitmap data after being clear, when it will be used in the future
bfOptions.inTempStorage=new byte[32 * 1024];
File file=new File(path);
FileInputStream fs=null;
try {
fs = new FileInputStream(file);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
try {
if(fs!=null)
{
bitmap=BitmapFactory.decodeFileDescriptor(fs.getFD(), null, bfOptions);
}
} catch (IOException e) {
e.printStackTrace();
} finally{
if(fs!=null) {
try {
fs.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return bitmap;
}
如果你想用高度和宽度分别为60和60的大图制作小图,并快速滚动列表视图,那么就使用这个概念
public static Bitmap decodeSampledBitmapFromPath(String path, int reqWidth,
int reqHeight) {
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(path, options);
options.inSampleSize = calculateInSampleSize(options, reqWidth,
reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
Bitmap bmp = BitmapFactory.decodeFile(path, options);
return bmp;
}
public static int calculateInSampleSize(BitmapFactory.Options options,
int reqWidth, int reqHeight) {
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
if (width > height) {
inSampleSize = Math.round((float) height / (float) reqHeight);
} else {
inSampleSize = Math.round((float) width / (float) reqWidth);
}
}
return inSampleSize;
}
我希望它能对你有很大帮助。
你可以从开发者网站获得帮助
这里的所有解决方案都需要设置IMAGE_MAX_SIZE。这限制了硬件更强大的设备,如果图像尺寸太小,在高清屏幕上看起来会很难看。
我想出了一个解决方案,它适用于我的三星Galaxy S3和其他几台设备(包括功能不太强大的设备),当使用功能更强大的设备时,图像质量会更好。
它的要点是计算在特定设备上分配给应用程序的最大内存,然后将规模设置为尽可能低的,而不超过这个内存。代码如下:
public static Bitmap decodeFile(File f)
{
Bitmap b = null;
try
{
// Decode image size
BitmapFactory.Options o = new BitmapFactory.Options();
o.inJustDecodeBounds = true;
FileInputStream fis = new FileInputStream(f);
try
{
BitmapFactory.decodeStream(fis, null, o);
}
finally
{
fis.close();
}
// In Samsung Galaxy S3, typically max memory is 64mb
// Camera max resolution is 3264 x 2448, times 4 to get Bitmap memory of 30.5mb for one bitmap
// If we use scale of 2, resolution will be halved, 1632 x 1224 and x 4 to get Bitmap memory of 7.62mb
// We try use 25% memory which equals to 16mb maximum for one bitmap
long maxMemory = Runtime.getRuntime().maxMemory();
int maxMemoryForImage = (int) (maxMemory / 100 * 25);
// Refer to
// http://developer.android.com/training/displaying-bitmaps/cache-bitmap.html
// A full screen GridView filled with images on a device with
// 800x480 resolution would use around 1.5MB (800*480*4 bytes)
// When bitmap option's inSampleSize doubled, pixel height and
// weight both reduce in half
int scale = 1;
while ((o.outWidth / scale) * (o.outHeight / scale) * 4 > maxMemoryForImage)
scale *= 2;
// Decode with inSampleSize
BitmapFactory.Options o2 = new BitmapFactory.Options();
o2.inSampleSize = scale;
fis = new FileInputStream(f);
try
{
b = BitmapFactory.decodeStream(fis, null, o2);
}
finally
{
fis.close();
}
}
catch (IOException e)
{
}
return b;
}
我将此位图使用的最大内存设置为最大已分配内存的25%,您可能需要根据需要进行调整,并确保此位图被清理,并且当您使用完它时,它不会停留在内存中。通常我使用这段代码来执行图像旋转(源和目标位图),所以我的应用程序需要同时在内存中加载2个位图,25%给了我一个很好的缓冲区,而不会在执行图像旋转时耗尽内存。
希望这能帮助到一些人。
我使用解码文件描述符,它为我工作:
FileInputStream fileInputStream = null;
try {
fileInputStream = new FileInputStream(file);
FileDescriptor fd = fileInputStream.getFD();
Bitmap imageBitmap = decodeSampledBitmapFromDescriptor(fd , 612,
816);
imageView.setImageBitmap(imageBitmap);
} catch (Exception e) {
e.printStackTrace();
}finally {
if(fileInputStream != null){
try {
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
从文件描述符解码采样位图的代码:
/**
* Decode and sample down a bitmap from a file input stream to the requested width and height.
*
* @param fileDescriptor The file descriptor to read from
* @param reqWidth The requested width of the resulting bitmap
* @param reqHeight The requested height of the resulting bitmap
* @return A bitmap sampled down from the original with the same aspect ratio and dimensions
* that are equal to or greater than the requested width and height
*/
public static Bitmap decodeSampledBitmapFromDescriptor(
FileDescriptor fileDescriptor, int reqWidth, int reqHeight) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options);
}
/**
* Calculate an inSampleSize for use in a {@link android.graphics.BitmapFactory.Options} object when decoding
* bitmaps using the decode* methods from {@link android.graphics.BitmapFactory}. This implementation calculates
* the closest inSampleSize that will result in the final decoded bitmap having a width and
* height equal to or larger than the requested width and height. This implementation does not
* ensure a power of 2 is returned for inSampleSize which can be faster when decoding but
* results in a larger bitmap which isn't as useful for caching purposes.
*
* @param options An options object with out* params already populated (run through a decode*
* method with inJustDecodeBounds==true
* @param reqWidth The requested width of the resulting bitmap
* @param reqHeight The requested height of the resulting bitmap
* @return The value to be used for inSampleSize
*/
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) {
// Calculate ratios of height and width to requested height and width
final int heightRatio = Math.round((float) height / (float) reqHeight);
final int widthRatio = Math.round((float) width / (float) reqWidth);
// Choose the smallest ratio as inSampleSize value, this will guarantee a final image
// with both dimensions larger than or equal to the requested height and width.
inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
// This offers some additional logic in case the image has a strange
// aspect ratio. For example, a panorama may have a much larger
// width than height. In these cases the total pixels might still
// end up being too large to fit comfortably in memory, so we should
// be more aggressive with sample down the image (=larger inSampleSize).
final float totalPixels = width * height;
// Anything more than 2x the requested pixels we'll sample down further
final float totalReqPixelsCap = reqWidth * reqHeight * 2;
while (totalPixels / (inSampleSize * inSampleSize) > totalReqPixelsCap) {
inSampleSize++;
}
}
return inSampleSize;
}