问题
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.
回答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)
回答2:
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.
来源:https://stackoverflow.com/questions/31890156/how-do-you-pass-an-image-path-between-two-activities-without-getting-outofmemory