. lang。OutOfMemoryError:位圖大小超過VM預算- Android

[英]java.lang.OutOfMemoryError: bitmap size exceeds VM budget - Android


I developed an application that uses lots of images on Android.

我開發了一個在Android上使用大量圖像的應用程序。

The app runs once, fills the information on the screen (Layouts, Listviews, Textviews, ImageViews, etc) and user reads the information.

該應用程序運行一次,填充屏幕上的信息(布局、列表視圖、textview、imageview等),用戶讀取信息。

There is no animation, no special effects or anything that can fill the memory. Sometimes the drawables can change. Some are android resources and some are files saved in a folder in the SDCARD.

沒有動畫,沒有特效或者任何可以填滿記憶的東西。有時可畫的東西會改變。有些是android資源,有些是保存在SDCARD文件夾中的文件。

Then the user quits (the onDestroy method is executed and app stays in memory by the VM ) and then at some point the user enters again.

然后用戶退出(onDestroy方法被執行,應用程序被VM保存在內存中),然后在某個時候用戶再次進入。

Each time the user enters to the app, I can see the memory growing more and more until user gets the java.lang.OutOfMemoryError.

每次用戶進入應用程序,我都能看到內存越來越大,直到用戶獲得java.lang.OutOfMemoryError。

So what is the best/correct way to handle many images?

那么,處理許多圖像的最佳/正確方法是什么呢?

Should I put them in static methods so they are not loaded all the time? Do I have to clean the layout or the images used in the layout in a special way?

我是否應該將它們放在靜態方法中,以便它們不會一直被加載?我是否需要用一種特殊的方式來清理布局或布局中使用的圖像?

13 个解决方案

#1


69  

It sounds like you have a memory leak. The problem isn't handling many images, it's that your images aren't getting deallocated when your activity is destroyed.

聽起來你好像有內存泄漏。問題不在於處理許多映像,而是當您的活動被破壞時,您的映像不會被釋放。

It's difficult to say why this is without looking at your code. However, this article has some tips that might help:

如果不查看代碼,很難說這是為什么。然而,本文有一些建議可能會有所幫助:

http://android-developers.blogspot.de/2009/01/avoiding-memory-leaks.html

http://android-developers.blogspot.de/2009/01/avoiding-memory-leaks.html

In particular, using static variables is likely to make things worse, not better. You might need to add code that removes callbacks when your application redraws -- but again, there's not enough information here to say for sure.

特別是,使用靜態變量可能會使事情變得更糟,而不是更好。您可能需要添加一些代碼,以便在應用程序重新繪制時刪除回調——但同樣,這里沒有足夠的信息可以確定。

#2


96  

One of the most common errors that I found developing Android Apps is the “java.lang.OutOfMemoryError: Bitmap Size Exceeds VM Budget” error. I found this error frequently on activities using lots of bitmaps after changing orientation: the Activity is destroyed, created again and the layouts are “inflated” from the XML consuming the VM memory available for bitmaps.

我發現開發Android應用程序最常見的錯誤之一是“java.lang”。OutOfMemoryError:位圖大小超過VM預算。在改變方向后,我經常在使用大量位圖的活動中發現這個錯誤:活動被破壞、再次創建,並且布局由於使用位圖可用的VM內存的XML而“膨脹”。

Bitmaps on the previous activity layout are not properly de-allocated by the garbage collector because they have crossed references to their activity. After many experiments I found a quite good solution for this problem.

垃圾收集器不能正確地分配之前活動布局上的位圖,因為它們交叉地引用了它們的活動。經過多次實驗,我找到了一個很好的解決這個問題的方法。

First, set the “id” attribute on the parent view of your XML layout:

首先,在XML布局的父視圖上設置“id”屬性:

    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="fill_parent"
     android:layout_height="fill_parent"
     android:id="@+id/RootView"
     >
     ...

Then, on the onDestroy() method of your Activity, call the unbindDrawables() method passing a reference to the parent View and then do a System.gc().

