Android 图片 ViewPager:内存泄漏/应用程序崩溃

19
我正在编写一个应用程序,用于显示全景图片,并在其上放置多个标记以显示有关某些点的信息。
由于大型图像导致应用程序崩溃(我还有另一个在应用程序中显示大型地图的活动),因此我现在正在尝试使用ViewPager将全景图像显示为一系列页面。
我已经成功地显示了6个部分的图片,并且我认为事情进展顺利,但是现在应用程序在几次滑动后(大约7到8次)会因内存耗尽而崩溃。
我很困惑,不知道为什么会这样,因为我认为我的项目一旦离开屏幕就会被销毁?
我是一个绝对的新手,如果我浪费了您的时间,我非常抱歉。我已经花了一整天的时间阅读并尝试从这里和其他地方找到解决方案,但我仍然一无所获。
以下是我的代码: activity PanoramaView
public class PanoramaView extends Activity {

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.panorama);
    MyPagerAdapter adapter = new MyPagerAdapter();
    ViewPager myPager = (ViewPager) findViewById(R.id.mysixpanelpager);
    myPager.setAdapter(adapter);
    myPager.setCurrentItem(2);
}


}

MyPagerAdapter

public class MyPagerAdapter extends PagerAdapter {
    public int getCount() {
        return 6;
    }
    public Object instantiateItem(View collection, int position) {
        LayoutInflater inflater = (LayoutInflater) collection.getContext()
                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        int resId = 0;
        switch (position) {
        case 0:
            resId = R.layout.farleft;
            break;
        case 1:
            resId = R.layout.left;
            break;
        case 2:
            resId = R.layout.middle;
            break;
        case 3:
            resId = R.layout.right;
            break;
        case 4:
            resId = R.layout.farright;
            break;
        case 5:
            resId = R.layout.farfarright;
            break;
        }
        //ImageView imageView = new ImageView(getApplicationContext());
        //imageView.findViewById(R.id.imageView);
        //imageView.setImageBitmap(BitmapFactory.decodeResource(getResources(), ids[position]));

        View view = inflater.inflate(resId, null);
        ((ViewPager) collection).addView(view, 0);
        return view;
    }
    @Override
    public void destroyItem(View collection, int position, Object o) {
        View view = (View)o;
        ((ViewPager) collection).removeView(view);
        view = null;
    }

    @Override
    public boolean isViewFromObject(View arg0, Object arg1) {
        return arg0 == ((View) arg1);
    }
    @Override
    public Parcelable saveState() {
        return null;
    }
}

我的主要布局文件

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<android.support.v4.view.ViewPager
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/mysixpanelpager"/>
</LinearLayout>

我保证我会成为一个有用的成员(或者一旦我真正知道我在做什么,我会更加有用)。
编辑: - 在第一个活动中,我展示了一张大小为552kb的图片。 - 我在这个活动(PanoramaView)中展示的六张图片大小在309到500kb之间。 - 我在Eclipse中使用了一个分配跟踪器,我所能看到的只是内存填充,但确切的数据对我来说并不清楚 - 崩溃发生在显示7或8张图片后(基本上在来回滑动几次后)
这是farfarright.xml的代码。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content" >

<ImageView 
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:id="@+id/imageView"
    android:src="@drawable/panorama6"
    android:adjustViewBounds="true"
    android:contentDescription="@string/panorama" >

</ImageView>
</LinearLayout>

我尝试设置了离屏页面限制,但没有帮助。

我在另一篇帖子中发现了关于内存管理的链接,今晚我会看一下。

编辑:这是LogCat输出。

