在将图像加载到Bitmap对象时出现内存不足的问题
我有一个列表视图与每行上的几个图像button。 当您单击列表行时,它将启动一个新的活动。 由于相机布局的问题,我不得不build立自己的选项卡。 为结果启动的活动是一张地图。 如果我点击我的button启动图像预览(从SD卡加载图像),应用程序从活动返回到listview
活动的结果处理程序重新启动我的新活动,这不过是一个图像小部件。
列表视图上的图像预览正在使用游标和ListAdapter
。 这使得它非常简单,但我不知道如何可以放置一个resize的图像(即小图像大小不是像素的图像button的src
,所以我只是调整了手机相机的图像。
问题是,当它尝试返回并重新启动第二个活动时,出现内存不足错误。
- 有没有一种方法,我可以轻松地逐行build立列表适配器,在哪里我可以在飞行( 位智者 )resize?
这将是更可取的,因为我也需要对每一行中的部件/元素的属性进行一些更改,因为焦点问题,我无法select带触摸屏的行。 ( 我可以使用滚球。 )
- 我知道我可以做一个带外resize和保存我的图像,但这不是我想要做的,但一些示例代码将是很好的。
只要我在列表视图中禁用图像,它再次正常工作。
仅供参考:我是这么做的:
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
显示图像时我也有一个新的错误:
01-25 22:13:18.594: DEBUG/skia(4204): xxxxxxxxxxx jpeg error 20 Improper call to JPEG library in state %d 01-25 22:13:18.604: INFO/System.out(4204): resolveUri failed on bad bitmap uri: 01-25 22:13:18.694: ERROR/dalvikvm-heap(4204): 6291456-byte external allocation too large for this process. 01-25 22:13:18.694: ERROR/(4204): VM won't let us allocate 6291456 bytes 01-25 22:13:18.694: DEBUG/skia(4204): xxxxxxxxxxxxxxxxxxxx allocPixelRef failed
Android培训课程“ 高效地显示位图 ”为理解和处理exceptionjava.lang.OutOfMemoryError: bitmap size exceeds VM budget
提供了一些很好的信息java.lang.OutOfMemoryError: bitmap size exceeds VM budget
在加载Bitmaps时,位java.lang.OutOfMemoryError: bitmap size exceeds VM budget
。
读取位图尺寸和types
BitmapFactory
类提供了多种解码方法( decodeByteArray()
, decodeFile()
, decodeResource()
等),用于从各种来源创buildBitmap
。 根据您的图像数据源select最合适的解码方法。 这些方法试图为构造的位图分配内存,因此很容易导致OutOfMemory
exception。 每种types的解码方法都有额外的签名,可让您通过BitmapFactory.Options
类指定解码选项。 在解码过程中将inJustDecodeBounds
属性设置为true
可以避免内存分配,对于位图对象返回null
,但是设置了outWidth
, outHeight
和outMimeType
。 这种技术允许您在构build位图之前读取图像数据的尺寸和types(以及内存分配)。
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
exception,请在解码之前检查位图的尺寸,除非您绝对相信源为您提供可预测大小的图像数据,以便在可用内存中进行适当的调整。
将缩小的版本加载到内存中
现在图像尺寸是已知的,它们可以用来决定是否应该将完整图像加载到内存中,或者是否应该加载二次采样版本。 这里有一些要考虑的因素:
- 预计在内存中加载完整映像的内存使用情况。
- 在给定应用程序的任何其他内存需求的情况下,您愿意承担加载此映像的内存量。
- 图像将被加载到的目标ImageView或UI组件的尺寸。
- 当前设备的屏幕大小和密度。
例如,如果最终将在ImageView
中以128×96像素的缩略图显示,则将1024×768像素的图像加载到内存中是不值得的。
为了告诉解码器对图像进行二次采样,将较小的版本加载到内存中,在BitmapFactory.Options
对象中将inSampleSize
设置为true
。 例如,分辨率为2048×1536且用inSampleSize
为4解码的图像会生成大约512×384的位图。 加载到内存使用0.75MB,而不是12MB的完整图像(假设位图configurationARGB_8888
)。 以下是根据目标宽度和高度计算两个幂的样本大小值的方法:
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; }
注意 :由于解码器按照
inSampleSize
文档中的四舍五入取两个最接近的幂来计算两个值的inSampleSize
。
要使用此方法,请首先使用inJustDecodeBounds
设置为true
进行解码,然后通过选项,然后使用新的inSampleSize
值和inJustDecodeBounds
设置为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); }
此方法可以轻松地将任意大小的位图加载到显示100×100像素缩略图的ImageView
中,如以下示例代码所示:
mImageView.setImageBitmap( decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100));
您可以按照类似的过程来解码来自其他来源的位图,方法是根据需要replace相应的BitmapFactory.decode*
方法。
要修复OutOfMemory错误,你应该这样做:
BitmapFactory.Options options = new BitmapFactory.Options(); options.inSampleSize = 8; Bitmap preview_bitmap = BitmapFactory.decodeStream(is, null, options);
这inSampleSize
选项减less内存消耗。
这是一个完整的方法。 首先它读取图像大小而不解码内容本身。 然后find最好的inSampleSize
值,它应该是2的幂,最后对图像进行解码。
// Decodes image and scales it to reduce memory consumption private Bitmap decodeFile(File f) { try { // Decode image size BitmapFactory.Options o = new BitmapFactory.Options(); o.inJustDecodeBounds = true; BitmapFactory.decodeStream(new FileInputStream(f), null, o); // The new size we want to scale to final int REQUIRED_SIZE=70; // Find the correct scale value. It should be the power of 2. int scale = 1; while(o.outWidth / scale / 2 >= REQUIRED_SIZE && o.outHeight / scale / 2 >= REQUIRED_SIZE) { scale *= 2; } // Decode with inSampleSize BitmapFactory.Options o2 = new BitmapFactory.Options(); o2.inSampleSize = scale; return BitmapFactory.decodeStream(new FileInputStream(f), null, o2); } catch (FileNotFoundException e) {} return null; }
我对Fedor的代码做了一点改进。 它基本上是一样的,但没有(在我看来)丑陋的循环,它总是导致两个权力。 荣誉给Fedor做出最初的解决scheme,我一直坚持到我find他的,然后我才能做出这个:)
private Bitmap decodeFile(File f){ Bitmap b = null; //Decode image size BitmapFactory.Options o = new BitmapFactory.Options(); o.inJustDecodeBounds = true; FileInputStream fis = new FileInputStream(f); BitmapFactory.decodeStream(fis, null, o); fis.close(); int scale = 1; if (o.outHeight > IMAGE_MAX_SIZE || o.outWidth > IMAGE_MAX_SIZE) { scale = (int)Math.pow(2, (int) Math.ceil(Math.log(IMAGE_MAX_SIZE / (double) Math.max(o.outHeight, o.outWidth)) / Math.log(0.5))); } //Decode with inSampleSize BitmapFactory.Options o2 = new BitmapFactory.Options(); o2.inSampleSize = scale; fis = new FileInputStream(f); b = BitmapFactory.decodeStream(fis, null, o2); fis.close(); return b; }
我来自iOS的经验,我很沮丧地发现一个如加载和显示图像这样基本的问题。 毕竟,每个有这个问题的人都试图显示合理大小的图像。 无论如何,这里有两个修改我的问题(使我的应用程序非常敏感)。
1)每次你做BitmapFactory.decodeXYZ()
,确保传入一个BitmapFactory.Options
与inPurgeable
设置为true
(最好与inInputShareable
也设置为true
)。
2)不要使用Bitmap.createBitmap(width, height, Config.ARGB_8888)
。 我的意思是永远不会! 我从来没有这样的事情没有提高几次通过后的内存错误。 没有数量的recycle()
, System.gc()
,无论帮助。 它总是提出exception。 实际工作的另一种方式是在绘图中(或者使用上述步骤1解码的另一个Bitmap)中创build一个虚拟图像,将其重新缩放到任何所需的位置,然后操作生成的Bitmap(例如将其传递到Canvas为更多的乐趣)。 所以,你应该使用什么: Bitmap.createScaledBitmap(srcBitmap, width, height, false)
。 如果因为某种原因你必须使用强制创build方法,那么至less要通过Config.ARGB_4444
。
如果不是几天,这几乎可以保证你的工作时间。 所有关于缩放图像的内容都没有效果(除非您考虑获得错误的尺寸或降级的图像解决scheme)。
这是一个已知的错误 ,这不是因为大文件。 由于AndroidcachingDrawables,在使用less量图像之后会出现内存不足。 但我已经find了一个替代方式,通过跳过android默认caching系统。
解决scheme :将图像移动到“assets”文件夹并使用以下函数获取BitmapDrawable:
public static Drawable getAssetImage(Context context, String filename) throws IOException { AssetManager assets = context.getResources().getAssets(); InputStream buffer = new BufferedInputStream((assets.open("drawable/" + filename + ".png"))); Bitmap bitmap = BitmapFactory.decodeStream(buffer); return new BitmapDrawable(context.getResources(), bitmap); }
我有这个相同的问题,并通过避免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方式分配,而是通过本地调用;分配是在虚拟堆外部完成的,但是会被计算在内! ”
我认为避免OutOfMemoryError
最好办法是面对它并理解它。
我做了一个应用程序故意导致OutOfMemoryError
,并监视内存使用情况。
在我完成了这个应用程序的大量实验后,我得到了以下结论:
在Honey Comb之前,我会先谈谈SDK的版本。
-
位图存储在本机堆中,但会自动收集垃圾,调用recycle()是不必要的。
-
如果{VM堆大小} + {分配的本机堆内存}> = {设备的VM堆大小限制},并且您尝试创build位图,则会抛出OOM。
注意:VM HEAP SIZE计数而不是VM ALLOCATED MEMORY。
-
即使分配的虚拟机内存已经缩小,虚拟机堆大小也不会缩小。
-
因此,您必须尽可能降低峰值虚拟机内存,以防止虚拟机堆大小增长太大而无法保存位图的可用内存。
-
手动调用System.gc()是没有意义的,系统会在尝试增加堆大小之前调用它。
-
原生堆大小永远不会缩小,但不计入OOM,因此不必担心。
然后,我们来谈谈Honey Comb的SDK开始。
-
位图存储在VM堆中,本机内存不计入OOM。
-
OOM的条件要简单得多:{VM堆大小}> = {设备的VM堆大小限制}。
-
所以你有更多的可用内存来创build具有相同堆大小限制的位图,OOM不太可能被抛出。
这里是我的一些关于垃圾收集和内存泄漏的观察。
你可以在应用程序中看到它自己。 如果一个Activity执行了一个在Activity被销毁后仍然运行的AsyncTask,那么在AsyncTask完成之前,这个Activity不会被垃圾收集。
这是因为AsyncTask是一个匿名内部类的实例,它拥有一个Activity的引用。
如果任务在后台线程中的IO操作中被阻塞,调用AsyncTask.cancel(true)将不会停止执行。
callback函数也是匿名的内部类,所以如果你的项目中有一个静态实例持有它们,并且不释放它们,内存将被泄漏。
如果你计划了一个重复或延迟的任务,例如一个定时器,并且你不在onPause()中调用cancel()和purge(),内存就会被泄漏。
最近我看到很多关于OOMexception和caching的问题。 开发人员指南有一个非常好的文章 ,但有些往往无法以合适的方式实现它。
因此,我编写了一个示例应用程序,演示在Android环境中进行caching。 这个实现还没有得到一个OOM。
查看这个答案的结尾以获得源代码的链接。
要求:
- Android API 2.1或更高版本(我根本无法在API 1.6中获得应用程序的可用内存 – 这是API 1.6中不可用的唯一代码)
- Android支持包
特征:
- 如果存在方向更改 ,则使用单例保留高速caching
- 使用八分之一的应用程序内存caching(如果你想要修改)
- 大的位图被缩放 (你可以定义你想要的最大像素)
- 在下载位图之前控制可用的互联网连接
- 确保您只是每行实例化一个任务
- 如果您将
ListView
扔掉,它将不会下载之间的位图
这不包括:
- 磁盘caching。 无论如何,这应该很容易实现 – 只需指向从磁盘抓取位图的不同任务即可
示例代码:
正在下载的图像是来自Flickr的图像(75×75)。 然而,把你想要处理的任何图像URL,如果超过最大值,应用程序将缩小它。 在这个应用程序中,url只是一个String
数组。
LruCache
有一个处理位图的好方法。 然而,在这个应用程序中,我将LruCache
一个实例放入另一个caching类中,以便让应用程序更加可行。
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(); } } }
除非要实现磁盘caching,否则不需要编辑Cache.java文件中的任何内容。
MainActivity.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
池的工作队列限制。
下载:
You can download the source code from https://www.dropbox.com/s/pvr9zyl811tfeem/ListViewImageCache.zip .
Last words:
I have tested this for a few weeks now, I haven't gotten a single OOM exception yet. I have tested this on the emulator, on my Nexus One and on my Nexus S. I have tested image urls that contain images that were in HD quality. The only bottleneck is that it takes more time to download.
There is only one possible scenario where I can imagine that the OOM will appear, and that is if we download many, really big images, and before they get scaled and put into cache, will simultaneously take up more memory and cause an OOM. But that isn't even an ideal situation anyway and it most likely won't be possible to solve in a more feasible way.
Report errors in the comments! 🙂
I did the following to take the image and resize it on the fly. 希望这可以帮助
Bitmap bm; bm = Bitmap.createScaledBitmap(BitmapFactory.decodeFile(filepath), 100, 100, true); mPicture = new ImageView(context); mPicture.setImageBitmap(bm);
It seems that this is a very long running problem, with a lot of differing explanations. I took the advice of the two most common presented answers here, but neither one of these solved my problems of the VM claiming it couldn't afford the bytes to perform the decoding part of the process. After some digging I learned that the real problem here is the decoding process taking away from the NATIVE heap.
See here: BitmapFactory OOM driving me nuts
That lead me to another discussion thread where I found a couple more solutions to this problem. One is to call System.gc();
manually after your image is displayed. But that actually makes your app use MORE memory, in an effort to reduce the native heap. The better solution as of the release of 2.0 (Donut) is to use the BitmapFactory option "inPurgeable". So I simply added o2.inPurgeable=true;
just after o2.inSampleSize=scale;
。
More on that topic here: Is the limit of memory heap only 6M?
Now, having said all of this, I am a complete dunce with Java and Android too. So if you think this is a terrible way to solve this problem, you are probably right. 😉 But this has worked wonders for me, and I have found it impossible to run the VM out of heap cache now. The only drawback I can find is that you are trashing your cached drawn image. Which means if you go RIGHT back to that image, you are redrawing it each and every time. In the case of how my application works, that is not really a problem. 你的旅费可能会改变。
unfortunately if None of the Above works, then Add this to your Manifest file. Inside application tag
<application android:largeHeap="true"
I have a much more effective solution which does not need scaling of any sort. Simply decode your bitmap only once and then cache it in a map against its name. Then simply retrieve the bitmap against the name and set it in the ImageView. There is nothing more that needs to be done.
This will work because the actual binary data of the decoded bitmap is not stored within the dalvik VM heap. It is stored externally. So every time you decode a bitmap, it allocates memory outside of VM heap which is never reclaimed by GC
To help you better appreciate this, imagine you have kept ur image in the drawable folder. You just get the image by doing a getResources().getDrwable(R.drawable.). This will NOT decode your image everytime but re-use an already decoded instance everytime you call it. So in essence it is cached.
Now since your image is in a file somewhere (or may even be coming from an external server), it is YOUR responsibility to cache the decoded bitmap instance to be reused any where it is needed.
希望这可以帮助。
Use this bitmap.recycle();
This helps without any image quality issue.
There are two issues here….
- Bitmap memory isn't in the VM heap but rather in the native heap – see BitmapFactory OOM driving me nuts
- Garbage collection for the native heap is lazier than the VM heap – so you need to be quite aggressive about doing bitmap.recycle and bitmap =null every time you go through an Activity's onPause or onDestroy
这对我有用!
public Bitmap readAssetsBitmap(String filename) throws IOException { try { BitmapFactory.Options options = new BitmapFactory.Options(); options.inPurgeable = true; Bitmap bitmap = BitmapFactory.decodeStream(assets.open(filename), null, options); if(bitmap == null) { throw new IOException("File cannot be opened: It's value is null"); } else { return bitmap; } } catch (IOException e) { throw new IOException("File cannot be opened: " + e.getMessage()); } }
I have resolved the same issue in the following manner.
Bitmap b = null; Drawable d; ImageView i = new ImageView(mContext); try { b = Bitmap.createBitmap(320,424,Bitmap.Config.RGB_565); b.eraseColor(0xFFFFFFFF); Rect r = new Rect(0, 0,320 , 424); Canvas c = new Canvas(b); Paint p = new Paint(); p.setColor(0xFFC0C0C0); c.drawRect(r, p); d = mContext.getResources().getDrawable(mImageIds[position]); d.setBounds(r); d.draw(c); /* BitmapFactory.Options o2 = new BitmapFactory.Options(); o2.inTempStorage = new byte[128*1024]; b = BitmapFactory.decodeStream(mContext.getResources().openRawResource(mImageIds[position]), null, o2); o2.inSampleSize=16; o2.inPurgeable = true; */ } catch (Exception e) { } i.setImageBitmap(b);
None of the answers above worked for me, but I did come up with a horribly ugly workaround that solved the problem. I added a very small, 1×1 pixel image to my project as a resource, and loaded it into my ImageView before calling into garbage collection. I think it might be that the ImageView was not releasing the Bitmap, so GC never picked it up. It's ugly, but it seems to be working for now.
if (bitmap != null) { bitmap.recycle(); bitmap = null; } if (imageView != null) { imageView.setImageResource(R.drawable.tiny); // This is my 1x1 png. } System.gc(); imageView.setImageBitmap(...); // Do whatever you need to do to load the image you want.
这对我有用。
Bitmap myBitmap; BitmapFactory.Options options = new BitmapFactory.Options(); options.InPurgeable = true; options.OutHeight = 50; options.OutWidth = 50; options.InSampleSize = 4; File imgFile = new File(filepath); myBitmap = BitmapFactory.DecodeFile(imgFile.AbsolutePath, options);
and this is on C# monodroid. you can easily change the path of the image. what important here is the options to be set.
Great answers here, but I wanted a fully usable class to address this problem.. so I did one.
Here is my BitmapHelper class that is OutOfMemoryError proof 🙂
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; } }
I just ran into this issue a couple minutes ago. I solved it by doing a better job at managing my listview adapter. I thought it was an issue with the hundreds of 50x50px images I was using, turns out I was trying to inflate my custom view each time the row was being shown. Simply by testing to see if the row had been inflated I eliminated this error, and I am using hundreds of bitmaps. This is actually for a Spinner, but the base adapter works all the same for a ListView. This simple fix also greatly improved the performance of the adapter.
@Override public View getView(final int position, View convertView, final ViewGroup parent) { if(convertView == null){ LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); convertView = inflater.inflate(R.layout.spinner_row, null); } ...
I've spent the entire day testing these solutions and the only thing that worked for me is the above approaches for getting the image and manually calling the GC, which I know is not supposed to be necessary, but it is the only thing that worked when I put my app under heavy load testing switching between activities. My app has a list of thumbnail images in a listview in (lets say activity A) and when you click on one of those images it takes you to another activity (lets say activity B) that shows a main image for that item. When I would switch back and forth between the two activities, I would eventually get the OOM error and the app would force close.
When I would get half way down the listview it would crash.
Now when I implement the following in activity B, I can go through the entire listview with no issue and keep going and going and going…and its plenty fast.
@Override public void onDestroy() { Cleanup(); super.onDestroy(); } private void Cleanup() { bitmap.recycle(); System.gc(); Runtime.getRuntime().gc(); }
This seems like the appropriate place to share my utility class for loading and processing images with the community, you are welcome to use it and modify it freely.
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; } }
In one of my application i need to take picture either from Camera/Gallery
. If user click image from Camera(may be 2MP, 5MP or 8MP), image size varies from kB
s to MB
s. If image size is less(or up to 1-2MB) above code working fine but if i have image of size above 4MB or 5MB then OOM
comes in frame 🙁
then i have worked to solve this issue & finally i've made the below improvement to Fedor's(All Credit to Fedor for making such a nice solution) code 🙂
private Bitmap decodeFile(String fPath) { // Decode image size BitmapFactory.Options opts = new BitmapFactory.Options(); /* * If set to true, the decoder will return null (no bitmap), but the * out... fields will still be set, allowing the caller to query the * bitmap without having to allocate the memory for its pixels. */ opts.inJustDecodeBounds = true; opts.inDither = false; // Disable Dithering mode opts.inPurgeable = true; // Tell to gc that whether it needs free // memory, the Bitmap can be cleared opts.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 BitmapFactory.decodeFile(fPath, opts); // The new size we want to scale to final int REQUIRED_SIZE = 70; // Find the correct scale value. int scale = 1; if (opts.outHeight > REQUIRED_SIZE || opts.outWidth > REQUIRED_SIZE) { // Calculate ratios of height and width to requested height and width final int heightRatio = Math.round((float) opts.outHeight / (float) REQUIRED_SIZE); final int widthRatio = Math.round((float) opts.outWidth / (float) REQUIRED_SIZE); // 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. scale = heightRatio < widthRatio ? heightRatio : widthRatio;// } // Decode bitmap with inSampleSize set opts.inJustDecodeBounds = false; opts.inSampleSize = scale; Bitmap bm = BitmapFactory.decodeFile(fPath, opts).copy( Bitmap.Config.RGB_565, false); return bm; }
I hope this will help the buddies facing the same problem!
for more please refer this
This issue only happens in Android emulators. I also faced this issue in an emulator but when I checked in a device then it worked fine.
So please check in a device. It may be run in device.
This code will help to load large bitmap from drawable
public class BitmapUtilsTask extends AsyncTask<Object, Void, Bitmap> { Context context; public BitmapUtilsTask(Context context) { this.context = context; } /** * Loads a bitmap from the specified url. * * @param url The location of the bitmap asset * @return The bitmap, or null if it could not be loaded * @throws IOException * @throws MalformedURLException */ public Bitmap getBitmap() throws MalformedURLException, IOException { // Get the source image's dimensions int desiredWidth = 1000; BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeResource(context.getResources(), R.drawable.green_background , options); int srcWidth = options.outWidth; int srcHeight = options.outHeight; // Only scale if the source is big enough. This code is just trying // to fit a image into a certain width. if (desiredWidth > srcWidth) desiredWidth = srcWidth; // Calculate the correct inSampleSize/scale value. This helps reduce // memory use. It should be a power of 2 int inSampleSize = 1; while (srcWidth / 2 > desiredWidth) { srcWidth /= 2; srcHeight /= 2; inSampleSize *= 2; } // Decode with inSampleSize options.inJustDecodeBounds = false; options.inDither = false; options.inSampleSize = inSampleSize; options.inScaled = false; options.inPreferredConfig = Bitmap.Config.ARGB_8888; options.inPurgeable = true; Bitmap sampledSrcBitmap; sampledSrcBitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.green_background , options); return sampledSrcBitmap; } /** * The system calls this to perform work in a worker thread and delivers * it the parameters given to AsyncTask.execute() */ @Override protected Bitmap doInBackground(Object... item) { try { return getBitmap(); } catch (MalformedURLException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return null; }
}
My 2 cents: i solved my OOM errors with bitmaps by:
a) scaling my images by a factor of 2
b) using Picasso library in my custom Adapter for a ListView, with a one-call in getView like this: Picasso.with(context).load(R.id.myImage).into(R.id.myImageView);
use these code for every image in select from SdCard or drewable to convert bitmap object.
Resources res = getResources(); WindowManager window = (WindowManager) getSystemService(Context.WINDOW_SERVICE); Display display = window.getDefaultDisplay(); @SuppressWarnings("deprecation") int width = display.getWidth(); @SuppressWarnings("deprecation") int height = display.getHeight(); try { if (bitmap != null) { bitmap.recycle(); bitmap = null; System.gc(); } bitmap = Bitmap.createScaledBitmap(BitmapFactory .decodeFile(ImageData_Path.get(img_pos).getPath()), width, height, true); } catch (OutOfMemoryError e) { if (bitmap != null) { bitmap.recycle(); bitmap = null; System.gc(); } BitmapFactory.Options options = new BitmapFactory.Options(); options.inPreferredConfig = Config.RGB_565; options.inSampleSize = 1; options.inPurgeable = true; bitmapBitmap.createScaledBitmap(BitmapFactory.decodeFile(ImageData_Path.get(img_pos) .getPath().toString(), options), width, height,true); } return bitmap;
use your image path instend of ImageData_Path.get(img_pos).getPath() .
All the solutions here require setting a IMAGE_MAX_SIZE. This limits devices with more powerful hardware and if the image size is too low it looks ugly on the HD screen.
I came out with a solution that works with my Samsung Galaxy S3 and several other devices including less powerful ones, with better image quality when a more powerful device is used.
The gist of it is to calculate the maximum memory allocated for the app on a particular device, then set the scale to be lowest possible without exceeding this memory. 代码如下:
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; }
I set the maximum memory used by this bitmap to be 25% of maximum allocated memory, you may need to adjust this to your needs and make sure this bitmap is cleaned up and don't stay in memory when you've finished using it. Typically I use this code to perform image rotation (source and destination bitmap) so my app needs to load 2 bitmaps in memory at the same time, and 25% gives me a good buffer without running out of memory when performing image rotation.
Hope this helps someone out there..
Such OutofMemoryException
cannot be totally resolved by calling the System.gc()
and so on .
By referring to the Activity Life Cycle
The Activity States are determined by the OS itself subject to the memory usage for each process and the priority of each process.
You may consider the size and the resolution for each of the bitmap pictures used. I recommend to reduce the size ,resample to lower resolution , refer to the design of galleries (one small picture PNG , and one original picture.)
Generally android device heap size is only 16MB (varies from device/OS see post Heap Sizes ), if you are loading the images and it crosses the size of 16MB , it will throw out of memory exception, instead of using the Bitmap for , loading images from SD card or from resources or even from network try to using getImageUri , loading bitmap require more memory , or you can set bitmap to null if your work done with that bitmap.