然后,在活動的onDestroy()方法上,調用unbindDrawables()方法,將引用傳遞給父視圖,然后執行System.gc()。

    @Override
    protected void onDestroy() {
    super.onDestroy();

    unbindDrawables(findViewById(R.id.RootView));
    System.gc();
    }

    private void unbindDrawables(View view) {
        if (view.getBackground() != null) {
        view.getBackground().setCallback(null);
        }
        if (view instanceof ViewGroup) {
            for (int i = 0; i < ((ViewGroup) view).getChildCount(); i++) {
            unbindDrawables(((ViewGroup) view).getChildAt(i));
            }
        ((ViewGroup) view).removeAllViews();
        }
    }

This unbindDrawables() method explores the view tree recursively and:

這個unbindDrawables()方法遞歸地探索了視圖樹,並且:

  1. Removes callbacks on all the background drawables
  2. 刪除對所有后台繪制文件的回調
  3. Removes children on every viewgroup
  4. 刪除每個視圖組上的子元素

#3


10  

To avoid this problem you can use native method Bitmap.recycle() before null-ing Bitmap object (or setting another value). Example:

為了避免這個問題,您可以在空化位圖對象(或設置另一個值)之前使用本機方法Bitmap.recycle()。例子:

public final void setMyBitmap(Bitmap bitmap) {
  if (this.myBitmap != null) {
    this.myBitmap.recycle();
  }
  this.myBitmap = bitmap;
}

And next you can change myBitmap w/o calling System.gc() like:

然后可以更改myBitmap w/o調用System.gc(),比如:

setMyBitmap(null);    
setMyBitmap(anotherBitmap);

#4


7  

I've ran into this exact problem. The heap is pretty small so these images can get out of control rather quickly in regards to memory. One way is to give the garbage collector a hint to collect memory on a bitmap by calling its recycle method.

我遇到了這個問題。堆非常小,因此這些映像在內存方面可以很快失控。一種方法是給垃圾收集器一個提示,通過調用其回收方法來收集位圖上的內存。

Also, the onDestroy method is not guaranteed to get called. You may want to move this logic/clean up into the onPause activity. Check out the Activity Lifecycle diagram/table on this page for more info.

另外,onDestroy方法不能保證被調用。您可能希望將此邏輯移到onPause活動中。查看此頁面上的活動生命周期圖/表以獲得更多信息。

#5


7  

This explanation might help: http://code.google.com/p/android/issues/detail?id=8488#c80

這個解釋可能有幫助:http://code.google.com/p/android/issues/detail?id=8488#c80

"Fast Tips:

“快速提示:

1) NEVER call System.gc() yourself. This has been propagated as a fix here, and it doesn't work. Do not do it. If you noticed in my explanation, before getting an OutOfMemoryError, the JVM already runs a garbage collection so there is no reason to do one again (its slowing your program down). Doing one at the end of your activity is just covering up the problem. It may causes the bitmap to be put on the finalizer queue faster, but there is no reason you couldn't have simply called recycle on each bitmap instead.

1)永遠不要親自調用System.gc()。在這里,這已經作為一個補丁進行了傳播,但是它不起作用。並沒有這樣做。如果您在我的解釋中注意到,在獲得OutOfMemoryError之前,JVM已經運行了一個垃圾收集,所以沒有理由再做一次(這會減慢程序的運行速度)。在你的活動結束時做一個只是掩蓋問題。它可能會使位圖更快地放到終結器隊列中,但是沒有理由不簡單地在每個位圖上調用recycle。

2) Always call recycle() on bitmaps you don't need anymore. At the very least, in the onDestroy of your activity go through and recycle all the bitmaps you were using. Also, if you want the bitmap instances to be collected from the dalvik heap faster, it doesn't hurt to clear any references to the bitmap.

