【问题标题】:How do you pass an image path between two activities without getting outofMemory error?如何在两个活动之间传递图像路径而不会出现 outofMemory 错误?
【发布时间】:2015-08-08 05:59:28
【问题描述】:

所以基本上,用户点击一个按钮,它会从图库中抓取一张图片。然后将该图像发送到另一个要显示的活动。这是我抓取图像的第一个活动。

private void grabImage()
{
    Intent imageGetter = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
    startActivityForResult(imageGetter, RESULT_LOAD_IMAGE);
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data)
{
    super.onActivityResult(requestCode, resultCode, data);
    if(requestCode == RESULT_LOAD_IMAGE && resultCode == RESULT_OK && null != data)
    {
        Uri selectedImage = data.getData();
        String[] filePathColumn = {MediaStore.Images.Media.DATA};//Array size of 1, and we put in a string
        Cursor cursor = getContentResolver().query(selectedImage, filePathColumn, null, null, null);
        cursor.moveToFirst();
        int columnIndex = cursor.getColumnIndex(filePathColumn[0]);
        user_image_path = cursor.getString(columnIndex);//here we have our image path.
        cursor.close();
        ImageView imageView = (ImageView) findViewById(R.id.imageView);
        imageView.setImageBitmap(BitmapFactory.decodeFile(user_image_path));
    }

    Intent theIntent = new Intent(this,CardModel.class);
    theIntent.putExtra("imagePath", user_image_path);
}

现在这是我尝试显示该图像的第二个活动。

  @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.card_model_layout);
        String grabImagePath = getIntent().getStringExtra("imagePath");
        ImageView img = (ImageView) findViewById(R.id.userImage);
        img.setImageBitmap(BitmapFactory.decodeFile(grabImagePath));

    }

我不断收到 OutOfMemoryError 错误,我不想添加: android:largeHeap="true" 来解决问题。

谁能给我一个简洁的示例(包含所有代码),说明如何在两个活动之间正确传递图像(使用其字符串路径)。我相信很多开发人员都可以从中受益。谢谢!

还有一种方法可以在不调用 onActivityResult 的情况下做到这一点,而只需让你自己的方法并将代码放在那里并在 onCreate() 方法中调用它。

【问题讨论】:

  • 您是否曾在 Android 开发者 youtube 频道观看过此视频? youtube.com/… ?
  • 安德烈,是的,我刚刚看了,我从没想过执行这个动作会这么复杂......但仍然试图正确地实现它......

标签: android android-intent android-studio


【解决方案1】:

事实是——

鉴于您的内存有限,理想情况下您只需要 在内存中加载较低分辨率的版本。较低的分辨率 版本应该与显示它的 UI 组件的大小相匹配。一个 分辨率更高的图像不会提供任何可见的好处, 但仍会占用宝贵的内存并产生额外的性能 由于额外的动态缩放而产生的开销。

[参考链接:http://developer.android.com/training/displaying-bitmaps/load-bitmap.html]

这就是您需要缩小图像的原因。希望以下代码对您有所帮助!

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;

public class BitmapUtility {

    public static Bitmap decodeSampledBitmapFromResource(String path,int reqWidth, int reqHeight) {

        // First decode with inJustDecodeBounds=true to check dimensions
        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeFile(path,options);

        // Calculate inSampleSize
        options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);

        // Decode bitmap with inSampleSize set
        options.inJustDecodeBounds = false;
        return BitmapFactory.decodeFile(path, options);
    }
    private static int calculateInSampleSize(
            BitmapFactory.Options options, int reqWidth, int reqHeight) {
        // Raw height and width of image
        final int height = options.outHeight;
        final int width = options.outWidth;
        int inSampleSize = 1;

        if (height > reqHeight || width > reqWidth) {

            final int halfHeight = height / 2;
            final int halfWidth = width / 2;

            // Calculate the largest inSampleSize value that is a power of 2 and keeps both
            // height and width larger than the requested height and width.
            while ((halfHeight / inSampleSize) > reqHeight
                    && (halfWidth / inSampleSize) > reqWidth) {
                inSampleSize *= 2;
            }
        }

        return inSampleSize;
    }
}

