Android ViewPager с изображениями: утечка памяти/сбои приложений

Я пишу приложение, которое отображает панорамное изображение, которое в конечном итоге имеет несколько маркеров, чтобы отображать информацию о некоторых точках.

Поскольку большое изображение разбило приложение (у меня есть еще одна активность в приложении, отображающая большую карту), теперь я пытаюсь отобразить панораму как последовательность страниц с ViewPager.

Мне удалось отобразить изображение в 6 бит, и я подумал, что все идет хорошо, но теперь приложение вылетает после нескольких прокруток (от 7 до 8), когда память заканчивается.

Я вытягиваю свои волосы, чтобы понять, почему это так, как я думал, что мои предметы будут уничтожены, как только они покинут экран? Я абсолютный noobs, и мне очень жаль, если я буду тратить время. Я весь день читал и тестировал решения здесь и в другом месте, и я не мудрее.

вот мой код: деятельность 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>

Я обещаю, что с этого момента я буду полезным членом (или тем более, когда я действительно знаю, что я делаю).

EDIT: - В первом действии я показываю изображение 552kb. - Шесть изображений, которые я показываю в этой активности (PanoramaView), составляют от 309 до 500 КБ. - Я использовал трекер распределения в 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>

Я попробовал установить ограничение на страницу, которое не помогло.

Я нашел эту ссылку об управлении памятью на другом посту, и я посмотрю на нее сегодня вечером.

EDIT: вот вывод 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

В случае, если кто-то сталкивается с этим сообщением, вот что, по-видимому, исправило это: Я изменил код на код из приведенного ниже ответа, и я также разрезал свою панораму на более мелкие биты, чтобы каждое изображение теперь ниже 300 КБ.

Ответы

Ответ 1

Поэтому используйте

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)

2 Протестировано на 6 изображений размером 400 тыс. каждый работает нормально

@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

Гудлак

Ответ 2

Немного поздно с ответом, но проблема довольно проста на самом деле, и я не вижу других ответов, нацеленных на нее.

Дело в том, что когда вы вручную декодируете растровые изображения (как и в тех пронумерованных строках), , вам нужно .recycle() их. Итак, вернемся к вашему коду, в вашем адаптере вы должны дополнить это:

@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;
}

Это так просто, не нужно использовать отдельное управление растровым изображением или что-то еще.

Ответ 3

Хорошо документированный вопрос, и даже если вы, похоже, решили его, вот крошечный бит потенциально полезной информации.

От вас logcat, я обнаруживаю, что вы разрабатываете (или, по крайней мере, тестирование) на Gingerbread (потому что Honeycomb onward не включает в себя "внешнюю" часть на выходе. Это важно только потому, что в нем подчеркивается, что происходит с вашими растровыми изображениями В Gingerbread данные в растровых изображениях помещаются в встроенную память, за пределами кучи. Затем указатель на эти данные помещается в кучу вместе с некоторой другой ссылочной информацией. Удаление каждой ссылки на растровое изображение освобождает ссылочную информацию (в в какой-то момент, когда выполняется System.gc()). Однако данные растровых изображений никогда не будут выпущены, если ваше устройство не пострадает от астероида - или (менее драматично) вы вызываете метод recycle() на этом растровом изображении. Следует отметить, что действительно освобождает встроенную память, поэтому вам действительно нужно повторно создать растровое изображение с нуля, когда это необходимо снова с помощью ViewPager (скорее всего, в instantiateItem. Сотовые и последующие помещают растровые данные в пределах стека, который немного менее раздражает. data по-прежнему выпускается только тогда, когда вы recycle() растровое изображение (так что вам нужно копать немного глубже, используя DDMS, чтобы определить, что происходит - например, я сейчас борюсь с этой же проблемой на JB и в ICS, 4.0.3 ведет себя иначе, чем 4.0.4, но я отвлекаюсь).

Это, вероятно, слишком много, но мое решение заключалось в том, чтобы реализовать класс для отслеживания моих растровых изображений и быть уверенным, что я их переработал - в вашем случае это произойдет, когда вы destroyItem() в ViewPager.

Здесь (несколько пешеходный) класс, который я использую для отслеживания вещей, fwiw.

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);
}

Это, конечно, требует Исполнитель:

    private ExecutorService mImagePipelineTask = null;

Что может быть создано таким образом:

    mImagePipelineTask = Executors.newSingleThreadExecutor();

(Или, если вы предприимчивы, вы можете использовать многопоточный исполнитель, такую ​​же общую идею).

Возможно, это помогает уточнить.

Ответ 4

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

здесь MAX_PAGE - количество страниц, которые могут оставаться за пределами видимости. Любое дополнение к ним будет уничтожено и только воссоздается, когда пользователь переместится обратно в ближайшую позицию.