1

我似乎有内存泄漏,我不知道如何修复。我已经阅读了 android.com 上的所有 ImageSwitcher 教程和示例,但这些似乎都处理了已经在 drawables 文件夹中的 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 错误。现在,根据我通过调试的理解,当 .setFactory(this) 被调用时,makeView() 似乎被调用了两次,这似乎很好,因为 ImageSwitcher 的目的是在两个图像之间切换。我想知道除了 SetImageUri() 是否有更好的方法来切换图像。我看不到任何可能泄漏的地方或可能导致问题的原因。我看不到任何地方甚至可以使用 convertView。有没有办法缓存来自 Uri 的图像?每次调用 .setImageUri() 时是否都会重新加载图像?有没有办法转储该内存(或重用)?这就是吞噬我记忆的东西吗?

不要听起来不尊重或粗鲁,但我真的更喜欢帮助,而不需要有人参考避免内存泄漏文章或指向 imageswitcher 的 javadocs 的链接?避免内存泄漏文章显示了一些“您不应该这样做”,但从未显示您“应该”做什么。我已经有指向 javadocs 的链接。我正在寻找能够真正解释我做错了什么并用代码为我指明更好方向的人(我学习最好的代码而不是模糊的抽象学术理论),而不是仅仅从谷歌搜索中反刍前 3 个链接. :)

感谢您的任何帮助!:)

编辑: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。我看不出使用 SoftReference 有什么帮助,因为我现在在初始加载时崩溃了。

4

3 回答 3

2
ImageView v = (ImageView)imageSwitcher.getNextView(); 
BitmapDrawable bd = (BitmapDrawable) v.getDrawable();
if (bd != null) 
{
    Bitmap b = bd.getBitmap();
    b.recycle();
}
于 2012-04-03T11:47:03.457 回答
0

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

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

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

使用DDMS 和 MAT查看泄漏位置。

我看不到任何地方甚至可以使用 convertView。

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

有没有办法缓存来自 Uri 的图像?

是的。使用BitmapFactory,自己加载图像。缓存结果,最好使用SoftReferences. 不再需要对象时recycle()调用它们。Bitmap

每次调用 .setImageUri() 时是否都会重新加载图像?

是的。

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

如果您有 AndroidBitmap为您创建,则不会。ImageSwitcher似乎是一种单向 API,您可以在其中设置图像但不能检索它们。

于 2012-02-10T13:51:43.493 回答
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

    }
}

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

于 2012-02-10T18:22:06.063 回答