11-28 21:17:42.551: D/dalvikvm(328): GC_EXTERNAL_ALLOC freed 51K, 53% free 2558K/5379K, external 2002K/2137K, paused 65ms
11-28 21:17:43.261: D/dalvikvm(328): GC_EXTERNAL_ALLOC freed 1K, 53% free 2557K/5379K, external 3297K/4118K, paused 44ms
11-28 21:17:47.741: W/KeyCharacterMap(328): No keyboard for id 0
11-28 21:17:47.741: W/KeyCharacterMap(328): Using default keymap: /system/usr/keychars/qwerty.kcm.bin
11-28 21:17:49.141: D/DFSAPP(328): my button id before is 2
11-28 21:17:49.691: D/dalvikvm(328): GC_EXTERNAL_ALLOC freed 36K, 52% free 2614K/5379K, external 15576K/15708K, paused 50ms
11-28 21:17:54.571: D/dalvikvm(328): GC_EXTERNAL_ALLOC freed 12K, 52% free 2616K/5379K, external 17386K/17735K, paused 39ms
11-28 21:17:54.661: D/dalvikvm(328): GC_EXTERNAL_ALLOC freed 0K, 52% free 2616K/5379K, external 17386K/17735K, paused 61ms
11-28 21:17:54.711: I/dalvikvm-heap(328): Clamp target GC heap from 25.629MB to 24.000MB
11-28 21:17:54.711: D/dalvikvm(328): GC_FOR_MALLOC freed <1K, 52% free 2616K/5379K, external 18975K/21023K, paused 42ms
11-28 21:18:03.751: D/dalvikvm(328): GC_EXTERNAL_ALLOC freed 6K, 52% free 2616K/5379K, external 18269K/20317K, paused 46ms
11-28 21:18:03.822: I/dalvikvm-heap(328): Clamp target GC heap from 25.628MB to 24.000MB
11-28 21:18:03.852: D/dalvikvm(328): GC_FOR_MALLOC freed <1K, 52% free 2615K/5379K, external 18975K/20317K, paused 32ms
11-28 21:18:04.131: D/dalvikvm(328): GC_EXTERNAL_ALLOC freed <1K, 52% free 2615K/5379K, external 17386K/19434K, paused 49ms
11-28 21:18:04.191: I/dalvikvm-heap(328): Clamp target GC heap from 25.628MB to 24.000MB
11-28 21:18:04.201: D/dalvikvm(328): GC_FOR_MALLOC freed 0K, 52% free 2615K/5379K, external 18975K/19434K, paused 34ms
11-28 21:18:07.301: D/dalvikvm(328): GC_EXTERNAL_ALLOC freed 1K, 52% free 2616K/5379K, external 18269K/19434K, paused 46ms
11-28 21:18:07.381: I/dalvikvm-heap(328): Clamp target GC heap from 25.801MB to 24.000MB
11-28 21:18:07.401: D/dalvikvm(328): GC_FOR_MALLOC freed <1K, 52% free 2616K/5379K, external 19152K/19434K, paused 38ms
11-28 21:18:07.611: D/dalvikvm(328): GC_EXTERNAL_ALLOC freed <1K, 52% free 2615K/5379K, external 18159K/19434K, paused 47ms
11-28 21:18:07.681: I/dalvikvm-heap(328): Clamp target GC heap from 25.801MB to 24.000MB
11-28 21:18:07.681: D/dalvikvm(328): GC_FOR_MALLOC freed 0K, 52% free 2615K/5379K, external 19152K/19434K, paused 36ms
11-28 21:18:18.901: D/dalvikvm(328): GC_EXTERNAL_ALLOC freed 5K, 52% free 2616K/5379K, external 18269K/19434K, paused 57ms
11-28 21:18:18.972: I/dalvikvm-heap(328): Clamp target GC heap from 25.802MB to 24.000MB
11-28 21:18:18.991: D/dalvikvm(328): GC_FOR_MALLOC freed <1K, 52% free 2616K/5379K, external 19152K/19434K, paused 33ms
11-28 21:18:19.181: D/dalvikvm(328): GC_EXTERNAL_ALLOC freed 1K, 52% free 2615K/5379K, external 18159K/19434K, paused 55ms
11-28 21:18:19.251: I/dalvikvm-heap(328): Clamp target GC heap from 25.801MB to 24.000MB
11-28 21:18:19.251: D/dalvikvm(328): GC_FOR_MALLOC freed 0K, 52% free 2615K/5379K, external 19152K/19434K, paused 33ms
11-28 21:18:21.551: D/dalvikvm(328): GC_EXTERNAL_ALLOC freed 1K, 52% free 2616K/5379K, external 18975K/19434K, paused 46ms
11-28 21:18:21.581: E/dalvikvm-heap(328): 1627200-byte external allocation too large for this process.
11-28 21:18:21.621: I/dalvikvm-heap(328): Clamp target GC heap from 25.629MB to 24.000MB
11-28 21:18:21.621: E/GraphicsJNI(328): VM won't let us allocate 1627200 bytes
11-28 21:18:21.631: D/dalvikvm(328): GC_FOR_MALLOC freed 0K, 52% free 2616K/5379K, external 18975K/19434K, paused 34ms
11-28 21:18:21.641: D/AndroidRuntime(328): Shutting down VM
11-28 21:18:21.641: W/dalvikvm(328): threadid=1: thread exiting with uncaught exception (group=0x40015560)
11-28 21:18:21.732: E/AndroidRuntime(328): FATAL EXCEPTION: main
11-28 21:18:21.732: E/AndroidRuntime(328): android.view.InflateException: Binary XML file line #7: Error inflating class <unknown>
11-28 21:18:21.732: E/AndroidRuntime(328):  at android.view.LayoutInflater.createView(LayoutInflater.java:518)
11-28 21:18:21.732: E/AndroidRuntime(328):  at com.android.internal.policy.impl.PhoneLayoutInflater.onCreateView(PhoneLayoutInflater.java:56)
11-28 21:18:21.732: E/AndroidRuntime(328):  at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:568)
11-28 21:18:21.732: E/AndroidRuntime(328):  at android.view.LayoutInflater.rInflate(LayoutInflater.java:623)
11-28 21:18:21.732: E/AndroidRuntime(328):  at android.view.LayoutInflater.inflate(LayoutInflater.java:408)
11-28 21:18:21.732: E/AndroidRuntime(328):  at android.view.LayoutInflater.inflate(LayoutInflater.java:320)
11-28 21:18:21.732: E/AndroidRuntime(328):  at android.view.LayoutInflater.inflate(LayoutInflater.java:276)
11-28 21:18:21.732: E/AndroidRuntime(328):  at com.businesbike.dfp.MyPagerAdapter.instantiateItem(MyPagerAdapter.java:43)
11-28 21:18:21.732: E/AndroidRuntime(328):  at android.support.v4.view.ViewPager.addNewItem(ViewPager.java:692)
11-28 21:18:21.732: E/AndroidRuntime(328):  at android.support.v4.view.ViewPager.populate(ViewPager.java:849)
11-28 21:18:21.732: E/AndroidRuntime(328):  at android.support.v4.view.ViewPager.populate(ViewPager.java:772)
11-28 21:18:21.732: E/AndroidRuntime(328):  at android.support.v4.view.ViewPager.completeScroll(ViewPager.java:1539)
11-28 21:18:21.732: E/AndroidRuntime(328):  at android.support.v4.view.ViewPager.computeScroll(ViewPager.java:1422)
11-28 21:18:21.732: E/AndroidRuntime(328):  at android.view.ViewGroup.drawChild(ViewGroup.java:1562)
11-28 21:18:21.732: E/AndroidRuntime(328):  at android.view.ViewGroup.dispatchDraw(ViewGroup.java:1373)
11-28 21:18:21.732: E/AndroidRuntime(328):  at android.view.ViewGroup.drawChild(ViewGroup.java:1644)
11-28 21:18:21.732: E/AndroidRuntime(328):  at android.view.ViewGroup.dispatchDraw(ViewGroup.java:1373)
11-28 21:18:21.732: E/AndroidRuntime(328):  at android.view.View.draw(View.java:6883)
11-28 21:18:21.732: E/AndroidRuntime(328):  at android.widget.FrameLayout.draw(FrameLayout.java:357)
11-28 21:18:21.732: E/AndroidRuntime(328):  at android.view.ViewGroup.drawChild(ViewGroup.java:1646)
11-28 21:18:21.732: E/AndroidRuntime(328):  at android.view.ViewGroup.dispatchDraw(ViewGroup.java:1373)
11-28 21:18:21.732: E/AndroidRuntime(328):  at android.view.ViewGroup.drawChild(ViewGroup.java:1644)
11-28 21:18:21.732: E/AndroidRuntime(328):  at android.view.ViewGroup.dispatchDraw(ViewGroup.java:1373)
11-28 21:18:21.732: E/AndroidRuntime(328):  at android.view.View.draw(View.java:6883)
11-28 21:18:21.732: E/AndroidRuntime(328):  at android.widget.FrameLayout.draw(FrameLayout.java:357)
11-28 21:18:21.732: E/AndroidRuntime(328):  at com.android.internal.policy.impl.PhoneWindow$DecorView.draw(PhoneWindow.java:1862)
11-28 21:18:21.732: E/AndroidRuntime(328):  at android.view.ViewRoot.draw(ViewRoot.java:1522)
11-28 21:18:21.732: E/AndroidRuntime(328):  at android.view.ViewRoot.performTraversals(ViewRoot.java:1258)
11-28 21:18:21.732: E/AndroidRuntime(328):  at android.view.ViewRoot.handleMessage(ViewRoot.java:1859)
11-28 21:18:21.732: E/AndroidRuntime(328):  at android.os.Handler.dispatchMessage(Handler.java:99)
11-28 21:18:21.732: E/AndroidRuntime(328):  at android.os.Looper.loop(Looper.java:123)
11-28 21:18:21.732: E/AndroidRuntime(328):  at android.app.ActivityThread.main(ActivityThread.java:3683)
11-28 21:18:21.732: E/AndroidRuntime(328):  at java.lang.reflect.Method.invokeNative(Native Method)
11-28 21:18:21.732: E/AndroidRuntime(328):  at java.lang.reflect.Method.invoke(Method.java:507)
11-28 21:18:21.732: E/AndroidRuntime(328):  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:839)
11-28 21:18:21.732: E/AndroidRuntime(328):  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:597)
11-28 21:18:21.732: E/AndroidRuntime(328):  at dalvik.system.NativeStart.main(Native Method)
11-28 21:18:21.732: E/AndroidRuntime(328): Caused by: java.lang.reflect.InvocationTargetException
11-28 21:18:21.732: E/AndroidRuntime(328):  at java.lang.reflect.Constructor.constructNative(Native Method)
11-28 21:18:21.732: E/AndroidRuntime(328):  at java.lang.reflect.Constructor.newInstance(Constructor.java:415)
11-28 21:18:21.732: E/AndroidRuntime(328):  at android.view.LayoutInflater.createView(LayoutInflater.java:505)
11-28 21:18:21.732: E/AndroidRuntime(328):  ... 36 more
11-28 21:18:21.732: E/AndroidRuntime(328): Caused by: java.lang.OutOfMemoryError: bitmap size exceeds VM budget
11-28 21:18:21.732: E/AndroidRuntime(328):  at android.graphics.Bitmap.nativeCreate(Native Method)
11-28 21:18:21.732: E/AndroidRuntime(328):  at android.graphics.Bitmap.createBitmap(Bitmap.java:477)
11-28 21:18:21.732: E/AndroidRuntime(328):  at android.graphics.Bitmap.createBitmap(Bitmap.java:444)
11-28 21:18:21.732: E/AndroidRuntime(328):  at android.graphics.Bitmap.createScaledBitmap(Bitmap.java:349)
11-28 21:18:21.732: E/AndroidRuntime(328):  at android.graphics.BitmapFactory.finishDecode(BitmapFactory.java:498)
11-28 21:18:21.732: E/AndroidRuntime(328):  at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:473)
11-28 21:18:21.732: E/AndroidRuntime(328):  at android.graphics.BitmapFactory.decodeResourceStream(BitmapFactory.java:336)
11-28 21:18:21.732: E/AndroidRuntime(328):  at android.graphics.drawable.Drawable.createFromResourceStream(Drawable.java:697)
11-28 21:18:21.732: E/AndroidRuntime(328):  at android.content.res.Resources.loadDrawable(Resources.java:1709)
11-28 21:18:21.732: E/AndroidRuntime(328):  at android.content.res.TypedArray.getDrawable(TypedArray.java:601)
11-28 21:18:21.732: E/AndroidRuntime(328):  at android.widget.ImageView.<init>(ImageView.java:118)
11-28 21:18:21.732: E/AndroidRuntime(328):  at android.widget.ImageView.<init>(ImageView.java:108)
11-28 21:18:21.732: E/AndroidRuntime(328):  ... 39 more

