赞
踩
我有一个列表视图,每行都有几个图像按钮。 当您单击列表行时,它将启动一个新活动。 由于相机布局存在问题,我不得不构建自己的标签。 为结果而启动的活动是地图。 如果单击我的按钮以启动图像预览(将图像从SD卡加载),则应用程序将从活动返回到listview
活动,再返回到结果处理程序,以重新启动我的新活动,无非就是图像小部件。
列表视图上的图像预览是使用光标和ListAdapter
。 这非常简单,但是我不确定如何放置一个调整大小后的图像(即,较小的位大小而不是像素作为飞行中图像按钮的src
。因此,我只是调整了从电话摄像头传来的图像的大小。
问题是,当它尝试返回并重新启动第二个活动时,出现内存不足错误。
这将是更可取的,因为我也需要对每行中的小部件/元素的属性进行一些更改,因为由于焦点问题而无法使用触摸屏选择一行。 ( 我可以用滚球。 )
一旦禁用列表视图中的图像,它就会再次正常工作。
仅供参考:这就是我的做法:
- 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 Training类“ 高效显示位图 ”为理解和处理异常java.lang.OutOfMemoryError: bitmap size exceeds VM budget
提供了一些重要信息java.lang.OutOfMemoryError: bitmap size exceeds VM budget
加载位图时位java.lang.OutOfMemoryError: bitmap size exceeds VM budget
。
BitmapFactory
类提供了几种用于从各种来源创建Bitmap
解码方法( decodeByteArray()
, decodeFile()
, decodeResource()
等)。 根据您的图像数据源选择最合适的解码方法。 这些方法尝试为构造的位图分配内存,因此很容易导致OutOfMemory
异常。 每种类型的解码方法都有其他签名,可让您通过BitmapFactory.Options
类指定解码选项。 解码时将inJustDecodeBounds
属性设置为true
可以避免内存分配,为位图对象返回null
,但是设置outWidth
, 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
异常,请在解码位图之前检查其大小,除非您完全相信该源可为您提供大小可预测的图像数据,以适合可用内存。
现在已经知道了图像尺寸,可以将它们用于确定是否应将完整图像加载到内存中,或者是否应该加载子采样版本。 以下是一些要考虑的因素:
例如,如果将1024x768像素的图像最终显示在ImageView
中的128x96像素的缩略图中,则不值得将其加载到内存中。
要告诉解码器对图像进行二次采样,将较小的版本加载到内存中,请在BitmapFactory.Options
对象中将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的幂是因为解码器根据
inSampleSize
文档通过舍入到最接近的2的幂来使用最终值。
要使用此方法,请先将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);
- }
使用此方法可以轻松地将任意大尺寸的位图加载到显示100x100像素缩略图的ImageView
中,如以下示例代码所示:
- mImageView.setImageBitmap(
- decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100));
通过根据需要替换适当的BitmapFactory.decode*
方法,可以遵循类似的过程来解码其他来源的位图。
这对我有用!
- 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());
- }
- }
通过调用System.gc()
等不能完全解决此类OutofMemoryException
。
通过参考
活动生命周期
活动状态由操作系统本身决定,取决于每个进程的内存使用情况和每个进程的优先级。
您可以考虑所使用的每个位图图片的大小和分辨率。 我建议减小尺寸,重新采样以降低分辨率,请参考画廊的设计(一幅小图片PNG和一幅原始图片。)
仅在Android模拟器中会发生此问题。 我在模拟器中也遇到了这个问题,但是当我检入设备时,它运行良好。
因此,请检入设备。 它可以在设备中运行。
我最近看到了很多有关OOM异常和缓存的问题。 开发人员指南对此有非常好的文章 ,但有些人往往无法以合适的方式实现它。
因此,我编写了一个示例应用程序,演示了在Android环境中的缓存。 此实现尚未获得OOM。
在此答案的末尾找到源代码的链接。
ListView
,它根本不会下载之间的位图 正在下载的图像是来自Flickr的图像(75x75)。 但是,请放置您要处理的任何图像URL,如果超出最大数量,应用程序将按比例缩小它。 在此应用程序中,URL仅位于String
数组中。
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文件中的任何内容。
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
池中的工作队列限制。
您可以从https://www.dropbox.com/s/pvr9zyl811tfeem/ListViewImageCache.zip下载源代码。
我已经对此进行了几周的测试,但还没有一个OOM异常。 我已经在模拟器,Nexus One和Nexus S上对此进行了测试。我已经测试了包含高清质量图像的图像URL。 唯一的瓶颈是下载时间更长。
我可以想象只有一种可能的情况,即OOM会出现,也就是说,如果我们下载许多非常大的图像,并且在将它们缩放和放入缓存之前,它们将同时占用更多内存并导致OOM。 但这毕竟不是一个理想的情况,而且很可能不可能以更可行的方式解决。
报告评论中的错误! :-)
这对我有用。
- 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);
这是在C#monodroid上。 您可以轻松更改图像的路径。 这里重要的是要设置的选项。
此代码将有助于从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;
- }
- }
这似乎是与社区共享我的实用程序类以加载和处理图像的合适位置,欢迎您使用和自由修改它。
- 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;
- }
- }
通常android设备的堆大小只有16MB(根据设备/操作系统的不同,请参阅post Heap Sizes ),如果您正在加载图像并且大小超过16MB,则会抛出内存不足异常,而不是将Bitmap用于,加载来自SD卡,资源或什至来自网络的图像尝试使用getImageUri ,加载位图需要更多内存,或者如果使用该位图完成工作,则可以将位图设置为null。
我认为避免OutOfMemoryError
最佳方法是面对并理解它。
我制作了一个有意引起OutOfMemoryError
的应用程序 ,并监视内存使用情况。
在使用此应用程序进行了大量实验后,我得出以下结论:
我要先介绍Honey Comb之前的SDK版本。
位图存储在本机堆中,但会自动收集垃圾,因此无需调用recycle()。
如果{VM堆大小} + {已分配的本机堆内存}> = {设备的VM堆大小限制},而您尝试创建位图,则会引发OOM。
注意:计算的是VM HEAP SIZE,而不是VM分配的内存。
即使增加了分配的VM内存,VM Heap大小也将永远不会缩小。
因此,您必须将峰值VM内存保持尽可能低,以防止VM堆大小变得太大而无法为位图节省可用内存。
手动调用System.gc()是没有意义的,系统会先尝试调用它,然后再尝试增加堆大小。
本机堆大小也永远不会缩小,但不算在OOM中,因此无需担心。
然后,让我们讨论从Honey Comb开始的SDK。
位图存储在VM堆中,本机内存不计入OOM。
OOM的条件要简单得多:{VM堆大小}> = {设备的VM堆大小限制}。
因此,您拥有更多可用内存来创建具有相同堆大小限制的位图,因此不太可能引发OOM。
这是我对垃圾回收和内存泄漏的一些观察。
您可以在应用程序中自己查看。 如果活动在销毁活动后执行了仍在运行的AsyncTask,则在AsyncTask完成之前将不会收集垃圾。
这是因为AsyncTask是匿名内部类的实例,它包含Activity的引用。
如果任务在后台线程的IO操作中被阻止,则调用AsyncTask.cancel(true)不会停止执行。
回调也是匿名内部类,因此,如果项目中的静态实例将其保留且不释放它们,则内存将泄漏。
如果您安排了重复任务或延迟任务(例如Timer),并且没有在onPause()中调用cancel()和purge(),则内存将被泄漏。
这里的答案很好,但是我想要一个完全可用的课程来解决这个问题。所以我做了一个。
这是我的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;
- }
-
- }
在我的一个应用程序中,我需要从Camera/Gallery
。 如果用户单击“摄像机”中的图像(可能是2MP,5MP或8MP),则图像大小从kB
s到MB
s不等。 如果图像大小小于(或高达1-2MB)以上代码工作正常,但如果我的图像大小大于4MB或5MB,则OOM
进入框架:(
然后我努力解决了这个问题,最后我对Fedor做了以下改进(感谢Fedor做出了如此出色的解决方案)代码:)
- 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;
-
- }
我希望这将有助于哥们面临同样的问题!
有关更多信息,请参阅此
我的2分钱:我使用以下位图解决了OOM错误:
a)将我的图像缩放2倍
b)在我的自定义适配器中将Picasso库用于ListView,并在getView中进行如下调用: Picasso.with(context).load(R.id.myImage).into(R.id.myImageView);
这里的所有解决方案都需要设置IMAGE_MAX_SIZE。 这限制了设备使用更强大的硬件,如果图像尺寸太小,则在高清屏幕上看起来很难看。
我提出了一种可与我的Samsung 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%的位图为我提供了良好的缓冲区,并且在执行图像旋转时不会耗尽内存。
希望这可以帮助某人。
对于从SdCard中选择的每个图像或可绘制的位图对象,请使用这些代码。
- 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;
使用ImageData_Path.get(img_pos).getPath()的图像路径。
不幸的是,如果上述方法均无效,则将其添加到清单文件中。 内部应用标签
- <application
- android:largeHeap="true"
我对Fedor的代码做了一些小的改进。 它基本上具有相同的功能,但是没有(在我看来)丑陋的while循环,并且总是产生2的幂。 感谢Fedor提出了原始解决方案,直到发现他的存在,我才被困住了,然后我得以做出这个:)
- 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;
- }
使用此bitmap.recycle();
这可以帮助您解决任何图像质量问题。
我有一个更有效的解决方案,不需要进行任何缩放。 只需将您的位图解码一次,然后针对其名称将其缓存在映射中。 然后只需根据名称检索位图,然后在ImageView中进行设置。 没有什么需要做的了。
这将起作用,因为已解码位图的实际二进制数据未存储在dalvik VM堆中。 它存储在外部。 因此,每次解码位图时,它都会在VM堆之外分配内存,而该内存不会被GC回收
为了帮助您更好地理解这一点,假设您已将ur图像保存在drawable文件夹中。 您只需通过执行getResources()。getDrwable(R.drawable。)来获取图像。 这不会每次都解码图像,而是每次调用它时都重新使用已经解码的实例。 因此,从本质上讲,它是缓存的。
现在,由于您的图像位于某个位置的文件中(甚至可能来自外部服务器),因此您有责任缓存已解码的位图实例,以便在需要的任何地方重复使用。
希望这可以帮助。
我做了以下工作来拍摄图像并即时调整其大小。 希望这可以帮助
- Bitmap bm;
- bm = Bitmap.createScaledBitmap(BitmapFactory.decodeFile(filepath), 100, 100, true);
- mPicture = new ImageView(context);
- mPicture.setImageBitmap(bm);
这里有两个问题。
似乎这是一个长期运行的问题,有许多不同的解释。 我在这里提出了两个最常见的答案,但是这些都没有解决我关于VM的问题,声称VM无法承受执行该过程的解码部分所需的字节数。 经过一番挖掘后,我了解到真正的问题是解码过程不占用NATIVE堆。
看到这里: BitmapFactory OOM让我发疯
这导致我进入另一个讨论线程,在那里我找到了解决该问题的更多解决方案。 一种是调用System.gc();
在显示图像后手动进行。 但这实际上使您的应用程序使用更多的内存,以减少本机堆。 从2.0版(甜甜圈)开始,更好的解决方案是使用BitmapFactory选项“ inPurgeable”。 所以我只是添加了o2.inPurgeable=true;
就在o2.inSampleSize=scale;
。
有关该主题的更多信息: 内存堆的限制是否仅为6M?
现在,说了这么多,我对Java和Android也是一个完全的傻瓜。 因此,如果您认为这是解决此问题的可怕方法,那么您可能是对的。 ;-)但这对我来说是个奇迹,我发现现在无法在堆缓存中运行VM。 我可以发现的唯一缺点是,您正在丢弃缓存的绘制图像。 这意味着,如果您向右返回该图像,则每次都将重新绘制它。 就我的应用程序如何工作而言,这并不是真正的问题。 你的旅费可能会改变。
这是一个已知的错误 ,不是因为文件很大。 由于Android会缓存Drawable,因此使用少量图像后,内存就会耗尽。 但是我通过跳过android默认的缓存系统找到了另一种方法。
解决方案 :将图像移到“资产”文件夹,然后使用以下函数获取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);
- }
我已经通过以下方式解决了相同的问题。
- 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);
我遇到了同样的问题,并通过避免使用BitmapFactory.decodeStream或encodeFile函数来解决它,而是使用了BitmapFactory.decodeFileDescriptor
decodeFileDescriptor
看起来好像调用了与ADCStream / decodeFile不同的本机方法。
无论如何,这是行得通的(请注意,我添加了一些上述选项,但这并没有什么不同。关键是对BitmapFactory.decodeFileDescriptor的调用,而不是defineStream或encodeFile的调用 ):
- 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;
- }
我认为在解码流/解码文件中使用的本机函数存在问题。 我已经确认,使用解码文件描述符时会调用其他本机方法。 我还读到的是“图像(位图)不是通过标准Java方式分配的,而是通过本机调用分配的;分配是在虚拟堆之外完成的,但要对它进行计数! ”
要修复OutOfMemory错误,您应该执行以下操作:
- BitmapFactory.Options options = new BitmapFactory.Options();
- options.inSampleSize = 8;
- Bitmap preview_bitmap = BitmapFactory.decodeStream(is, null, options);
此inSampleSize
选项可减少内存消耗。
这是一个完整的方法。 首先,它读取图像大小而不解码内容本身。 然后,它找到最佳的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;
- }
上面的答案都对我没有用,但是我确实提出了一个可怕的丑陋变通办法来解决问题。 我在项目中添加了一个很小的1x1像素图像作为资源,并在调用垃圾回收之前将其加载到ImageView中。 我认为可能是ImageView没有释放位图,所以GC从未选择它。 这很丑陋,但现在似乎可以使用。
- 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.
我来自iOS经验,对于发现诸如加载和显示图像这样的基本问题感到沮丧,我感到很沮丧。 毕竟,遇到此问题的每个人都在尝试显示合理大小的图像。 无论如何,这是修复我的问题的两个更改(并使我的应用程序响应速度很快)。
1)每次执行BitmapFactory.decodeXYZ()
,请确保传入inPurgeable
设置为true
的BitmapFactory.Options
(最好将inInputShareable
设置为true
)。
2)永远不要使用Bitmap.createBitmap(width, height, Config.ARGB_8888)
。 我的意思是永远! 经过数次传递之后,我再也没有出现过这样的事情不会引起内存错误的情况。 无论如何,都没有多少recycle()
, System.gc()
。 它总是引发异常。 实际工作的另一种方法是在可绘制对象(或使用上面的步骤1解码的另一个位图)中有一个虚拟图像,将其缩放到所需的大小,然后操作生成的位图(例如将其传递到Canvas)以获取更多乐趣)。 因此,您应该使用的是: Bitmap.createScaledBitmap(srcBitmap, width, height, false)
。 如果出于某种原因必须使用蛮力创建方法,则至少传递Config.ARGB_4444
。
几乎可以保证,即使没有几天,您也可以节省几个小时。 关于缩放图像等的所有讨论都无法真正实现(除非您考虑使用错误的尺寸或降级的图像作为解决方案)。
我只是在几分钟前遇到了这个问题。 我通过在管理Listview适配器方面做得更好而解决了它。 我以为使用的数百个50x50px图像是个问题,事实证明,每次显示该行时,我都试图为自己的自定义视图充气。 只需通过测试以查看行是否已膨胀,就可以消除此错误,并且我正在使用数百个位图。 这实际上是一个Spinner,但是基本适配器对ListView的工作都相同。 这个简单的修复程序也极大地提高了适配器的性能。
- @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);
- }
- ...
我整天都在测试这些解决方案,而对我来说唯一有效的方法就是上述获取图像并手动调用GC的方法,我知道这不是必需的,但这是唯一有效的方法当我将我的应用置于重负载测试下时,在活动之间进行切换。 我的应用在的列表视图中有一个缩略图列表(让我们说活动A),当您单击其中一个图像时,它会带您到另一个活动(让我们说活动B),该活动显示该项目的主图像。 当我在两个活动之间来回切换时,最终会出现OOM错误,并且该应用程序将强制关闭。
当我到达列表视图的一半时,它将崩溃。
现在,当我在活动B中实现以下内容时,我可以毫无问题地遍历整个列表视图,并且不断前进……而且速度很快。
- @Override
- public void onDestroy()
- {
- Cleanup();
- super.onDestroy();
- }
-
- private void Cleanup()
- {
- bitmap.recycle();
- System.gc();
- Runtime.getRuntime().gc();
- }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。