2)始終在不需要的位圖上調用recycle()。至少,在onDestroy您的活動中,循環使用您正在使用的所有位圖。另外,如果您想要更快地從dalvik堆中收集位圖實例,那么清除任何對位圖的引用也沒有壞處。

3) Calling recycle() and then System.gc() still might not remove the bitmap from the Dalvik heap. DO NOT BE CONCERNED about this. recycle() did its job and freed the native memory, it will just take some time to go through the steps I outlined earlier to actually remove the bitmap from the Dalvik heap. This is NOT a big deal because the large chunk of native memory is already free!

3)調用recycle()然后System.gc()仍然可能不會從Dalvik堆中刪除位圖。不要擔心這個。recycle()完成了它的工作並釋放了本機內存,它只需要一些時間來完成我前面介紹的步驟,以便從Dalvik堆中實際刪除位圖。這並不是什么大問題,因為本機內存的大部分已經是免費的!

4) Always assume there is a bug in the framework last. Dalvik is doing exactly what its supposed to do. It may not be what you expect or what you want, but its how it works. "

4)始終假設框架中最后有一個bug。Dalvik正在做它應該做的事情。它可能不是你期望的或你想要的,但它是如何運作的。”

#6


5  

I had the exact same problem. After a few testing I found that this error is appearing for large image scaling. I reduced the image scaling and the problem disappeared.

我也有同樣的問題。經過幾次測試,我發現這個錯誤出現在大的圖像縮放上。我減少了圖像縮放,問題消失了。

P.S. At first I tried to reduce the image size without scaling the image down. That did not stop the error.

一開始我試圖減小圖像的大小而不縮小圖像。這並沒有阻止錯誤的發生。

#7


5  

Following points really helped me a lot. There might be other points too, but these are very crucial:

以下幾點確實對我有很大幫助。也許還有其他幾點,但這些是非常重要的:

  1. Use application context(instead of activity.this) where ever possible.
  2. 盡可能使用應用程序上下文(而不是activity.this)。
  3. Stop and release your threads in onPause() method of activity
  4. 在onPause()活動方法中停止並釋放線程
  5. Release your views / callbacks in onDestroy() method of activity
  6. 在onDestroy()活動方法中釋放視圖/回調

#8


4  

I suggest a convenient way to solve this problem. Just assign the attribute "android:configChanges" value as followed in the Mainfest.xml for your errored activity. like this:

我建議一個方便的方法來解決這個問題。只需按主節中所示分配屬性“android:configChanges”值。錯誤活動的xml。是這樣的:

<activity android:name=".main.MainActivity"
              android:label="mainActivity"
              android:configChanges="orientation|keyboardHidden|navigation">
</activity>

the first solution I gave out had really reduced the frequency of OOM error to a low level. But, it did not solve the problem totally. And then I will give out the 2nd solution:

我給出的第一個解決方案實際上把OOM錯誤的頻率降低到了一個較低的水平。但是,它並沒有完全解決問題。然后給出第二種解

As the OOM detailed, I have used too much runtime memory. So, I reduce the picture size in ~/res/drawable of my project. Such as an overqualified picture which has a resolution of 128X128, could be resized to 64x64 which would also be suitable for my application. And after I did so with a pile of pictures, the OOM error doesn't occur again.

隨着OOM的詳細介紹,我使用了太多的運行時內存。因此,我減少了我的項目中~/res/drawable的圖片大小。例如,一張分辨率為128X128的圖片,可以調整為64x64,這也適用於我的應用。在我用一堆圖片做這些之后,OOM錯誤就不會再發生了。

#9


3  

I too am frustrated by the outofmemory bug. And yes, I too found that this error pops up a lot when scaling images. At first I tried creating image sizes for all densities, but I found this substantially increased the size of my app. So I'm now just using one image for all densities and scaling my images.

我也對outofmemory錯誤感到沮喪。是的,我也發現當縮放圖像時這個錯誤會出現很多。起初,我嘗試為所有密度創建圖像大小,但我發現這大大增加了我的應用程序的大小。