如果有人遇到了这个问题,以下是解决方法: 我已经将代码更改为下面答案中的代码,并将我的全景图片分成较小的部分,使每个图像现在低于300kb。


你说的大图是什么意思?能否提供堆栈跟踪信息?它是在第一张图片崩溃还是成功显示了一些图片后崩溃?你能否也提供“farfarright”布局的信息? - Mikhaili
我已经在上面添加了一些更多的细节。非常感谢任何帮助。 - Workbarby
你需要从logcat中发布异常堆栈跟踪。使用一个布局来进行页面视图。 - Mikhaili
创建具有图片视图的 XML,每次设置 imageview.setImageResourceId,或者如果您可以共享您项目的一部分 - Mikhaili
4个回答

8
有点晚了,但实际上这个问题非常简单,我没有看到其他答案指向它。
问题是,当您手动解码位图(就像您在那些已注释的行中所做的那样),您必须自己进行回收。 因此,回到您的代码,在适配器中,您必须补充如下内容:
@Override
public Object instantiateItem(View collection, int position) {
    // ...
    View view = inflater.inflate(resId, null);
    ImageView imageView = (ImageView) view.findViewById(R.id.imageView);
    imageView.setImageBitmap(BitmapFactory.decodeResource(getResources(),
            ids[position]));
    ((ViewPager) collection).addView(view, 0);
    return view;
}

