【问题标题】:Android ImageSwitcher: Out Of Memory Error when setImageURIAndroid ImageSwitcher:setImageURI 时出现内存不足错误
【发布时间】:2012-02-10 08:57:05
【问题描述】:

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

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

感谢您的帮助! :)

编辑:2012 年 2 月 10 日 因此,我尝试加载 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 有什么帮助,因为我现在在初始加载时崩溃了。

【问题讨论】:

    标签: android android-image android-view imageswitcher


    【解决方案1】:
    ImageView v = (ImageView)imageSwitcher.getNextView(); 
    BitmapDrawable bd = (BitmapDrawable) v.getDrawable();
    if (bd != null) 
    {
        Bitmap b = bd.getBitmap();
        b.recycle();
    }
    

    【讨论】:

      【解决方案2】:

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

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

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

      使用DDMS and MAT 查看您的漏洞在哪里。

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

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

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

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

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

      是的。

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

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

      【讨论】:

      • 感谢您的回复。你是对的。我在上面没有 convertView,但我说我什至看不到在这种情况下如何使用回收视图(convertView)。我读到将位图保存在内存中可能会占用大量内存。我会给 BitmapFactory 一个机会,看看是否有帮助,尤其是使用 Wea​​kReference/SoftReference。干杯!
      【解决方案3】:

      好的,所以这似乎可行,我将提供代码来为其他对此感到困惑的人节省 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
      
          }
      }
      

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2013-03-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-01-12
        • 1970-01-01
        • 2016-10-10
        • 2017-06-21
        相关资源
        最近更新 更多