0

由于 Android 在 Android 3.0 中引入了新的位图内存管理(位图数据现在在 Dalvik Heap中分配),我得到了这个 OutOfMemoryError 错误,它使我的应用程序崩溃。

基本上我有一个 ListView 大约。1000 个 ImageViews 包含我的资产文件夹中的位图(分辨率:300 x 200 像素)。

我在运行 Jelly Bean 的 HTC Desire 上检测到了这个问题。如果我滚动到快,我的应用程序的 Pro 版本会崩溃。(免费版列表中只有 150 项)我尝试在模拟器中重现错误,发现 Android 3.0 及更高版本中 DDMS 的垃圾收集消息不像我的旧 2.3.6 智能手机上的 GC_EXTERNAL_ALLOC (它运行应用程序没有任何问题)。因此,大量数据的内存大小是不够的。我不认为 ListView 真的将所有的 Bitmaps 都保存在内存中,但是 150 个 Items 和 1000 个 Items 之间肯定是有区别的。

我尝试在我的列表适配器中对 GetView() 执行 AsyncTask,但随后 OutOfMemory 发生在 AsyncTask 线程中。所以我对此无能为力......

有人有解决这个问题的想法吗?

谷歌的开发指南对我没有帮助

编辑:

好的,我对我的APP进行了MAT分析,似乎不是导致问题的ListView,而是我的ViewFlipper。

我的 MainMenu、我的 SearchScreen、我的 SearchResults(ListView 和 GridView - 可切换)和我的DetailedView 都是我的 ViewFlipper 的单个“页面”。

MainMenu、SearchScreen 和DetailedView 占用了我大约20 MB 的内存。我从代码中提取了 List- 和 Gridview,看起来它们占用了 8MB 到 13MB 的空间(取决于执行滚动的速度)。

Android SDK 模拟器(模拟 4.2)的 VM 堆大小为 32MB。所以最初它可以工作,如果我滚动到快速,我会遇到内存不足,并显示错误消息:

skia decoder->decode returned false

其次是各种 Bitmap-decode-OutOfMemory 问题。

所以我想我必须卸载/缓存我的 ViewFlipper 的页面 - 对吧?怎么做???

这是 ListView/GridView 实现的提取源代码。

public class GridTestActivity extends Activity {

    GridView gv;
    ListView lv;
    int lvScreenWidth;
    int lvScreenHeight;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        this.requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.activity_grid_test);

        Display display = getWindowManager().getDefaultDisplay(); 
        this.lvScreenWidth  =  display.getWidth();
        this.lvScreenHeight  =  display.getHeight();

        MyAdapter adp = new MyAdapter(this);

        gv = (GridView)findViewById(R.id.gridView1);
        gv.setAdapter(adp);
        gv.setFastScrollEnabled(true);

        lv = (ListView)findViewById(R.id.listView1);
        lv.setAdapter(adp);
        lv.setFastScrollEnabled(true);


        ActivityManager am = (ActivityManager)this.getSystemService(ACTIVITY_SERVICE);
        Toast.makeText(this, "DALVIK HEAP SIZE: " + am.getMemoryClass() + "MB", 5).show();


    }

    static class ViewHolder{
         TextView text;
         ImageView icon;
    }


    public class MyAdapter extends BaseAdapter{

        private LayoutInflater mInflater;

        public MyAdapter(Context context) {
            mInflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        }


        @Override
        public int getCount() {
            return 1000;
        }

        @Override
        public Object getItem(int arg0) {
            return null;
        }

        @Override
        public long getItemId(int position) {
            return 0;
        }

        @Override
        public boolean hasStableIds() {
            return true;
        }


        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            ViewHolder holder;

            if (convertView == null) {
                convertView = mInflater.inflate(R.layout.list_item_icon_text, parent, false);
                holder = new ViewHolder();
                holder.text = (TextView) convertView.findViewById(R.id.text);
                holder.icon = (ImageView) convertView.findViewById(R.id.icon);

                convertView.setTag(holder);
            } else {
                holder = (ViewHolder) convertView.getTag();
            }

            holder.text.setText("pos:" + position);

            // best way to load an asset-image???
            try {
                InputStream ims = getAssets().open("assetpic_" + position + ".jpg");
                BitmapDrawable d = (BitmapDrawable) Drawable.createFromStream(ims, null);
                ims.close();
                holder.icon.setImageDrawable(d);


                if (parent.equals(lv)){   
                    int oldWidth = d.getIntrinsicWidth();
                    int oldHeight= d.getIntrinsicHeight();
                    holder.icon.getLayoutParams().height = lvScreenWidth * oldHeight/oldWidth;
                }


            }
            catch(IOException ex) {
                Log.i("MyAdapter" , "Could not load 'assetpic_" + position + ".jpg' from Assets-folder");
            }

            return convertView;
        }

    }

}

我不确定,但也许我错过了我的代码的一些细节?

完整源代码 (25MB)

4

3 回答 3

1

您应该在不使用时回收位图。

bitmap.recycle();

您可以使用通用图像加载器。https://github.com/nostra13/Android-Universal-Image-Loader

您可以使用延迟加载图像。https://github.com/thest1/LazyList

两者都使用缓存。

http://www.youtube.com/watch?v=_CruQY55HOk。演讲内容是关于内存管理、回收位图以及如何使用 MAT Analyzer 检测内存泄漏。

http://developer.android.com/training/improving-layouts/smooth-scrolling.html。使用 View Holder 进行平滑滚动。

http://www.youtube.com/watch?v=wDBM6wVEO70。谈话是关于视图持有者和列表视图的性能。

正如您所提到的,您已经有效地完成了位图的显示。

于 2013-03-22T14:15:54.553 回答
0

这可能不是一个适当的解决方案,但这就是我现在必须提供的全部。添加你的清单(在应用程序标签中)

android:largeHeap="true"

这可能会在一定程度上解决您的内存不足问题。如果您显示您的适配器和列表视图对,我可能会告诉一些更合法的解决方案。

于 2013-03-22T14:17:16.163 回答
0

问题是 AsyncTask 仅适用于少数图像,对于数百(或数千)图像它们处理得不是很好。

缓存肯定是绝对必要的,在您发布的 Google 链接上,有关于 RAM 和磁盘缓存的解释。

对于像这样的大型数据集,您实际上有两个选择:

  1. 使用库(例如 Universal Image Loader 或 Lazy List),它们并不完美,但它们在大多数情况下都运行良好。

  2. 创建自己的实现。我之前已经实现了这些,我希望它们不是公司 IP 的一部分,因为我想开源它。但是您必须为从一个缓存或另一个缓存或从在线加载的线程实现几组线程(使用Executors)并返回结果并记住如果该 ImageView 正在被回收,则取消返回。这是一个漫长而复杂的过程,但如果你做得对,你会得到一些非常流畅的滚动。

于 2013-03-22T14:42:02.750 回答