使用以下方法-- 对Bitmap进行回收并将View从层次结构中移除:

@Override
public void destroyItem(View collection, int position, Object o) {
    View view = (View)o;
    ImageView imgView = (ImageView) view.findViewById(R.id.imageView);
    BitmapDrawable bmpDrawable = (BitmapDrawable) imgView.getDrawable();
    if (bmpDrawable != null && bmpDrawable.getBitmap() != null) {
            // This is the important part
            bmpDrawable.getBitmap().recycle();
    }
    ((ViewPager) collection).removeView(view);
    view = null;
}

就是这么简单,不需要使用单独的位图管理或其他任何东西。

我遇到了java.lang.RuntimeException异常:Canvas:尝试使用已回收的位图android.graphics.Bitmap@2affc178,当返回到之前查看过的图像(现在已被回收)时。 - Yar
@Yar 这很奇怪。问题是,在调用destroyItem之后,它所包含的图像视图不再被渲染。因此,除了页面适配器之外,您必须有其他一些逻辑来尝试访问该位图。如果您提供更多细节(至少包括行号和相关代码),我们可能可以想出解决方案。 - Ivan Bartsov
你好,当我在查看器中滑动图像时,出现了相同的错误“java.lang.RuntimeException: Canvas: trying to use a recycled bitmap”。请问能否提供帮助? - Dory

