内存不足错误(viewpager + imageviews)

11

我正在viewpager中展示150+个图片,但当页面数量超过70时,应用程序会崩溃。所有图片都是从网络加载的,我遵循了[链接]:在将图像加载到位图对象时出现奇怪的内存不足问题

每当页面滑动达到4时,我就将其回收利用。

对于70个页面,该应用程序占用了200 MB的内存。

我需要你的帮助,如何处理它

我必须展示所有页面并能够滑动...

我还使用了Runtime.getRuntime().gc();

如果应用程序内存达到50+ MB,是否有释放内存的方法

提前感谢


你是将从网络加载的位图存储在应用程序内存中还是SD卡中? - user936414
感谢您的快速回复,我正在将位图存储在SD卡中:File sdDir = android.os.Environment.getExternalStorageDirectory(); cacheDir = new File(sdDir, context.getString(R.string.cache_dir)); - Sheetal Suryan
每一页都包含一张图片,总页数超过150页。 - Sheetal Suryan
你检查了第70页的图片分辨率是否更高吗?如果你缩放并加载图片,那么使用的内存应该是相对恒定的,因为每4张图片就会回收一次。为什么只有第70页占用了200MB的内存? - user936414
尝试捕获OutOfMemoryError,在catch中仅保留2页而不是4页,并尝试重新加载当前页面。 - user936414
显示剩余3条评论
6个回答

18

完整的解决方案如下所示,重要的行是在destroyItem方法中:

private class ContentPagerAdapter extends PagerAdapter {
    @Override
    public void destroyItem(View collection, int position, Object o) {
        View view = (View)o;
        ((ViewPager) collection).removeView(view);
        view = null;
    }

    @Override
    public void finishUpdate(View arg0) {
        // TODO Auto-generated method stub

    }
    @Override
    public int getCount() {
        return ids.length;
    }

    @Override
    public Object instantiateItem(View context, int position) {
        ImageView imageView = new ImageView(getApplicationContext());
        imageView.findViewById(R.id.item_image);
        imageView.setImageBitmap(BitmapFactory.decodeResource(getResources(), ids[position]));

        ((ViewPager) context).addView(imageView);

        return imageView;
    }

    @Override
    public boolean isViewFromObject(View view, Object object) {
        return view==((ImageView)object);
    }

    @Override
    public void restoreState(Parcelable arg0, ClassLoader arg1) {
        // TODO Auto-generated method stub
    }
    @Override
    public Parcelable saveState() {
        // TODO Auto-generated method stub
        return null;
    }
    @Override
    public void startUpdate(View arg0) {
        // TODO Auto-generated method stub

    }

在使用 View view = (View)o; 时出现了 ClassCastException 异常。 - Aristo Michael

4
我认为这是因为你存在内存泄漏,仔细检查你的变量,不要为任何大型内容使用静态变量,尽可能使用final,将所有成员设置为私有。
我建议你进行提交(或保存当前代码),然后尝试按照我的要求操作,看看是否能解决问题。
如果有代码示例,我可以判断是否存在内存泄漏,也许你可以在GitHub或Google Code等地方发布代码。
重点是:即使你做得很对,但变量仍然保留着对图像的引用,因此垃圾回收器无法清除它们。
我知道说你存在内存泄漏会让人感到难受,但请不要惊慌,这种情况发生在最优秀的人身上,因为它很容易发生。
注意:无论从网络加载多大的数据,如果正确处理,应用程序永远不需要超过1个文件的大小。
谢谢。

如何检查内存泄漏,所需变量public rest是private。 - Sheetal Suryan

1

Sheetal,

看了你的代码后,你能试一下以下操作吗:

@Override
public void destroyItem(View collection, int position, Object o) {
    Log.d("DESTROY", "destroying view at position " + position);
    View view = (View) o;
    ((ViewPager) collection).removeView(view);
    view = null;
}

这将释放 imageView 以进行垃圾回收。


0

Sheetal,

我现在没有我的代码,但它应该类似于以下内容:

public class MyFragment extends Fragment {
  private Resources resources;  // I load my images from different resources installed on the device
  private int id;

