Android:图库中的内存不足异常 [英] Android: out of memory exception in Gallery

查看:22
本文介绍了Android:图库中的内存不足异常的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我的应用程序显示了一个包含 9 个类别的列表,每个类别显示一个基于图库的封面流(由 Neil Davies 慷慨提供 此处) 与所选类别的图像.

图片取自网络,每张大小从300K到500K不等,存储在Drawables的arrayList中.此数据使用 BaseAdapter(以下代码)绑定到 Coverflow.
每次我退出coverflow并返回类别列表时,我都会清除arrayList(再次,下面的代码).

在场景 1 中,我的 arrayList 包含 5 个 Drawable.在这种情况下,我可以自由浏览所有类别并显示它们的图像.在我的测试中,我在所有类别中循环了 5 次,这似乎足以确定没有问题.

在场景 2 中,我的 arrayList 包含 10 个可绘制对象.在这种情况下,我在浏览第 5 或第 6 类中的图像时遇到 OutOfMemoryError 异常:

<前>07-13 08:38:21.266: 错误/dalvikvm-heap(2133): 819840 字节的外部分配对于这个过程来说太大了.07-13 08:38:21.266:错误/(2133):VM 不会让我们分配 819840 字节07-13 08:38:21.277:调试/skia(2133):---解码器->解码返回假07-13 08:38:21.287: WARN/dalvikvm(2133): threadid=25: 线程退出时出现未捕获的异常 (group=0x4001b188)07-13 08:38:21.296:错误/AndroidRuntime(2133):未捕获的处理程序:线程 Thread-64 由于未捕获的异常而退出07-13 08:38:21.308: 错误/AndroidRuntime(2133): java.lang.OutOfMemoryError: 位图大小超出 VM 预算07-13 08:38:21.308:错误/AndroidRuntime(2133):在 android.graphics.BitmapFactory.nativeDecodeStream(本机方法)07-13 08:38:21.308: 错误/AndroidRuntime(2133): 在 android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:459)07-13 08:38:21.308: 错误/AndroidRuntime(2133): 在 android.graphics.BitmapFactory.decodeResourceStream(BitmapFactory.java:323)07-13 08:38:21.308: 错误/AndroidRuntime(2133): 在 android.graphics.drawable.Drawable.createFromResourceStream(Drawable.java:697)07-13 08:38:21.308: 错误/AndroidRuntime(2133): 在 android.graphics.drawable.Drawable.createFromStream(Drawable.java:657)

这对我来说没有意义.如果我正在泄漏内存,我预计会在场景 1 中的某个时刻崩溃,但我已经多次浏览了所有类别并且没有崩溃.我还使用了 Eclipse 的 Memory Analyzer 插件,它没有出现任何潜在的罪魁祸首.

如果系统不能处理 10 张图像,就像在场景 2 中一样,我会在第一类中崩溃,但我只在 5 或 6 类后崩溃.

coverflow 的适配器函数:

public int getCount() {返回 DataManager.getInstance().getImageBufferInstance().getImageArraySize();}公共对象 getItem(int position) {返回 DataManager.getInstance().getImagesBuffer().get(position);}public long getItemId(int position) {返回位置;}public View getView(int position, View convertView, ViewGroup parent) {图像视图我;如果(转换视图 == 空)i = new ImageView(mContext);别的i = (ImageView)convertView;Drawable bufferedImage = (Drawable)getItem(position);Log.v("getView", "位置:" + 位置);i.setImageDrawable(bufferedImage);i.setLayoutParams(new CoverFlow.LayoutParams(Utils.getInstance().getScreenWidth()/2,Utils.getInstance().getScreenHeight()/2));i.setScaleType(ImageView.ScaleType.CENTER_INSIDE);尝试{//确保我们设置了抗锯齿,否则会出现锯齿BitmapDrawable drawable = (BitmapDrawable) i.getDrawable();drawable.setAntiAlias(true);}捕获(例外 e){Log.v("getView", "异常:" + e.toString());}返回我;}

在进入类别时填充数据源:

for (int i = 0; i 

退出分类时清除数据源(在finish()中):

for (int i = 0; i 

好的,我在 Coverflow 上应用了 Mathias 的 LogHeap 函数,这里有一些输出.在加载第一个画廊之前:

<前>调试/应用程序(5221):调试.==================================调试/应用程序(5221):debug.heap native:在 [com.example.Coverflow] 中分配了 6.28MB 的 6.20MB(0.07MB 空闲)调试/应用程序(5221):debug.memory:已分配:24.00MB 的 4.00MB(0.00MB 空闲)DEBUG/dalvikvm(5221):GC 在 84 毫秒内释放了 4558 个对象/638152 字节DEBUG/dalvikvm(5221):GC 在 67 毫秒内释放了 17 个对象/808 字节

进入第一个画廊后:

<前>调试/应用程序(5221):调试.==================================调试/应用程序(5221):debug.heap native:在 [com.example.Coverflow] 中分配了 16.89MB 的 14.90MB(0.07MB 空闲)调试/应用程序(5221):调试内存:分配:24.00MB 的 4.00MB(1.00MB 空闲)DEBUG/dalvikvm(5221):GC 在 68 毫秒内释放了 357 个对象/50080 字节DEBUG/dalvikvm(5221):GC 在 67 毫秒内释放了 353 个对象/27312 字节

存在第一个画廊后:

<前>调试/应用程序(5221):调试.==================================调试/应用程序(5221):debug.heap native:在 [com.example.Coverflow] 中分配了 16.89MB 的 14.83MB(0.11MB 空闲)调试/应用程序(5221):debug.memory:已分配:24.00MB 的 4.00MB(1.00MB 空闲)DEBUG/dalvikvm(5221):GC 在 77 毫秒内释放了 330 个对象/17920 字节DEBUG/dalvikvm(5221):GC 在 67 毫秒内释放了 13 个对象/760 字节

进入第五个画廊后:

<前>调试/应用程序(5221):调试.==================================调试/应用程序(5221):debug.heap native:在 [com.example.Coverflow] 中分配了 23.32MB 的 16.80MB(0.08MB 空闲)调试/应用程序(5221):debug.memory:已分配:24.00MB 的 4.00MB(1.00MB 空闲)DEBUG/dalvikvm(5221):GC 在 73 毫秒内释放了 842 个对象/99256 字节DEBUG/dalvikvm(5221):GC 在 69 毫秒内释放了 306 个对象/24896 字节

退出第五个画廊后:

<前>调试/应用程序(5221):调试.==================================调试/应用程序(5221):debug.heap native:在 [com.example.Coverlow] 中分配了 16.74MB 的 23.32MB(0.11MB 空闲)调试/应用程序(5221):调试内存:分配:24.00MB 的 4.00MB(1.00MB 空闲)DEBUG/dalvikvm(5221):GC 在 68 毫秒内释放了 331 个对象/18184 字节DEBUG/dalvikvm(5221):GC 在 68 毫秒内释放了 60 个对象/3128 字节

似乎进入画廊时分配的内存越来越多,但退出后释放的内存很少.我没有正确清除我的可绘制对象吗?对于我的可绘制数组列表中的每个元素,我调用 setCallBack(null) 并将元素设置为 null.这还不够吗?
渴望获得任何见解.
谢谢

解决方案

图片来自网络,每个范围从 300K 到 500K大小,并存储在一个数组列表中可绘制对象.

您从网络加载的图像的 kb 文件大小没有直接关系.由于它们已转换为位图,因此您需要为常规 ARGB 图像计算每个图像的宽度 * 高度 * 4 字节.(宽度和高度以 px 为单位).

位图消耗本机堆,这通常不会显示在 hprof 中.hprof 应该只显示对象的数量,即剩余的 BitmapDrawables 或 Bitmaps.

我在我的应用程序中使用此代码来输出应用程序和本机堆使用的当前已用内存:

public static void logHeap(Class clazz) {双分配 = new Double(Debug.getNativeHeapAllocatedSize())/new Double((1048576));双可用 = new Double(Debug.getNativeHeapSize())/1048576.0);Double free = new Double(Debug.getNativeHeapFreeSize())/1048576.0);DecimalFormat df = new DecimalFormat();df.setMaximumFractionDigits(2);df.setMinimumFractionDigits(2);Log.d(APP, "调试.================================);Log.d(APP, "debug.heap native:located" + df.format(allocated) + "MB of " + df.format(available) + "MB (" + df.format(free) + "MB free)在 [" + clazz.getName().replaceAll("com.myapp.android.","") + "]");Log.d(APP, "debug.memory:located:" + df.format(new Double(Runtime.getRuntime().totalMemory()/1048576)) + "MB of " + df.format(new Double(Runtime.getRuntime().maxMemory()/1048576))+ "MB (" + df.format(new Double(Runtime.getRuntime().freeMemory()/1048576)) +"MB free)");System.gc();System.gc();//不需要添加以下几行,它只是我的应用程序中特定于应用程序的处理if (allocated>=(new Double(Runtime.getRuntime().maxMemory())/new Double((1048576))-MEMORY_BUFFER_LIMIT_FOR_RESTART)) {android.os.Process.killProcess(android.os.Process.myPid());}}

我在开发过程中开始或完成活动时调用.

logHeap(this.getClass());

这里有一些信息性链接 - 通常这里有很多关于这个主题的主题.

这里还有 Romain Guy(Android 框架工程师)关于软引用、弱引用、简单缓存、图像处理的有用幻灯片:http://docs.huihoo.com/google/io/2009/Th_0230_TurboChargeYourUI-Howtomake yourAndroidUIfastandefficient.pdf

My app shows a list of 9 categories and each category displays a Gallery-based coverflow (graciously offered by Neil Davies here) with images of the selected category.

The images are fetched from the Web, each ranging from 300K to 500K in size, and stored in an arrayList of Drawables. This data is bound to the coverflow using a BaseAdapter (code below).
Every time I exit the coverflow and go back to the list of categories, I clear the arrayList (again, code below).

In scenario 1, my arrayList contains 5 Drawables. In this scenario, I can freely browse all the categories and show their images. During my test I cycled through all the categories for 5 times, which seems enough to determine that there is no problem.

In scenario 2, my arrayList contains 10 drawables. In this scenario, I get an OutOfMemoryError exception while going through images inside the 5th or 6th categeory:

07-13 08:38:21.266: ERROR/dalvikvm-heap(2133): 819840-byte external allocation too large for this process.
07-13 08:38:21.266: ERROR/(2133): VM won't let us allocate 819840 bytes
07-13 08:38:21.277: DEBUG/skia(2133): --- decoder->decode returned false
07-13 08:38:21.287: WARN/dalvikvm(2133): threadid=25: thread exiting with uncaught exception (group=0x4001b188)
07-13 08:38:21.296: ERROR/AndroidRuntime(2133): Uncaught handler: thread Thread-64 exiting due to uncaught exception
07-13 08:38:21.308: ERROR/AndroidRuntime(2133): java.lang.OutOfMemoryError: bitmap size exceeds VM budget
07-13 08:38:21.308: ERROR/AndroidRuntime(2133):     at android.graphics.BitmapFactory.nativeDecodeStream(Native Method)
07-13 08:38:21.308: ERROR/AndroidRuntime(2133):     at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:459)
07-13 08:38:21.308: ERROR/AndroidRuntime(2133):     at android.graphics.BitmapFactory.decodeResourceStream(BitmapFactory.java:323)
07-13 08:38:21.308: ERROR/AndroidRuntime(2133):     at android.graphics.drawable.Drawable.createFromResourceStream(Drawable.java:697)
07-13 08:38:21.308: ERROR/AndroidRuntime(2133):     at android.graphics.drawable.Drawable.createFromStream(Drawable.java:657)

This doesn't make sense to me. If I am leaking memory I would have expected to crash at some point in scenario 1, but I went through all the categories a substantial number of times and didn't crash. I also used the Memory Analyzer plugin for Eclipse which didn't present any potential culprits.

If the system couldn't handle 10 images, like in scenarion 2, I would have expected to crash in the first category, but I crash only after 5 or 6 categories.

The coverflow's adapter functions:

public int getCount() {
     return DataManager.getInstance().getImageBufferInstance().getImageArraySize(); 
}

public Object getItem(int position) {    
     return DataManager.getInstance().getImagesBuffer().get(position);
}

public long getItemId(int position) {
     return position;
}

public View getView(int position, View convertView, ViewGroup parent) {      
         ImageView i;
         if (convertView == null)
             i = new ImageView(mContext);
         else
             i = (ImageView)convertView;
         Drawable bufferedImage = (Drawable)getItem(position);
         Log.v("getView", "position: " + position);
         i.setImageDrawable(bufferedImage);

         i.setLayoutParams(new CoverFlow.LayoutParams(Utils.getInstance().getScreenWidth() / 2,
                 Utils.getInstance().getScreenHeight() / 2));
         i.setScaleType(ImageView.ScaleType.CENTER_INSIDE); 

         try{
         //Make sure we set anti-aliasing otherwise we get jaggies
         BitmapDrawable drawable = (BitmapDrawable) i.getDrawable();
         drawable.setAntiAlias(true);
         }
         catch (Exception e)
         {
             Log.v("getView", "Exception: " + e.toString());
         }
         return i;      
     }

populating the data source upon entry to the category:

for (int i = 0; i < ImageBuffer.getInstance().getImageArraySize(); i++)  
{  
  String imageUrl = ImageBuffer.getInstance().getImageUrl(i);  
  Log.v("Initial", imageUrl);  
  Drawable fullImage = AsyncImageLoader.getInstance().loadImageByUrl(imageUrl);  
  ImageBuffer.getInstance().getImages().add(i, fullImage);  

}

clearing the data source when exiting the category (in finish()):

for (int i = 0; i < ImageBuffer.getInstance().getImageArraySize(); i++)  
{  
  if (ImageBuffer.getInstance().images.get(i) != null)  
            {  
                ImageBuffer.getInstance().images.get(i).setCallback(null);  
                ImageBuffer.getInstance().images.set(i, null);  
            }    

}

EDIT:

OK, I applied Mathias' LogHeap function on my coverflow and here are some outputs. Prior to loading the first gallery:

DEBUG/Application(5221): debug. =================================
DEBUG/Application(5221): debug.heap native: allocated 6.20MB of 6.28MB (0.07MB free) in [com.example.Coverflow]
DEBUG/Application(5221): debug.memory: allocated: 4.00MB of 24.00MB (0.00MB free)
DEBUG/dalvikvm(5221): GC freed 4558 objects / 638152 bytes in 84ms
DEBUG/dalvikvm(5221): GC freed 17 objects / 808 bytes in 67ms

After entering the first gallery:

DEBUG/Application(5221): debug. =================================
DEBUG/Application(5221): debug.heap native: allocated 14.90MB of 16.89MB (0.07MB free) in [com.example.Coverflow]
DEBUG/Application(5221): debug.memory: allocated: 4.00MB of 24.00MB (1.00MB free)
DEBUG/dalvikvm(5221): GC freed 357 objects / 50080 bytes in 68ms
DEBUG/dalvikvm(5221): GC freed 353 objects / 27312 bytes in 67ms

After existing the first gallery:

DEBUG/Application(5221): debug. =================================
DEBUG/Application(5221): debug.heap native: allocated 14.83MB of 16.89MB (0.11MB free) in [com.example.Coverflow]
DEBUG/Application(5221): debug.memory: allocated: 4.00MB of 24.00MB (1.00MB free)
DEBUG/dalvikvm(5221): GC freed 330 objects / 17920 bytes in 77ms
DEBUG/dalvikvm(5221): GC freed 13 objects / 760 bytes in 67ms

After entering the fifth gallery:

DEBUG/Application(5221): debug. =================================
DEBUG/Application(5221): debug.heap native: allocated 16.80MB of 23.32MB (0.08MB free) in [com.example.Coverflow]
DEBUG/Application(5221): debug.memory: allocated: 4.00MB of 24.00MB (1.00MB free)
DEBUG/dalvikvm(5221): GC freed 842 objects / 99256 bytes in 73ms
DEBUG/dalvikvm(5221): GC freed 306 objects / 24896 bytes in 69ms

After exiting the fifth gallery:

DEBUG/Application(5221): debug. =================================
DEBUG/Application(5221): debug.heap native: allocated 16.74MB of 23.32MB (0.11MB free) in [com.example.Coverlow]
DEBUG/Application(5221): debug.memory: allocated: 4.00MB of 24.00MB (1.00MB free)
DEBUG/dalvikvm(5221): GC freed 331 objects / 18184 bytes in 68ms
DEBUG/dalvikvm(5221): GC freed 60 objects / 3128 bytes in 68ms

It seems that more and more memory is allocated when entering a gallery, but very little is released after exiting. Am I not clearing my drawables properly? For each element in my arrayList of drawables I call setCallBack(null) and set the element to null. Is that not enough?
Desperate for any insight.
Thanks

解决方案

The images are fetched from the Web, each ranging from 300K to 500K in size, and stored in an arrayList of Drawables.

The kb file size of the image you're loading from the web isn't directly relevant. Since they're converted into bitmaps you need to calculate width * height * 4 bytes per image for regular ARGB images. (width and height in px).

The bitmaps consume native heap, which usually doesn't show in a hprof. The hprof should only show you the number of objects, i.e. BitmapDrawables or Bitmaps that are left.

I use this code in my app to output the current used memory used by the app and native heap:

public static void logHeap(Class clazz) {
    Double allocated = new Double(Debug.getNativeHeapAllocatedSize())/new Double((1048576));
    Double available = new Double(Debug.getNativeHeapSize())/1048576.0);
    Double free = new Double(Debug.getNativeHeapFreeSize())/1048576.0);
    DecimalFormat df = new DecimalFormat();
    df.setMaximumFractionDigits(2);
    df.setMinimumFractionDigits(2);

    Log.d(APP, "debug. =================================");
    Log.d(APP, "debug.heap native: allocated " + df.format(allocated) + "MB of " + df.format(available) + "MB (" + df.format(free) + "MB free) in [" + clazz.getName().replaceAll("com.myapp.android.","") + "]");
    Log.d(APP, "debug.memory: allocated: " + df.format(new Double(Runtime.getRuntime().totalMemory()/1048576)) + "MB of " + df.format(new Double(Runtime.getRuntime().maxMemory()/1048576))+ "MB (" + df.format(new Double(Runtime.getRuntime().freeMemory()/1048576)) +"MB free)");
    System.gc();
    System.gc();

    // don't need to add the following lines, it's just an app specific handling in my app        
    if (allocated>=(new Double(Runtime.getRuntime().maxMemory())/new Double((1048576))-MEMORY_BUFFER_LIMIT_FOR_RESTART)) {
        android.os.Process.killProcess(android.os.Process.myPid());
    }
}

which I call when starting or finishing an activity during development.

logHeap(this.getClass());

Here are some informative links - generally there are lots of threads about this topic on here.

Here's also a useful slide by Romain Guy (Android Framework engineer) about soft references, weak references, simple caches, image handling: http://docs.huihoo.com/google/io/2009/Th_0230_TurboChargeYourUI-HowtomakeyourAndroidUIfastandefficient.pdf

这篇关于Android:图库中的内存不足异常的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