4

这是一个很好的问题,即使你似乎已经解决了它,这里有一点可能有用的信息。

从你的logcat中,我发现你正在开发(或至少在测试)Gingerbread(因为Honeycomb及以后的版本不包括输出中的“external”部分)。 这只是重要的一点,因为它突出了你的位图发生了什么。 在Gingerbread中,位图数据被放置在本机内存中,而不是堆内存中。 然后,在堆内存中放置了指向该数据的指针以及其他引用信息。 删除对位图的每个引用将释放引用信息(在某些情况下,当System.gc()运行时)。 但是,除非你的设备被小行星击中 - 或者(不那么戏剧化),你调用了recycle()方法,否则位图数据将永远不会被释放。 应该注意的是,该调用确实会释放本机内存,因此当ViewPager再次需要它时(很可能在instantiateItem中),你确实需要从头开始重新创建位图。 Honeycomb及以后版本将位图数据放置在堆栈中,这稍微不那么烦人。 只有当你recycle()该位图时,data才会被释放(因此你必须深入挖掘,使用DDMS来确定发生了什么 - 例如,我目前正在JB上解决同样的问题,在ICS中,4.0.3的行为与4.0.4不同,但我偏离了主题)。

这可能有点过度,但我的解决方案是实现一个类来跟踪我的位图,并确保我已经将它们回收 - 在你的情况下,当你在ViewPager中调用destroyItem()时,这将发生。

这是我用来跟踪事物的(有点平凡的)类,供参考。

