在ListView中使用ArrayAdapter时发生了OutOfMemoryError异常。

3
我使用实现 getView() 方法的 ArrayAdapter 创建了一个 ListView,代码如下:

public View getView(int position, View convertView, ViewGroup parent) {
    View rowView = convertView;
    if(rowView ==null){
    LayoutInflater inflater = (LayoutInflater) context
                    .getSystemService(Context.LAYOUT_INFLATER_SERVICE);         
    rowView = inflater.inflate(R.layout.rowlayout_member, parent, false);
            }
    TextView textView = (TextView) rowView.findViewById(R.id.label);
    TextView textView1 = (TextView) rowView.findViewById(R.id.label1);
    ImageView imageView = (ImageView) rowView.findViewById(R.id.icon);
    textView.setText(values[position]);
    textView1.setText(surname[position]);

    String s = values[position];
    if(foto[position] != null){
    Drawable c = Drawable.createFromPath(basePath+foto[position]);   // line 56 
    if(c!=null)
        imageView.setImageDrawable(c);
    else
        imageView.setImageResource(R.drawable.default);
    }else{
        imageView.setImageResource(R.drawable.default);                
    }
    return rowView;
}

所有东西似乎都正常工作,但是当我多次进入和退出列表(更改活动)时,有时会出现以下错误:

08-06 15:06:14.110: E/dalvikvm-heap(3527): Out of memory on a 2380816-byte allocation.
08-06 15:06:14.110: W/dalvikvm(3527): threadid=1: thread exiting with uncaught exception (group=0x40a3d1f8)
08-06 15:06:14.120: E/AndroidRuntime(3527): FATAL EXCEPTION: main
08-06 15:06:14.120: E/AndroidRuntime(3527): java.lang.OutOfMemoryError
08-06 15:06:14.120: E/AndroidRuntime(3527):     at android.graphics.BitmapFactory.nativeDecodeStream(Native Method)
08-06 15:06:14.120: E/AndroidRuntime(3527):     at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:493)
08-06 15:06:14.120: E/AndroidRuntime(3527):     at android.graphics.BitmapFactory.decodeFile(BitmapFactory.java:299)
08-06 15:06:14.120: E/AndroidRuntime(3527):     at android.graphics.BitmapFactory.decodeFile(BitmapFactory.java:324)
08-06 15:06:14.120: E/AndroidRuntime(3527):     at android.graphics.drawable.Drawable.createFromPath(Drawable.java:880)
08-06 15:06:14.120: E/AndroidRuntime(3527):     at host.framework.component.MySimpleArrayAdapter.getView(MySimpleArrayAdapter.java:56)
08-06 15:06:14.120: E/AndroidRuntime(3527):     at android.widget.AbsListView.obtainView(AbsListView.java:2012)
08-06 15:06:14.120: E/AndroidRuntime(3527):     at android.widget.ListView.makeAndAddView(ListView.java:1772)
08-06 15:06:14.120: E/AndroidRuntime(3527):     at android.widget.ListView.fillUp(ListView.java:705)
08-06 15:06:14.120: E/AndroidRuntime(3527):     at android.widget.ListView.fillGap(ListView.java:645)
08-06 15:06:14.120: E/AndroidRuntime(3527):     at android.widget.AbsListView.trackMotionScroll(AbsListView.java:4546)
08-06 15:06:14.120: E/AndroidRuntime(3527):     at android.widget.AbsListView$FlingRunnable.run(AbsListView.java:3813)
08-06 15:06:14.120: E/AndroidRuntime(3527):     at android.os.Handler.handleCallback(Handler.java:605)
08-06 15:06:14.120: E/AndroidRuntime(3527):     at android.os.Handler.dispatchMessage(Handler.java:92)
08-06 15:06:14.120: E/AndroidRuntime(3527):     at android.os.Looper.loop(Looper.java:137)
08-06 15:06:14.120: E/AndroidRuntime(3527):     at android.app.ActivityThread.main(ActivityThread.java:4424)
08-06 15:06:14.120: E/AndroidRuntime(3527):     at java.lang.reflect.Method.invokeNative(Native Method)
08-06 15:06:14.120: E/AndroidRuntime(3527):     at java.lang.reflect.Method.invoke(Method.java:511)
08-06 15:06:14.120: E/AndroidRuntime(3527):     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:784)
08-06 15:06:14.120: E/AndroidRuntime(3527):     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:551)
08-06 15:06:14.120: E/AndroidRuntime(3527):     at dalvik.system.NativeStart.main(Native Method)
08-06 15:06:14.120: W/ActivityManager(149):   Force finishing activity host.activity/.ACT_CircoliDiSupporto
08-06 15:06:14.640: W/ActivityManager(149): Activity pause timeout for ActivityRecord{413de508 host.activity/.ACT_CircoliDiSupporto}
08-06 15:06:24.140: W/ActivityManager(149): Launch timeout has expired, giving up wake lock!
08-06 15:06:24.650: W/ActivityManager(149): Activity idle timeout for ActivityRecord{41389d58 host.activity/.HostActivity}