My application would throw an outofmemory error whenever the user went from one activity to another. Setting my drawables to null and calling System.gc() didn't work, neither did recycling my bitmapDrawables with getBitMap().recycle(). Android would continue to throw the outofmemory error with the first approach, and it would throw a canvas error message whenever it tried using a recycled bitmap with the second approach.

每當用戶從一個活動切換到另一個活動時,我的應用程序都會拋出一個outofmemory錯誤。將drawables設置為null並調用System.gc()不起作用,使用getBitMap(). recycling()回收位圖也不起作用。Android將繼續使用第一種方法拋出outofmemory錯誤,每當它嘗試使用第二種方法使用回收的位圖時,都會拋出一個畫布錯誤消息。

I took an even third approach. I set all views to null and the background to black. I do this cleanup in my onStop() method. This is the method that gets called as soon as the activity is no longer visible. If you do it in the onPause() method, users will see a black background. Not ideal. As for doing it in the onDestroy() method, there is no guarantee that it will get called.

我采用了第三種方法。我將所有視圖設置為null,背景設置為黑色。我在onStop()方法中進行清理。這是在活動不再可見時調用的方法。如果在onPause()方法中執行,用戶將看到黑色的背景。不是最理想的。至於在onDestroy()方法中執行它,沒有保證它會被調用。

To prevent a black screen from occurring if the user presses the back button on the device, I reload the activity in the onRestart() method by calling the startActivity(getIntent()) and then finish() methods.

為了防止用戶按下設備上的后退按鈕時出現黑屏,我通過調用startActivity(getIntent())和finish()方法重新加載onRestart()方法中的活動。

Note: it's not really necessary to change the background to black.

注意:沒有必要將背景改為黑色。

#10


1  

The BitmapFactory.decode* methods, discussed in the Load Large Bitmaps Efficiently lesson, should not be executed on the main UI thread if the source data is read from disk or a network location (or really any source other than memory). The time this data takes to load is unpredictable and depends on a variety of factors (speed of reading from disk or network, size of image, power of CPU, etc.). If one of these tasks blocks the UI thread, the system flags your application as non-responsive and the user has the option of closing it (see Designing for Responsiveness for more information).

如果源數據是從磁盤或網絡位置(或實際上除內存之外的任何源)讀取的,則不應該在主UI線程上執行Load Large bitmap .decode*方法。數據加載的時間是不可預測的,取決於各種因素(從磁盤或網絡讀取速度、圖像大小、CPU功率等)。如果其中一個任務阻塞了UI線程,系統將您的應用程序標記為無響應,用戶可以選擇關閉它(更多信息請參見設計的響應性)。

#11


0  

Well I've tried everything I found on the internet and none of them worked. Calling System.gc() only drags down the speed of app. Recycling bitmaps in onDestroy didn't work for me too.

我試過了我在網上找到的所有東西,但都沒用。調用System.gc()只會降低應用程序的速度。

The only thing that works now is to have a static list of all the bitmap so that the bitmaps survive after a restart. And just use the saved bitmaps instead of creating new ones every time the activity if restarted.

現在唯一有效的方法是擁有所有位圖的靜態列表,以便位圖在重新啟動后繼續存在。只要使用保存的位圖,而不是每次重新啟動時創建新的位圖。

In my case the code looks like this:

在我的例子中,代碼是這樣的:

private static BitmapDrawable currentBGDrawable;

if (new File(uriString).exists()) {
    if (!uriString.equals(currentBGUri)) {
        freeBackground();
        bg = BitmapFactory.decodeFile(uriString);

        currentBGUri = uriString;
        bgDrawable = new BitmapDrawable(bg);
        currentBGDrawable = bgDrawable;
    } else {
        bgDrawable = currentBGDrawable;
    }
}

#12


0  

I had the same problem just with switching the background images with reasonable sizes. I got better results with setting the ImageView to null before putting in a new picture.