public class BitmapManager {

private final String TAG = "DEBUG -- " + ClassUtils.getShortClassName(this.getClass());
Context mContext = null;

private class BitmapVectorEntry {
    public Bitmap bm = null;
    public String name = null;
}

Vector<BitmapVectorEntry> mBitmapVector = new Vector<BitmapVectorEntry>();

public BitmapManager( Context aContext ) {
    mContext = aContext;
}

public void setContext( Context aContext ) {
    mContext = aContext;
}

public void registerBitmap( String name, Bitmap b) {

    if(mBitmapVector == null) {
        mBitmapVector = new Vector<BitmapVectorEntry>();
    }
    if(b == null) {
        Log.e(TAG, "Bitmap is NULL!!  ");
        return;
    }
    // Log.d(TAG, "        ~~~~~~ Registering ["+name+"]  ["+b+"]");
    BitmapVectorEntry be = new BitmapVectorEntry();
    be.bm = b;
    be.name = name;
    mBitmapVector.add(be);
}

public void registerBitmapForBackgroundDrawable( String name, View v ) {
    if(v != null) {
        Drawable d = v.getBackground();
        if(d != null) {
            if(d instanceof BitmapDrawable) {
                Bitmap bm = ((BitmapDrawable) d).getBitmap();
                if(bm != null) {
                    // Log.w(TAG, "     ~~~~  Registering Background Bitmap [" + bm + "]");
                    registerBitmap(name, bm);
                }  else  {
                    Log.w(TAG, " ~~~~ Background does not have a bitmap in the BitmapDrawable (Probably, but not necessarily, and error)");
                }
            }  else  {
                Log.w(TAG, " ~~~~ Background does not have a BitmapDrawable (Might not be an error)");
            }
        }  else  {
            Log.w(TAG, " ~~~~~ Background is null, no drawable (Might not be an error)");
        }
    } else  {
        Log.e(TAG, "  ~~~~~ View is null, is there no background for this view?");
    }
}

// We cannot recycle certain bitmaps, like the background for the page which houses
    // the ViewPager, since the pager reuses it, so we just delete it from vector
public void clear(Bitmap bm) {
    removeBitmap(bm, false);
}

// In most cases, when we are done with a bitmap, we want to recycle it.  This is a
    // synchronous call that frees external heap (in 2.3.x) or internal heap (3.x < ).
    // And when I say 'synchronous' I mean 'slow' and 'should not be run on the UI Thread,
    // So be sure to throw this on an async thread
public void recycleBitmap(Bitmap bm) {
    removeBitmap(bm, true);
}

private void removeBitmap(Bitmap bm, boolean andRecycleToo) {

    if(bm == null) {
        Log.e(TAG, "(RECYCLE BITMAP)  !!!! Bitmap is NULL!!, cannot recycle");
        return;
    }
    if(mBitmapVector == null) {
        Log.e(TAG, "(RECYCLE BITMAP)  !!!! Bitmap Vector is NULL!!");
        return;
    }

    boolean foundIt = false;
    Bitmap targetBm = null;
    int i = (mBitmapVector.size() - 1);
    try {
        for(; i >= 0; i--)  {
            BitmapVectorEntry b = mBitmapVector.get(i);
            targetBm = b.bm;
            if(targetBm.equals(bm)) {
                foundIt = true;
                if(andRecycleToo) {
                    if(!targetBm.isRecycled()) {
                        targetBm.recycle();
                    }
                }
                mBitmapVector.removeElementAt(i);
                // Log.e(TAG, "       Recycling ["+targetBm.name+"]  ["+targetBm.bm+"]");
                break;
            }
        }
    }  catch(Exception e) {

        Log.e(TAG, "Exception during recycling bitmap position ["+i+"] ["+bm+"] ["+e+"]");

    }  finally {

        mBitmapVector.trimToSize();
        if(andRecycleToo) {
            if(!foundIt && targetBm != null) {
                if(!targetBm.isRecycled())  {
                    targetBm.recycle();
                } 
                Log.e(TAG, "(RECYCLE BITMAP)   ========================= !!! RECYCLING Bitmap ["+targetBm+"], was unregistered, recycled is ["+targetBm.isRecycled()+"]");
            }  else  {
                // Log.i(TAG, "(RECYCLE BITMAP)   ========================= !!! RECYCLING Bitmap ["+targBe.name+"] ["+targBe.bm+"], was registered");
            }
        }

    }

}

public void flush() {
    if(mBitmapVector == null) {
        // Log.e(TAG, "!!!! Bitmap Vector is NULL!!");
        return;
    }
    for(int i = 0; i < mBitmapVector.size(); i++) {
        BitmapVectorEntry bme = mBitmapVector.get(i);
        if(!bme.bm.isRecycled()) {
            // Log.e(TAG, "Flushing Bitmap ["+bme.name+"] ["+bme.bm+"]");
            bme.bm.recycle();
        }
    }
    mBitmapVector.clear();
    mBitmapVector.trimToSize();

}

public void dumpBitmaps() {
    if(mBitmapVector == null) {
        // Log.e(TAG, "!!!! Bitmap Vector is NULL!!");
        return;
    }
    boolean foundOne = false;
    for(int i = 0; i < mBitmapVector.size(); i++) {
        Bitmap bm0 = mBitmapVector.get(i).bm;
        if(!bm0.isRecycled()) {
            foundOne = true;
            break;
        }
    }
    if(mBitmapVector.size() > 0 && foundOne) {
        Log.e(TAG, " ========= Dumping Bitmap Vector === (Found a leaker) ===== ");
        Log.e(TAG, "            "+mBitmapVector.size()+" entries");
        for(BitmapVectorEntry b : mBitmapVector) {
            if(!b.bm.isRecycled()) {
                Log.e(TAG, "       ["+b.name+"]  ["+b.bm+"]  Recycled ["+b.bm.isRecycled()+"]");
            }
        }
        Log.e(TAG, " ========= End of Bitmap Dump    ======== ");
    }
}
}