  public MyFragment() {
    setRetainInstance(true); // this will prevent the app from crashing on screen rotation
  }

  public MyFragment(Resources resources, int id) {
    this();  // this makes sure that setRetainInstance(true) is always called
    this.resources = resources;
    this.id = id;
  }

  @Override
  public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    ImageView imageView = (ImageView)inflater.infale(R.layout.fragmentview, container, false);
    imageView.setImageBitmap(BitmapFactory.decodeResource(resources, id));

    return imageView;
  }
}

这基本上是我脑海中的想法,所以可能不是很准确,但它非常接近。

如果你需要更多帮助,就大声喊出来,别忘了将这个答案标记为已经帮助了你:)。


1
感恩节示例代码,我需要你的更多帮助,如何在其中使用ViewPager?你能给出完整的ViewPager和Fragment示例代码吗? - Sheetal Suryan
不要在Fragment类中使用参数化构造函数。 - subair_a

0

Sheetal,

根据您的要求,我将上述代码用于以下方面:

在我的FragmentActivity中,我在onCreate()方法中执行以下操作:

pager = (ViewPager)findViewById(R.id.pager);
imageAdapter = new MyPagerAdapter(this, pager, theResources, listOfIds.pageBitMapIds);
imageAdapter.setRecentListener(this);
pager.setAdapter(imageAdapter);

然后在我的PagerAdapter中,我执行以下操作:

@Override
public Object instantiateItem(ViewGroup container, int position) {
    MyFragment myFragment = new MyFragment(resources, resourceIds[position], recentListener, position);
    myFragments[position] = myFragment;
    return super.instantiateItem(container, position);
}

@Override
public Fragment getItem(int position) {
    return myFragments[position];
}

然后我使用我在上面的先前答案中的代码。

这显然不是完整的代码,而是从我的实际代码进行了适应,但它应该让您了解该做什么以及在哪里做。

马克


马克,instantiateItem 返回什么?我在这个语句上遇到了错误:return super.instantiateItem(container, position); // 报错:Cannot directly invoke the abstract method instantiateItem(View, int) for the type PagerAdapter。 - Sheetal Suryan
马克,我已经将我的示例代码上传到 Github 上,请查看一下。它显示为空白页面,[链接] git@github.com:sheetalsuryan/Samples.git - Sheetal Suryan
Sheetal,我一直非常忙,但今天我会看看你的代码,看看我能提出什么。 - Mark
Sheetal,你在strings.xml文件中缺少cache_dir设置。请更新项目。我不想猜测这个值是什么。 - Mark
Sheetal,你能给我在Github项目中写作的权限吗?我的用户名是arcotc。谢谢。 - Mark
谢谢Mark,我已将您添加为协作者,并更新了string.xml文件。 - Sheetal Suryan

0
你是在onCreateView()视图方法中加载图片吗?
这种方式似乎可以满足内存管理需求。我曾经尝试过在FragmentPageAdapter中加载图片,将它们预加载或作为Fragment instaniateItem方法的一部分传递到我的Fragment构造函数中,但这两种方式都给了我你现在面临的问题。最终我将加载每个图片所需的信息传递给了Fragment构造函数,然后在onCreateView()方法中使用这些信息。
马克

不,我没有在onCreateView()方法中加载图片。所有的图片都是在instantiateItem PagerAdapter中加载的。 - Sheetal Suryan
有没有办法释放应用程序内存?哪些图片正在使用? - Sheetal Suryan
我认为您的应用程序当前的结构将保存所有图像的引用,因此在某个时候您还是会耗尽内存,特别是在较小的设备上。 您需要将图像的创建移动到onCreateView方法中,这样框架只会保留三个图像引用,即先前的图像,当前显示的图像和序列中的下一个图像。 这些图像在下一个滑动时被替换,其中一个图像从三个列表中掉落,另一个图像代替它加入。 - Mark
谢谢Mark,你能提供一个使用onCreateView方法与图片结合的示例吗? - Sheetal Suryan
1
好的,让我看看我能为您找到什么。 - Mark

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