第56行(在代码中高亮显示的部分)似乎产生了错误。
Drawable c = Drawable.createFromPath(basePath+foto[position]); 

1
你是否正确释放了对象?不要在getView()内部创建可绘制对象...至少只需创建一次,然后稍后重用该对象。 - AjOnFire
我怎样才能确保我的对象被正确释放?嗯,也许你是对的:每次调用getView时都会创建一个新的Drawable。 - GVillani82
2个回答

3

当前实现存在一些问题:

  1. 代码在主/UI线程上访问和构建位图(Drawable c = Drawable.createFromPath(basePath+foto[position]); // line 56),这会导致列表滚动非常卡顿。
  2. 每次请求视图时都会创建一个新的可绘制对象,在2.x Android系统上,未引用的位图需要等待垃圾回收器(GC)进行清理,加剧了内存问题。
  3. 你从存储中加载图像而没有将其缩放到正确的尺寸,这可能是一个问题,因为我不知道你正在显示的图像的尺寸。

我建议你使用Universal Image Loader库(https://github.com/nostra13/Android-Universal-Image-Loader),它可以解决上述所有问题。虽然我不确定它是否提供本地图像加载功能,或者你是否需要扩展某个类来具备这样的功能。


[编辑]

我不建议你自己编写解决方案,因为它将比你最初想象的时间更长(相信我...)。但是,如果你真的想这样做,你可以查看这个教程:http://codehenge.net/blog/2011/06/android-development-tutorial-asynchronous-lazy-loading-and-caching-of-listview-images/

上述教程解决了#1和#2问题,对于#3,请参阅此处:Strange out of memory issue while loading an image to a Bitmap object


是的,@Kai,你说得对,我的List非常卡顿。假设我不想使用Universal Loader库,我该如何解决你上面列出的问题呢?我想: 1)创建一个单独的线程,如果项目超出列表范围,则应中断该线程? 2)我可以在getView之外创建一次位图吗? 3)我可以适当地缩放所有图像吗?这样可以接受吗? - GVillani82
@Joseph82 更新了答案,请注意即使教程也没有做对,因为它没有取消视图之前的检索图像操作,这会导致在非常快速滚动时出现“闪烁图像”效果。所以我个人不建议这样做 :P - Kai
好的决定。我之前一直在使用自己的AsyncTask来实现这些功能,但是在处理下载任务取消和其他问题时遇到了很多错误,直到我意识到现在有一些惊人的图像加载库,它们有更少的错误和更好的功能。这确实教会了我一个经验,就是要不断地查看伟大的StackOverflow以获取新的库/实现/想法。 - Kai
是的,当然可以成为学习更多关于Android世界的机会 :) - GVillani82
它确实让我了解到不同Android版本中ListView的实现方式是多么奇怪和不同。例如,在许多版本中,ListView会连续三次获取第0个项目,因此您最好要有一些非常好的检查措施。 - Kai
显示剩余2条评论

1
首先,您需要像 LruCache 这样的数据结构来保存正在创建的可绘制对象。
“我如何确保我的对象被正确释放?”
在活动销毁时使用数据结构,请确保该类的对象被设置为 null 或清除其内部对象。(这将告诉 gc 此对象已准备好被清除(在大多数情况下))。
此外,在主线程上运行文件操作并不是最佳方法,因为它会导致列表视图滚动时出现卡顿。请尝试在次要线程中执行此操作。

谢谢 @66CLSjY,你能给我一些在ListView中使用LruCache的例子吗? - GVillani82
请查看https://dev59.com/I2gu5IYBdhLWcg3wDS0G,并研究Bitmap。 - AjOnFire

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接