您可以使用上面的代码如下: 而不是使用:

 imageView.setImageBitmap(BitmapFactory.decodeFile(user_image_path));

你可以使用:

imageView.setImageBitmap(BitmapUtilty.decodeSampledBitmapFromResource("path/to/image",300,400)

【讨论】:

  • 感谢 Vijay,但我如何创建此类的实例并在我的两个活动中正确调用它?
  • 我将如何在我的主要活动课程和第二个活动中使用这两个课程?你介意给我看一个实现例子吗?干杯~
  • 这是正确的恕我直言,添加了 +1 但这还不是全部。将在下面添加更多内容。
  • 嘿 Vijay,所以我使用了上面的类并使用了 setImageBitmap(Bitmaputility...),我没有出现内存不足错误,但是第二个活动的屏幕上没有显示图像?
  • 实际上 nvm 是我的错字,它工作正常。谢谢。
【解决方案2】:

对于图像(尤其是来自您无法控制的来源,例如互联网或用户数据),您不能只加载它们。它可能是巨大的。上一个答案显示了如何正确地做到这一点。

另一件需要注意的事情是回收图像数据占用的本机内存。出于某种原因,当 OOM 已经存在时,它不会自动回收或为时已晚。要做到这一点,应该维护引用计数,这需要扩展几个类。这就是我解决问题的方法。我想我使用了一些来自 android 教程的代码并对其进行了一些扩展。不管怎样,那是几年前的事了,所以我不太确定)在这里。

package com.companyname.myapp.ui.cache;

import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.util.Log;

import com.companyname.myapp.MyApp;
import com.companyname.myapp.BuildConfig;

/**
 * A BitmapDrawable that keeps track of whether it is being displayed or cached.
 * When the drawable is no longer being displayed or cached,
 * {@link Bitmap#recycle() recycle()} will be called on this drawable's bitmap.
 */
public class RecyclingBitmapDrawable extends BitmapDrawable {
    private int cacheRefCount = 0;
    private int displayRefCount = 0;

    private boolean hasBeenDisplayed;

    public RecyclingBitmapDrawable(Resources res, Bitmap bitmap) {
        super(res, bitmap);
    }

    /**
     * Notify the drawable that the displayed state has changed. Internally a
     * count is kept so that the drawable knows when it is no longer being
     * displayed.
     * 
     * @param isDisplayed
     *            - Whether the drawable is being displayed or not
     */
    public void setIsDisplayed(boolean isDisplayed) {
        synchronized (this) {
            if (isDisplayed) {
                displayRefCount++;
                hasBeenDisplayed = true;
            } else {
                displayRefCount--;
            }
        }

        // Check to see if recycle() can be called
        checkState();
    }

    /**
     * Notify the drawable that the cache state has changed. Internally a count
     * is kept so that the drawable knows when it is no longer being cached.
     * 
     * @param isCached
     *            - Whether the drawable is being cached or not
     */
    public void setIsCached(boolean isCached) {
        synchronized (this) {
            if (isCached) {
                cacheRefCount++;
            } else {
                cacheRefCount--;
            }
        }

        // Check to see if recycle() can be called
        checkState();
    }

    private synchronized void checkState() {
        // If the drawable cache and display ref counts = 0, and this drawable
        // has been displayed, then recycle
        if (cacheRefCount <= 0 && displayRefCount <= 0 && hasBeenDisplayed && hasValidBitmap()) {
            if (BuildConfig.DEBUG)
                Log.d(MyApp.TAG, "No longer being used or cached so recycling. " + toString());
            getBitmap().recycle();
        }
    }

    private synchronized boolean hasValidBitmap() {
        Bitmap bitmap = getBitmap();
        return bitmap != null && !bitmap.isRecycled();
    }

}

这又是一门课。

package com.mycompanyname.myapp.ui.cache;

import android.content.Context;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.util.AttributeSet;
import android.widget.ImageView;

/**
 * Sub-class of ImageView which automatically notifies the drawable when it is
 * being displayed.
 */