关键点在于dumpBitmaps()调用(至少对于你上述描述的问题而言)。如果认真注册了所有位图,则dumpBitmaps()调用将暴露出需要清理的任何位图。如果您不关心泄漏的位置,只想让它消失,那么您可以直接调用flush()来删除所有位图。
您需要在创建位图时放置registerBitmap()。我使用过充气机做了一些不可预测的事情,所以我更喜欢这样的方式:
    public Drawable getPreformattedFile() {
    // Log.d(TAG, "Loading in Drawable ["+preformattedFileName()+"]");
    if( preformattedFileName() == null) {
        Log.e(TAG, "Formatted Filename is null");
        return(null);
    }

    Drawable ret = null;
    try {

        BitmapFactory.Options opts = new BitmapFactory.Options();
        opts.inScaled = false;
        opts.inPurgeable = true;

        Bitmap bm = BitmapFactory.decodeFile(preformattedFileName(), opts);
        if(bm == null) {
            return(null);
        }
        mBitmapManager.registerBitmap(mItem.name(), bm);
        ret = new BitmapDrawable(mContext.getResources(), bm);

        // Log.i(TAG, "         ~~~~~~~~~~~~~~~~~ JUST CHECKING ["+bm+"]  ["+((BitmapDrawable) ret).getBitmap()+"]");

    } catch( OutOfMemoryError e ) {
        // Log.e(TAG, " ============== Before gc ==== OOME  Thread ["+Thread.currentThread().getName()+"]  getPreformattedFile.Before GC Heap Available [[[ "+(Debug.getNativeHeapFreeSize()/1024)+"k ]]]");
        System.gc();
        // Log.e(TAG, " ============== After gc  ==== OOME  Thread ["+Thread.currentThread().getName()+"]  getPreformattedFile.Before GC Heap Available [[[ "+(Debug.getNativeHeapFreeSize()/1024)+"k ]]]");
        e.printStackTrace();
    } catch( Exception e) {
        Log.e(TAG, "Trouble reading PNG file ["+e+"]");
    }
    return(ret);
}

我多次提到过异步操作的事情,你也提到你对Android还比较新。为了完整起见,我应该提到我不喜欢使用AsyncTask,因为它在多线程方面有一些严重的限制,而图像往往需要大量的多线程处理。因此,我使用一个Executor来做类似这样的事情(你会注意到,它使用了上面的方法来完成实际的工作):

