Android ImageSwitcher: Ошибка нехватки памяти при setImageURI - PullRequest
1 голос
/ 10 февраля 2012

Кажется, у меня утечка памяти, и я не уверен, как ее исправить. Я прочитал все учебные пособия и примеры по ImageSwitcher на android.com, но похоже, что все они имеют дело с рисованными объектами, которые уже находятся в папке drawables. Мой код позволяет пользователю делать одну или две фотографии своей камерой, сохранять изображения на SD-карте и использовать переключатель изображений, чтобы «переворачивать» изображения поверх.

public class CardViewImageActivity extends Activity implements ViewFactory {

    private CardDBAdapter mDbHelper;

    private String _cardImgGuidFront;
    private String _cardImgGuidBack;
    private Boolean frontShowing = false;
    private Boolean hasFront = false;
    private Boolean hasBack = false;

    private Uri uriFront;
    private Uri uriBack;

    private int cardId;

    private ImageSwitcher iSwitcher;
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.card_view_image);

        iSwitcher = (ImageSwitcher)findViewById(R.id.imageSwitcher);
        iSwitcher.setFactory(this);
        iSwitcher.setOnClickListener(SwitcherOnClick);

        this.cardId = Integer.parseInt(getIntent().getExtras().getString(
                "cardId")); //$NON-NLS-1$

        getCardImageGuids(this.cardId);

        if(_cardImgGuidFront != null)
        {
            hasFront = true;
            uriFront = Uri.parse(Environment.getExternalStorageDirectory().toString() + "/" + _cardImgGuidFront + ".jpg");
        }

        if(_cardImgGuidBack != null)
        {
            hasBack = true;
            uriBack = Uri.parse(Environment.getExternalStorageDirectory().toString() + "/" + _cardImgGuidBack + ".jpg");            
        }
        if(hasFront && hasBack)
            Toast.makeText(this, R.string.card_view_touch, Toast.LENGTH_SHORT).show();

        if(hasFront)
        {
            iSwitcher.setImageURI(uriFront);
            frontShowing = true;
        }
        else if(hasBack)
        {
            iSwitcher.setImageURI(uriBack);
            frontShowing = false;
        }
        else
        {
            Toast.makeText(this, R.string.card_no_image, Toast.LENGTH_SHORT).show();
        }
    }
    @Override
    public void onDestroy()
    {
        iSwitcher.setImageURI(null);
        super.onDestroy();
    }

    public View makeView() {
        ImageView iView = new ImageView(this);
        iView.setScaleType(ImageView.ScaleType.FIT_CENTER);
        iView.setLayoutParams(new ImageSwitcher.LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT));

        return iView;
    }


    protected OnClickListener SwitcherOnClick = new OnClickListener()
    {
        @Override
        public void onClick(View v)
        {
            if(frontShowing && hasBack)
            {
                iSwitcher.destroyDrawingCache();
                iSwitcher.setImageURI(uriBack);
                frontShowing = false;
            }
            else if(!frontShowing && hasFront)
            {
                iSwitcher.destroyDrawingCache();
                iSwitcher.setImageURI(uriFront);
                frontShowing = true;
            }
            else
            {

            }
        }       
    };


    private void getCardImageGuids(int cardId)
    {
        try
        {
            this.mDbHelper = new CardDBAdapter(this);
            this.mDbHelper.open();
            Cursor c = this.mDbHelper.fetchCard(this.cardId);


            _cardImgGuidFront = c.getString(c
                    .getColumnIndex(CardDBAdapter.CARD_IMG_GUID_FRONT));

            _cardImgGuidBack = c.getString(c
                    .getColumnIndex(CardDBAdapter.CARD_IMG_GUID_BACK));
        }
        catch(SQLiteException ex)
        {
            Toast.makeText(CardViewImageActivity.this, ex.toString(), Toast.LENGTH_LONG).show();
        }
        finally
        {
             this.mDbHelper.close();
             this.mDbHelper = null;
        }

    }
}

Вышеприведенный код, кажется, иногда работает довольно хорошо. Тем не менее, я иногда получаю ошибку OutOfMemory. Теперь, насколько я понимаю из-за отладки, makeView () вызывается дважды, когда вызывается .setFactory (this), и это нормально, поскольку цель ImageSwitcher - переключаться между двумя изображениями. Мне интересно, есть ли лучший способ для переключения изображений, кроме SetImageUri (). Я не вижу нигде, где я могу протекать или что может быть причиной проблемы. Я нигде не вижу, чтобы convertView мог даже использоваться. Есть ли способ кешировать изображения с Ури? Изображения загружаются каждый раз, когда вызывается .setImageUri ()? Есть ли способ сбросить эту память (или повторно использовать)? Это то, что съедает мою память?

Не звучит неуважительно или грубо, но я бы действительно предпочел помочь, если бы кто-то не ссылался на статью Как избежать утечек памяти или ссылок на javadocs для imageswitcher? Статья Избегать утечек памяти показывает пару «вы не должны этого делать», но никогда не показывает, что вы «должны» делать вместо этого. У меня уже есть ссылки на Javadocs. Я ищу кого-то, кто мог бы на самом деле объяснить, что я делаю неправильно, и направить меня в лучшем направлении с помощью кода (я изучаю код лучше, чем смутные абстрактные академические теории), а не просто извергать топ-3 ссылок из поиска Google , :)

Спасибо за любую помощь! :)

EDIT: 10Feb2012 Поэтому я попытался загрузить Drawables, и НЕМЕДЛЕННО получил ошибку нехватки памяти, которая является БОЛЬШЕ, чем получение ошибки иногда с помощью .setImageUri (). Следующее содержит моды:

private Drawable front;
private Drawable back;

