Android ImageSwitcher:setImageURI时内存不足错误

时间:2012-02-10 08:57:06

标签: android android-image android-view imageswitcher

我似乎有内存泄漏,我不知道如何修复。我已经阅读了android.com上的所有ImageSwitcher教程和示例,但这些似乎都处理了drawables文件夹中已有的drawable。我的代码允许用户用他们的相机拍摄一张或两张照片,将图像存储到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错误。现在,从我通过调试的理解,当调用.setFactory(this)时,makeView()似乎被调用了两次,这看起来很好,因为ImageSwitcher的目的是在两个图像之间切换。我想知道除了SetImageUri()之外是否有更好的方法来切换图像。我没有看到任何可能泄漏的地方或可能导致问题的原因。我没有看到convertView甚至可以被利用的任何地方。有没有办法从Uri缓存图像?是否每次重新加载图像.setImageUri()被调用?有没有办法转储内存(或重用)?这是什么让我记忆犹新?

不要听起来不尊重或粗鲁,但如果没有人引用 避免内存泄漏 文章或链接到imageswitcher的javadoc,我真的更喜欢帮助? 避免内存泄漏 文章显示了几个"你不应该这样做"但从来没有表明你应该"应该"做而不是。我已经有了javadocs的链接。我正在寻找可以解释我做错的事情的人,并指出我在代码方面的更好方向(我学习最好的代码而不是模糊的抽象学术理论),而不仅仅是回流前3个链接来自谷歌搜索。 :)

感谢您的帮助! :)

编辑:10Feb2012 所以我试图加载Drawables并且IMMEDIATELY收到了一个内存不足错误,这个错误比偶尔使用.setImageUri()获得错误。以下包含mods:

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,使用需要使用另一个drawable创建SoftReference。我不知道如何使用SoftReference可能会有所帮助,因为我现在正在崩溃初始负载。

3 个答案:

答案 0 :(得分:2)

ImageView v = (ImageView)imageSwitcher.getNextView(); 
BitmapDrawable bd = (BitmapDrawable) v.getDrawable();
if (bd != null) 
{
    Bitmap b = bd.getBitmap();
    b.recycle();
}

答案 1 :(得分:0)

  

我想知道除了SetImageUri()之外是否有更好的方法来切换图像。

使用缓存的BitmapDrawable调用setImageDrawable()。

  

我没有看到任何可能泄漏的地方或可能导致问题的原因。

使用DDMS and MAT查看泄漏的位置。

  

我没有看到convertView甚至可以被利用的任何地方。

考虑到源代码中没有任何名为convertView的内容,这并不奇怪。

  

有没有办法从Uri缓存图像?

是。使用BitmapFactory,自行加载图片。缓存结果,最好使用SoftReferences。 recycle()对象上不再需要时调用Bitmap

  

每次重新加载图像.setImageUri()被调用吗?

  

有没有办法转储内存(或重用)?

如果您让Android为您创建Bitmap,则不会。 ImageSwitcher似乎是单向API,您可以在其中设置图片但不能检索图片。

答案 2 :(得分:0)

好的,所以这似乎有效,我将提供代码,以便为其他对此感到慌乱的人保存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

    }
}

我希望这个解决方案(和ACTUAL代码)可以帮助其他人。