public Drawable getPreformattedFileAsync() {
    if(mItem == null) {
        Log.e(TAG, " -- ITEM is NULL!!");
        return(mErrorDrawable);
    }
    if(mFetchFileTask == null) {
        Log.e(TAG, " -- Task is Null!!, Need to start an executor");
        return(mErrorDrawable);
    }
    Runnable job = new Runnable() {
         public void run() {
             Thread.currentThread().setName("ImagePipeline");
             Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
             Thread.currentThread().yield();
             if(mItemDelegate != null) {
                 Drawable retDrawable = getPreformattedFile();
                 if(showAllDebugInformation) {
                     Log.w(TAG, "   ^^^^ Getting preformatted file size ["+retDrawable.getIntrinsicWidth()+"] x ["+retDrawable.getIntrinsicHeight()+"]");
                 }
                 if(retDrawable != null) {
                     Bitmap bm = ((BitmapDrawable) retDrawable).getBitmap();
                     // Log.w(TAG, "       Size of Bitmap is ["+(bm.getRowBytes()*bm.getHeight())+"]");
                     mItemDelegate.onDrawableRequest(mItem, retDrawable);
                     if(mBitmapManager != null) {
                         if(mBusyDrawable != null) {
                             mBitmapManager.recycleBitmap(((BitmapDrawable) mBusyDrawable).getBitmap());
                         }
                         if(mErrorDrawable != null) {
                             mBitmapManager.recycleBitmap(((BitmapDrawable) mErrorDrawable).getBitmap());
                         }
                     }
                 }  else  {
                     mItemDelegate.onDrawableRequest(mItem, mErrorDrawable);
                 }
             }
             // Log.i(TAG, "  RUNNABLE - Set the background");
         }
     };
     mImagePipelineTask.execute(job);
     return(mBusyDrawable);
}

当然,这需要一个执行器(Executor):
    private ExecutorService mImagePipelineTask = null;

可以这样创建:
    mImagePipelineTask = Executors.newSingleThreadExecutor();

(或者,如果你很有冒险精神,可以使用多线程执行器,基本思路相同。)
也许这样可以更清晰地说明问题。

谢谢!指向recycle()是我解决ViewPager内存泄漏所需的。 - Cephron

3
所以使用:
1
@Override
public Object instantiateItem(ViewGroup collection, int position)
@Override
public void destroyItem(ViewGroup collection, int position, Object view)

替代

 public Object instantiateItem(View collection, int position)
 public void destroyItem(View collection, int position, Object view)

已测试6张大小为400k的图像,工作正常。

@Override
public Object instantiateItem(ViewGroup collection, int position)
{
    LayoutInflater inflater = (LayoutInflater) collection.getContext()
            .getSystemService(Context.LAYOUT_INFLATER_SERVICE);



    int resId = 0;
    switch (position) {
    case 0:
        resId = R.layout.farleft;
        break;
    case 1:
        resId = R.layout.left;
        break;
    case 2:
        resId = R.layout.middle;
        break;
    case 3:
        resId = R.layout.right;
        break;
    case 4:
        resId = R.layout.farright;
        break;
    case 5:
        resId = R.layout.farfarright;
        break;
    }

View view = (View) inflater.inflate(resId, null);


 //get Image view from layout   
//ImageView imageView = (ImageView) view.findViewById(resId);

//imageView.setImageResource(resId);

    collection.addView(view, 0);

    return view;
}

@Override
public void destroyItem(ViewGroup collection, int position, Object view)
{
    collection.removeView((View) view);
}

3个有用的链接 PageAdapter

祝你好运


非常感谢您提供的代码和链接。我已经用您的代码替换了我的,但问题仍然存在。我之前没有意识到我实际上在使用过时的东西。我会尝试禁用一些部分,看看能否进一步解决问题。 - Workbarby
@Workbarby:你将这个标记为答案,所以能否告诉我确切的解决方案?我也遇到了同样的问题,对解决方案一无所知。我尝试了很多方法,但都失败了。我尝试了使用Picasso或Universal Image Loader,但仍然没有成功。 - Sagar Panwala

2
pager.setOffscreenPageLimit(MAX_PAGE);  //pager is the ViewPager instance

这里的MAX_PAGE是指可以保留在可见范围之外的页面数量。超过这个数量的页面将会被销毁,只有当用户向后滑动到附近的位置时才会重新创建。


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