我也有同樣的問題,只是把背景圖片換成了合理的尺寸。我將ImageView設為空,然后再放入新圖片,得到了更好的結果。

ImageView ivBg = (ImageView) findViewById(R.id.main_backgroundImage);
ivBg.setImageDrawable(null);
ivBg.setImageDrawable(getResources().getDrawable(R.drawable.new_picture));

#13


0  

FWIW, here's a lightweight bitmap-cache I coded and have used for a few months. It's not all-the-bells-and-whistles, so read the code before you use it.

FWIW,這是我編碼的一個輕量級位圖緩存,已經使用了幾個月了。它並不是十全十美的,所以在使用它之前先閱讀一下代碼。

/**
 * Lightweight cache for Bitmap objects. 
 * 
 * There is no thread-safety built into this class. 
 * 
 * Note: you may wish to create bitmaps using the application-context, rather than the activity-context. 
 * I believe the activity-context has a reference to the Activity object. 
 * So for as long as the bitmap exists, it will have an indirect link to the activity, 
 * and prevent the garbaage collector from disposing the activity object, leading to memory leaks. 
 */
public class BitmapCache { 

    private Hashtable<String,ArrayList<Bitmap>> hashtable = new Hashtable<String, ArrayList<Bitmap>>();  

    private StringBuilder sb = new StringBuilder(); 

    public BitmapCache() { 
    } 

    /**
     * A Bitmap with the given width and height will be returned. 
     * It is removed from the cache. 
     * 
     * An attempt is made to return the correct config, but for unusual configs (as at 30may13) this might not happen.  
     * 
     * Note that thread-safety is the caller's responsibility. 
     */
    public Bitmap get(int width, int height, Bitmap.Config config) { 
        String key = getKey(width, height, config); 
        ArrayList<Bitmap> list = getList(key); 
        int listSize = list.size();
        if (listSize>0) { 
            return list.remove(listSize-1); 
        } else { 
            try { 
                return Bitmap.createBitmap(width, height, config);
            } catch (RuntimeException e) { 
                // TODO: Test appendHockeyApp() works. 
                App.appendHockeyApp("BitmapCache has "+hashtable.size()+":"+listSize+" request "+width+"x"+height); 
                throw e ; 
            }
        }
    }

    /**
     * Puts a Bitmap object into the cache. 
     * 
     * Note that thread-safety is the caller's responsibility. 
     */
    public void put(Bitmap bitmap) { 
        if (bitmap==null) return ; 
        String key = getKey(bitmap); 
        ArrayList<Bitmap> list = getList(key); 
        list.add(bitmap); 
    }

    private ArrayList<Bitmap> getList(String key) {
        ArrayList<Bitmap> list = hashtable.get(key);
        if (list==null) { 
            list = new ArrayList<Bitmap>(); 
            hashtable.put(key, list); 
        }
        return list;
    } 

    private String getKey(Bitmap bitmap) {
        int width = bitmap.getWidth();
        int height = bitmap.getHeight();
        Config config = bitmap.getConfig();
        return getKey(width, height, config);
    }

    private String getKey(int width, int height, Config config) {
        sb.setLength(0); 
        sb.append(width); 
        sb.append("x"); 
        sb.append(height); 
        sb.append(" "); 
        switch (config) {
        case ALPHA_8:
            sb.append("ALPHA_8"); 
            break;
        case ARGB_4444:
            sb.append("ARGB_4444"); 
            break;
        case ARGB_8888:
            sb.append("ARGB_8888"); 
            break;
        case RGB_565:
            sb.append("RGB_565"); 
            break;
        default:
            sb.append("unknown"); 
            break; 
        }
        return sb.toString();
    }

}

注意!

本站翻译的文章,版权归属于本站,未经许可禁止转摘,转摘请注明本文地址:https://www.itdaan.com/blog/2009/12/22/7212c9fc191bc565bc32e900a907ee9b.html



 
粤ICP备14056181号  © 2014-2021 ITdaan.com