public class RecyclingImageView extends ImageView {
    public RecyclingImageView(Context context) {
        super(context);
    }

    public RecyclingImageView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    /**
     * @see android.widget.ImageView#onDetachedFromWindow()
     */
    @Override
    protected void onDetachedFromWindow() {
        // This has been detached from Window, so clear the drawable
        setImageDrawable(null);

        super.onDetachedFromWindow();
    }

    /**
     * @see android.widget.ImageView#setImageDrawable(android.graphics.drawable.Drawable)
     */
    @Override
    public void setImageDrawable(Drawable drawable) {
        // Keep hold of previous Drawable
        final Drawable previousDrawable = getDrawable();

        // Call super to set new Drawable
        super.setImageDrawable(drawable);

        // Notify new Drawable that it is being displayed
        notifyDrawable(drawable, true);

        // Notify old Drawable so it is no longer being displayed
        notifyDrawable(previousDrawable, false);
    }

    @Override
    public void setImageResource(int resId) {
        // Keep hold of previous Drawable
        final Drawable previousDrawable = getDrawable();
        super.setImageResource(resId);

        // Notify new Drawable that it is being displayed
        final Drawable newDrawable = getDrawable();
        notifyDrawable(newDrawable, true);

        // Notify old Drawable so it is no longer being displayed
        notifyDrawable(previousDrawable, false);
    }

    /**
     * Notifies the drawable that it's displayed state has changed.
     * 
     * @param drawable
     * @param isDisplayed
     */
    private static void notifyDrawable(Drawable drawable, final boolean isDisplayed) {
        if (drawable != null) {
            if (drawable instanceof RecyclingBitmapDrawable) {
                // The drawable is a CountingBitmapDrawable, so notify it
                ((RecyclingBitmapDrawable) drawable).setIsDisplayed(isDisplayed);
            } else if (drawable instanceof LayerDrawable) {
                // The drawable is a LayerDrawable, so recurse on each layer
                LayerDrawable layerDrawable = (LayerDrawable) drawable;
                for (int i = 0, z = layerDrawable.getNumberOfLayers(); i < z; i++) {
                    notifyDrawable(layerDrawable.getDrawable(i), isDisplayed);
                }
            }
        }
    }

}

现在在您的资源文件中(活动或其他)。假设这是用户个人资料头像。我看到有时它可能是几 MB 或更多)

<com.mycompanyname.myapp.ui.cache.RecyclingImageView
                    android:id="@+id/profile_icon"
                    android:layout_width="120dp"
                    android:layout_height="120dp"
                    android:adjustViewBounds="true"
                    android:contentDescription="@string/dummy_desc"
                    android:scaleType="centerCrop"
                    android:src="@drawable/icon_avatar_placeholder" />

如果您需要在应用中加载大量图片(比如说,它是用户个人资料及其头像的列表),那么您还需要某种图片缓存。不确定你现在是否需要它。如果您确实需要它,只需在 stackoverflow 上联系我,我会添加更多内容。

希望这会有所帮助。

【讨论】:

  • 感谢添加内存回收
  • 欢迎。我准备了一些课程,因为我已经遇到了这个问题。让我扩展答案并添加一些代码。
  • @Umka,所以我只是迷失在哪里/如何在我的活动中从这两个类创建一个实例(从电话库发送和获取图像)?如果你能给我一个很棒的例子!谢谢。
  • Umka 和 Vijay,我有没有办法在不倒置的情况下抓取图像?所以在我的第一个活动中,我从我的画廊中抓取图像,但它一直在横向反转它。我该如何阻止它这样做?
  • @TheQ,一旦您将其添加到活动的资源文件中,它就会自动创建。你所要求的其余部分基本上是自己做你的工作。恕我直言,这个网站是关于解决问题的讨论概念,而不是帮助创建开箱即用的解决方案)
猜你喜欢
  • 2015-03-10
  • 1970-01-01
  • 1970-01-01
  • 2021-05-30
  • 2012-08-21
  • 2017-08-05
  • 1970-01-01
  • 2013-05-12
相关资源
最近更新 更多