0

So basically, a user click a button and it grabs an image from the gallery. Then that image is sent to another activity to be displayed. This is my first activity where I grab the image.

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);
}

Now this is my second activity that tries to display that image.

  @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));

    }

I keep getting OutOfMemoryError error and I don't wanna add: android:largeHeap="true" to solve the problem.

Can someone give me a clean cut example (with all the code) of how to properly pass an image (using its string path) between two activities. I'm sure a lot of developers could benefit from this. Thanks!

Also is there a way to do this without calling onActivityResult and instead just make you're own method and put the code in there and call that in the onCreate() method.

TheQ
  • 1,949
  • 10
  • 38
  • 63
  • Have you ever watched this video from the Android Developers youtube channel ? https://www.youtube.com/watch?v=HY9aaXHx8yA&list=PLWz5rJ2EKKc9CBxr3BVjPTPoDPLdPIFCE&index=28 ? – André Oriani Aug 08 '15 at 06:17
  • Andre, yeah i just watched it and i never thought it would be that complicated to perform this action...but still trying to implement this correctly.. – TheQ Aug 08 '15 at 06:55

2 Answers2

1

The fact is-

Given that you are working with limited memory, ideally you only want to load a lower resolution version in memory. The lower resolution version should match the size of the UI component that displays it. An image with a higher resolution does not provide any visible benefit, but still takes up precious memory and incurs additional performance overhead due to additional on the fly scaling.

[Reference Link : http://developer.android.com/training/displaying-bitmaps/load-bitmap.html]

That's why you need to scale down images . Hope, following code will help you !

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;
    }
}

You can use above code as follows: Instead of using:

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

you can use:

imageView.setImageBitmap(BitmapUtilty.decodeSampledBitmapFromResource("path/to/image",300,400)
VIjay J
  • 736
  • 7
  • 14
  • Thanks Vijay, but how do I make an instance of this class and call it properly within my two activities? – TheQ Aug 08 '15 at 06:55
  • How would i use these two classes in my main activity class and then in my second activity? would you mind showing me an implementation example? cheers~ – TheQ Aug 08 '15 at 07:04
  • This is correct IMHO, added +1 but this is not yet all. Will add more below. – Cynichniy Bandera Aug 08 '15 at 07:41
  • Hey Vijay, so I used the above class and used setImageBitmap(Bitmaputility...) and I dont have the outofmemory error but the image isn't being displayed on the screen for the second activity? – TheQ Aug 08 '15 at 10:04
  • Actually nvm it was a typo by me it works fine. Thanks. – TheQ Aug 08 '15 at 21:17
0

With images (especially from sources you don't control, like internet or user data) you cannot just load them. It may be huge. Previous answer shows how to do it right.

Another thing to take care about is recycling native memory occupied by image data. For some reason it does not get recycled automatically or does it too late, when OOM is already here. To do this right one should maintain reference counting, which requires some work with extending couple classes. This is how I solved the issue. I think I used some code from android tutorial and extended it a bit. Anyways, it was couple years ago so I'm not really sure ) Here it is.

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();
    }

}

This is one more class.

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);
                }
            }
        }
    }

}

Now in your resource file (activity or else). Let's say this is user profile avatar. I saw sometimes it may be couple MB or more )

<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" />

In case you need to load lots of images in your app (let's say, it is list of user profiles with their avatars) then you also need some kind of image cache. Not sure you need it right now. In case you do need it, just ping me here on stackoverflow and I will add more.

Hope this helps.

Cynichniy Bandera
  • 5,991
  • 2
  • 29
  • 33
  • Thanks for adding about recycling of memory – VIjay J Aug 08 '15 at 08:34
  • Welcome. I have some classes ready because I faced this issue already. Let me extend the answer and add some code. – Cynichniy Bandera Aug 08 '15 at 10:39
  • @Umka, so I'm just lost at where/how i can create an instance from those 2 classes in my activities (to send and grab the image from phone gallery)? if you can give me an example that would be awesome! thanks. – TheQ Aug 08 '15 at 20:13
  • Umka and Vijay, is there way for me to grab the image without having it inverted? So in my first activity, I'm grabbing the image from my gallery, but it keeps inverting it sideways. How do I stop it from doing that? – TheQ Aug 08 '15 at 21:19
  • @TheQ, Its created automatically once you added it to the resource file of your activity. The rest you are asking is basically to do your job myself. IMHO this site is about discussion concepts of solving the issues rather than helping with creating out-of-the-box solutions ) – Cynichniy Bandera Aug 09 '15 at 15:09