@Override
    public void onCreate(Bundle savedInstanceState) {

...
        Resources res = getResources();

...
        if(_cardImgGuidFront != null)
        {
            hasFront = true;
            String frontPath = Environment.getExternalStorageDirectory().toString() + "/" + _cardImgGuidFront + ".jpg";
            front = new BitmapDrawable(res, frontPath);
        }

        if(_cardImgGuidBack != null)
        {
            hasBack = true;
            String backPath = Environment.getExternalStorageDirectory().toString() + "/" + _cardImgGuidBack + ".jpg";
            back = new BitmapDrawable(res, backPath);
        }

Глядя на SoftReference, использование требует создания SoftReference с использованием другого drawable. Я не понимаю, как использование SoftReference могло бы помочь, так как я сейчас сбой при начальной загрузке.

Ответы [ 3 ]

2 голосов
/ 03 апреля 2012
ImageView v = (ImageView)imageSwitcher.getNextView(); 
BitmapDrawable bd = (BitmapDrawable) v.getDrawable();
if (bd != null) 
{
    Bitmap b = bd.getBitmap();
    b.recycle();
}
0 голосов
/ 10 февраля 2012

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

import android.app.Activity;
import android.content.res.Resources;
import android.database.Cursor;
import android.database.sqlite.SQLiteException;
import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Environment;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup.LayoutParams;
import android.widget.ImageSwitcher;
import android.widget.ImageView;
import android.widget.Toast;
import android.widget.ViewSwitcher.ViewFactory;

public class CardViewImageActivity extends Activity implements ViewFactory {

    private CardDBAdapter mDbHelper;

    private String _cardImgGuidFront;
    private String _cardImgGuidBack;
    private Boolean frontShowing = false;
    private Boolean hasFront = false;
    private Boolean hasBack = false;

    private Drawable front;
    private Drawable back;

    private int cardId;

    private ImageSwitcher iSwitcher;
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.cert_view_image);

        iSwitcher = (ImageSwitcher)findViewById(R.id.imageSwitcher);
        iSwitcher.setFactory(this);
        iSwitcher.setOnClickListener(SwitcherOnClick);
        Resources res = getResources();
        BitmapFactory.Options o = new BitmapFactory.Options();
        o.inSampleSize = 2;

        this.cardId = Integer.parseInt(getIntent().getExtras().getString(
                "cardId")); //$NON-NLS-1$

        getCardImageGuids(this.cardId);

        if(_cardImgGuidFront != null)
        {
            hasFront = true;
            String frontPath = Environment.getExternalStorageDirectory().toString() + "/" + _cardImgGuidFront + ".jpg";
            front = new BitmapDrawable(res, BitmapFactory.decodeFile(frontPath, o));
        }

        if(_cardImgGuidBack != null)
        {
            hasBack = true;
            String backPath = Environment.getExternalStorageDirectory().toString() + "/" + _cardImgGuidBack + ".jpg";
            back = new BitmapDrawable(res, BitmapFactory.decodeFile(backPath, o));
        }
        if(hasFront && hasBack)
            Toast.makeText(this, R.string.card_view_touch, Toast.LENGTH_SHORT).show();

        if(hasFront)
        {
            iSwitcher.setImageDrawable(front);
            frontShowing = true;
        }
        else if(hasBack)
        {
            iSwitcher.setImageDrawable(back);
            frontShowing = false;       }
        else
        {
            Toast.makeText(this, R.string.card_no_image, Toast.LENGTH_SHORT).show();
        }
        res = null;
    }
    @Override
    public void onPause()
    {
        super.onPause();
    }
    @Override
    public void onDestroy()
    {
        front = null;
        back = null;
        super.onDestroy();
    }

    public View makeView() {
        ImageView iView = new ImageView(this);
        iView.setScaleType(ImageView.ScaleType.FIT_CENTER);
        iView.setLayoutParams(new ImageSwitcher.LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT));

        return iView;
    }


    protected OnClickListener SwitcherOnClick = new OnClickListener()
    {
        @Override
        public void onClick(View v)
        {
            if(frontShowing && hasBack)
            {
                iSwitcher.setImageDrawable(back);
                frontShowing = false;
            }
            else if(!frontShowing && hasFront)
            {
                iSwitcher.setImageDrawable(front);
                frontShowing = true;
            }
            else
            {

            }
        }       
    };


    private void getCardImageGuids(int cardId)
    {
        ...
        // Put your db logic retrieval for the img id here

    }
}

Я надеюсь, что это решение (и ФАКТИЧЕСКИЙ код) поможет кому-то еще.

0 голосов
/ 10 февраля 2012

Мне интересно, есть ли лучший способ переключения изображений, кроме SetImageUri ().

Вызовите setImageDrawable () с использованием кэшированного BitmapDrawable.

Я нигде не вижу, что у меня может быть утечка или что может быть причиной проблемы.

Используйте DDMS и MAT , чтобы увидеть, где находятся ваши утечки.

Я нигде не вижу возможности использования convertView.

Учитывая, что в вашем исходном коде нет ничего с именем convertView, это неудивительно.

Есть ли способ кеширования изображений из Uri?

Да. Используйте BitmapFactory, загрузите изображения самостоятельно. Кэшируйте результаты, предпочтительно используя SoftReferences. Вызовите recycle() на Bitmap объектах, когда они вам больше не нужны.

Загружаются ли изображения каждый раз, когда вызывается .setImageUri ()?

Да.

Есть ли способ сбросить эту память (или повторно использовать)?

Нет, если у вас есть Android, создайте Bitmap для вас. ImageSwitcher представляется односторонним API, в котором вы можете устанавливать изображения, но не получать их.

...