如何在两个活动之间传递图像路径而不会出现内存错误?

时间:2015-08-08 05:59:29

标签: android android-intent android-studio

基本上,用户点击一个按钮就可以从图库中抓取图像。然后该图像被发送到另一个要显示的活动。这是我抓住图像的第一个活动。

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()方法中调用它。

2 个答案:

答案 0 :(得分:1)

事实是 -

  

鉴于你正在使用有限的内存,理想情况下你只想要   在内存中加载较低分辨率的版本。分辨率较低   version应与显示它的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)

答案 1 :(得分:0)

使用图片(特别是来自您无法控制的来源,如互联网或用户数据),您无法加载它们。这可能是巨大的。以前的答案显示了如何正确地做到这一点。

要注意的另一件事是回收图像数据占用的本机内存。由于某些原因,当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上ping我,我会添加更多。

希望这有帮助。