Canvas: trying to use a recycled bitmap android.graphics.Bitmap in Android - android

I am working on the crop image class, but encounter a recycled bit map problem:
03-02 23:14:10.514: E/AndroidRuntime(16736): FATAL EXCEPTION: Thread-1470
03-02 23:14:10.514: E/AndroidRuntime(16736): java.lang.RuntimeException: Canvas: trying to use a recycled bitmap android.graphics.Bitmap#428e5450
03-02 23:14:10.514: E/AndroidRuntime(16736): at android.graphics.Canvas.throwIfRecycled(Canvas.java:1026)
03-02 23:14:10.514: E/AndroidRuntime(16736): at android.graphics.Canvas.drawBitmap(Canvas.java:1096)
03-02 23:14:10.514: E/AndroidRuntime(16736): at android.graphics.Bitmap.createBitmap(Bitmap.java:604)
03-02 23:14:10.514: E/AndroidRuntime(16736): at eu.janmuller.android.simplecropimage.CropImage$1.prepareBitmap(CropImage.java:630)
03-02 23:14:10.514: E/AndroidRuntime(16736): at eu.janmuller.android.simplecropimage.CropImage$1.run(CropImage.java:636)
03-02 23:14:10.514: E/AndroidRuntime(16736): at eu.janmuller.android.simplecropimage.CropImage$6.run(CropImage.java:343)
03-02 23:14:10.514: E/AndroidRuntime(16736): at eu.janmuller.android.simplecropimage.Util$BackgroundJob.run(Util.java:175)
03-02 23:14:10.514: E/AndroidRuntime(16736): at java.lang.Thread.run(Thread.java:856)
The line that error occur is the mScale = 256.0F / mBitmap.getWidth(); (line 630) please search this
for more information.
Notice, the code do not has this error before I adding the checkRotation () function. And that function return a bitmap, and that bitmap has caused the exception. This is the hints
Also, in the function I have copied the original bitmap and recycle the old bitmap, so it should not be the root of problem, you are suggested not to look on the code one by one but search the keywords.
/**
* The activity can crop specific region of interest from an image.
*/
public class CropImage extends MonitoredActivity {
final int IMAGE_MAX_SIZE = 1024;
private static final String TAG = "CropImage";
public static final String IMAGE_PATH = "image-path";
public static final String SCALE = "scale";
public static final String ORIENTATION_IN_DEGREES = "orientation_in_degrees";
public static final String ASPECT_X = "aspectX";
public static final String ASPECT_Y = "aspectY";
public static final String OUTPUT_X = "outputX";
public static final String OUTPUT_Y = "outputY";
public static final String SCALE_UP_IF_NEEDED = "scaleUpIfNeeded";
public static final String CIRCLE_CROP = "circleCrop";
public static final String RETURN_DATA = "return-data";
public static final String RETURN_DATA_AS_BITMAP = "data";
public static final String ACTION_INLINE_DATA = "inline-data";
// These are various options can be specified in the intent.
private Bitmap.CompressFormat mOutputFormat = Bitmap.CompressFormat.JPEG;
private Uri mSaveUri = null;
private boolean mDoFaceDetection = true;
private boolean mCircleCrop = false;
private final Handler mHandler = new Handler();
private int mAspectX;
private int mAspectY;
private int mOutputX;
private int mOutputY;
private boolean mScale;
private CropImageView mImageView;
private ContentResolver mContentResolver;
private Bitmap mBitmap;
private String mImagePath;
boolean mWaitingToPick; // Whether we are wait the user to pick a face.
boolean mSaving; // Whether the "save" button is already clicked.
HighlightView mCrop;
// These options specifiy the output image size and whether we should
// scale the output to fit it (or just crop it).
private boolean mScaleUp = true;
private final BitmapManager.ThreadSet mDecodingThreads =
new BitmapManager.ThreadSet();
#Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
mContentResolver = getContentResolver();
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.cropimage);
mImageView = (CropImageView) findViewById(R.id.image);
showStorageToast(this);
Intent intent = getIntent();
Bundle extras = intent.getExtras();
if (extras != null) {
if (extras.getString(CIRCLE_CROP) != null) {
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.HONEYCOMB) {
mImageView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
}
mCircleCrop = true;
mAspectX = 1;
mAspectY = 1;
}
mImagePath = extras.getString(IMAGE_PATH);
mBitmap = checkRotation(mImagePath);
Log.d("test1",""+mBitmap.isRecycled());
if (extras.containsKey(ASPECT_X) && extras.get(ASPECT_X) instanceof Integer) {
mAspectX = extras.getInt(ASPECT_X);
} else {
throw new IllegalArgumentException("aspect_x must be integer");
}
if (extras.containsKey(ASPECT_Y) && extras.get(ASPECT_Y) instanceof Integer) {
mAspectY = extras.getInt(ASPECT_Y);
} else {
throw new IllegalArgumentException("aspect_y must be integer");
}
mOutputX = extras.getInt(OUTPUT_X);
mOutputY = extras.getInt(OUTPUT_Y);
mScale = extras.getBoolean(SCALE, true);
mScaleUp = extras.getBoolean(SCALE_UP_IF_NEEDED, true);
}
if (mBitmap == null) {
Log.d(TAG, "finish!!!");
finish();
return;
}
// Make UI fullscreen.
getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
findViewById(R.id.discard).setOnClickListener(
new View.OnClickListener() {
public void onClick(View v) {
setResult(RESULT_CANCELED);
finish();
}
});
findViewById(R.id.save).setOnClickListener(
new View.OnClickListener() {
public void onClick(View v) {
try {
onSaveClicked();
} catch (Exception e) {
finish();
}
}
});
findViewById(R.id.rotateLeft).setOnClickListener(
new View.OnClickListener() {
public void onClick(View v) {
mBitmap = Util.rotateImage(mBitmap, -90);
RotateBitmap rotateBitmap = new RotateBitmap(mBitmap);
mImageView.setImageRotateBitmapResetBase(rotateBitmap, true);
mRunFaceDetection.run();
}
});
findViewById(R.id.rotateRight).setOnClickListener(
new View.OnClickListener() {
public void onClick(View v) {
mBitmap = Util.rotateImage(mBitmap, 90);
RotateBitmap rotateBitmap = new RotateBitmap(mBitmap);
mImageView.setImageRotateBitmapResetBase(rotateBitmap, true);
mRunFaceDetection.run();
}
});
Log.d("test1","a "+mBitmap.isRecycled());
startFaceDetection();
}
private Bitmap checkRotation(String url){
mSaveUri = getImageUri(url);
ExifInterface exif;
try {
exif = new ExifInterface(url);
int rotation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED);
Matrix matrix = new Matrix();
switch (rotation) {
case ExifInterface.ORIENTATION_FLIP_HORIZONTAL:
matrix.setScale(-1, 1);
break;
case ExifInterface.ORIENTATION_ROTATE_180:
matrix.setRotate(180);
break;
case ExifInterface.ORIENTATION_FLIP_VERTICAL:
matrix.setRotate(180);
matrix.postScale(-1, 1);
break;
case ExifInterface.ORIENTATION_TRANSPOSE:
matrix.setRotate(90);
matrix.postScale(-1, 1);
break;
case ExifInterface.ORIENTATION_ROTATE_90:
matrix.setRotate(90);
break;
case ExifInterface.ORIENTATION_TRANSVERSE:
matrix.setRotate(-90);
matrix.postScale(-1, 1);
break;
case ExifInterface.ORIENTATION_ROTATE_270:
matrix.setRotate(-90);
break;
case ExifInterface.ORIENTATION_NORMAL:
default:
break;
}
Bitmap beforeRotate = getBitmap(url);
int height = beforeRotate.getHeight();
int width = beforeRotate.getWidth();
Bitmap afterRotate = Bitmap.createBitmap(beforeRotate, 0, 0, width, height, matrix, true);
beforeRotate.recycle();
return afterRotate;
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return mBitmap;
}
private Uri getImageUri(String path) {
return Uri.fromFile(new File(path));
}
private Bitmap getBitmap(String path) {
Uri uri = getImageUri(path);
InputStream in = null;
try {
in = mContentResolver.openInputStream(uri);
//Decode image size
BitmapFactory.Options o = new BitmapFactory.Options();
o.inJustDecodeBounds = true;
BitmapFactory.decodeStream(in, null, o);
in.close();
int scale = 1;
if (o.outHeight > IMAGE_MAX_SIZE || o.outWidth > IMAGE_MAX_SIZE) {
scale = (int) Math.pow(2, (int) Math.round(Math.log(IMAGE_MAX_SIZE / (double) Math.max(o.outHeight, o.outWidth)) / Math.log(0.5)));
}
BitmapFactory.Options o2 = new BitmapFactory.Options();
o2.inSampleSize = scale;
in = mContentResolver.openInputStream(uri);
Bitmap b = BitmapFactory.decodeStream(in, null, o2);
in.close();
return b;
} catch (FileNotFoundException e) {
Log.e(TAG, "file " + path + " not found");
} catch (IOException e) {
Log.e(TAG, "file " + path + " not found");
}
return null;
}
private void startFaceDetection() {
if (isFinishing()) {
return;
}
mImageView.setImageBitmapResetBase(mBitmap, true);
Util.startBackgroundJob(this, null,
"Please wait\u2026",
new Runnable() {
public void run() {
final CountDownLatch latch = new CountDownLatch(1);
final Bitmap b = mBitmap;
mHandler.post(new Runnable() {
public void run() {
if (b != mBitmap && b != null) {
Log.d("test1","test");
mImageView.setImageBitmapResetBase(b, true);
mBitmap.recycle();
mBitmap = b;
}
if (mImageView.getScale() == 1F) {
mImageView.center(true, true);
}
latch.countDown();
}
});
try {
latch.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
mRunFaceDetection.run();
}
}, mHandler);
}
private void onSaveClicked() throws Exception {
// TODO this code needs to change to use the decode/crop/encode single
// step api so that we don't require that the whole (possibly large)
// bitmap doesn't have to be read into memory
if (mSaving) return;
if (mCrop == null) {
return;
}
mSaving = true;
Rect r = mCrop.getCropRect();
int width = r.width();
int height = r.height();
// If we are circle cropping, we want alpha channel, which is the
// third param here.
Bitmap croppedImage;
try {
croppedImage = Bitmap.createBitmap(width, height,
mCircleCrop ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565);
} catch (Exception e) {
throw e;
}
if (croppedImage == null) {
return;
}
{
Canvas canvas = new Canvas(croppedImage);
Rect dstRect = new Rect(0, 0, width, height);
canvas.drawBitmap(mBitmap, r, dstRect, null);
}
if (mCircleCrop) {
// OK, so what's all this about?
// Bitmaps are inherently rectangular but we want to return
// something that's basically a circle. So we fill in the
// area around the circle with alpha. Note the all important
// PortDuff.Mode.CLEAR.
Canvas c = new Canvas(croppedImage);
Path p = new Path();
p.addCircle(width / 2F, height / 2F, width / 2F,
Path.Direction.CW);
c.clipPath(p, Region.Op.DIFFERENCE);
c.drawColor(0x00000000, PorterDuff.Mode.CLEAR);
}
/* If the output is required to a specific size then scale or fill */
if (mOutputX != 0 && mOutputY != 0) {
if (mScale) {
/* Scale the image to the required dimensions */
Bitmap old = croppedImage;
croppedImage = Util.transform(new Matrix(),
croppedImage, mOutputX, mOutputY, mScaleUp);
if (old != croppedImage) {
old.recycle();
}
} else {
/* Don't scale the image crop it to the size requested.
* Create an new image with the cropped image in the center and
* the extra space filled.
*/
// Don't scale the image but instead fill it so it's the
// required dimension
Bitmap b = Bitmap.createBitmap(mOutputX, mOutputY,
Bitmap.Config.RGB_565);
Canvas canvas = new Canvas(b);
Rect srcRect = mCrop.getCropRect();
Rect dstRect = new Rect(0, 0, mOutputX, mOutputY);
int dx = (srcRect.width() - dstRect.width()) / 2;
int dy = (srcRect.height() - dstRect.height()) / 2;
/* If the srcRect is too big, use the center part of it. */
srcRect.inset(Math.max(0, dx), Math.max(0, dy));
/* If the dstRect is too big, use the center part of it. */
dstRect.inset(Math.max(0, -dx), Math.max(0, -dy));
/* Draw the cropped bitmap in the center */
canvas.drawBitmap(mBitmap, srcRect, dstRect, null);
/* Set the cropped bitmap as the new bitmap */
croppedImage.recycle();
croppedImage = b;
}
}
// Return the cropped image directly or save it to the specified URI.
Bundle myExtras = getIntent().getExtras();
if (myExtras != null && (myExtras.getParcelable("data") != null
|| myExtras.getBoolean(RETURN_DATA))) {
Bundle extras = new Bundle();
extras.putParcelable(RETURN_DATA_AS_BITMAP, croppedImage);
setResult(RESULT_OK,
(new Intent()).setAction(ACTION_INLINE_DATA).putExtras(extras));
finish();
} else {
final Bitmap b = croppedImage;
Util.startBackgroundJob(this, null, getString(R.string.saving_image),
new Runnable() {
public void run() {
saveOutput(b);
}
}, mHandler);
}
}
private void saveOutput(Bitmap croppedImage) {
if (mSaveUri != null) {
OutputStream outputStream = null;
try {
outputStream = mContentResolver.openOutputStream(mSaveUri);
if (outputStream != null) {
croppedImage.compress(mOutputFormat, 90, outputStream);
}
} catch (IOException ex) {
Log.e(TAG, "Cannot open file: " + mSaveUri, ex);
setResult(RESULT_CANCELED);
finish();
return;
} finally {
Util.closeSilently(outputStream);
}
Bundle extras = new Bundle();
Intent intent = new Intent(mSaveUri.toString());
intent.putExtras(extras);
intent.putExtra(IMAGE_PATH, mImagePath);
intent.putExtra(ORIENTATION_IN_DEGREES, Util.getOrientationInDegree(this));
setResult(RESULT_OK, intent);
} else {
Log.e(TAG, "not defined image url");
}
croppedImage.recycle();
finish();
}
#Override
protected void onPause() {
super.onPause();
BitmapManager.instance().cancelThreadDecoding(mDecodingThreads);
}
#Override
protected void onDestroy() {
super.onDestroy();
if (mBitmap != null) {
mBitmap.recycle();
}
}
Runnable mRunFaceDetection = new Runnable() {
#SuppressWarnings("hiding")
float mScale = 1F;
Matrix mImageMatrix;
FaceDetector.Face[] mFaces = new FaceDetector.Face[3];
int mNumFaces;
// For each face, we create a HightlightView for it.
private void handleFace(FaceDetector.Face f) {
PointF midPoint = new PointF();
int r = ((int) (f.eyesDistance() * mScale)) * 2;
f.getMidPoint(midPoint);
midPoint.x *= mScale;
midPoint.y *= mScale;
int midX = (int) midPoint.x;
int midY = (int) midPoint.y;
HighlightView hv = new HighlightView(mImageView);
int width = mBitmap.getWidth();
int height = mBitmap.getHeight();
Rect imageRect = new Rect(0, 0, width, height);
RectF faceRect = new RectF(midX, midY, midX, midY);
faceRect.inset(-r, -r);
if (faceRect.left < 0) {
faceRect.inset(-faceRect.left, -faceRect.left);
}
if (faceRect.top < 0) {
faceRect.inset(-faceRect.top, -faceRect.top);
}
if (faceRect.right > imageRect.right) {
faceRect.inset(faceRect.right - imageRect.right,
faceRect.right - imageRect.right);
}
if (faceRect.bottom > imageRect.bottom) {
faceRect.inset(faceRect.bottom - imageRect.bottom,
faceRect.bottom - imageRect.bottom);
}
hv.setup(mImageMatrix, imageRect, faceRect, mCircleCrop,
mAspectX != 0 && mAspectY != 0);
mImageView.add(hv);
}
// Create a default HightlightView if we found no face in the picture.
private void makeDefault() {
HighlightView hv = new HighlightView(mImageView);
int width = mBitmap.getWidth();
int height = mBitmap.getHeight();
Rect imageRect = new Rect(0, 0, width, height);
// make the default size about 4/5 of the width or height
int cropWidth = Math.min(width, height) * 4 / 5;
int cropHeight = cropWidth;
if (mAspectX != 0 && mAspectY != 0) {
if (mAspectX > mAspectY) {
cropHeight = cropWidth * mAspectY / mAspectX;
} else {
cropWidth = cropHeight * mAspectX / mAspectY;
}
}
int x = (width - cropWidth) / 2;
int y = (height - cropHeight) / 2;
RectF cropRect = new RectF(x, y, x + cropWidth, y + cropHeight);
hv.setup(mImageMatrix, imageRect, cropRect, mCircleCrop,
mAspectX != 0 && mAspectY != 0);
mImageView.mHighlightViews.clear(); // Thong added for rotate
mImageView.add(hv);
}
// Scale the image down for faster face detection.
private Bitmap prepareBitmap() {
if (mBitmap == null) {
return null;
}
// 256 pixels wide is enough.
if (mBitmap.getWidth() > 256) {
mScale = 256.0F / mBitmap.getWidth();
}
Matrix matrix = new Matrix();
matrix.setScale(mScale, mScale);
return Bitmap.createBitmap(mBitmap, 0, 0, mBitmap.getWidth(), mBitmap.getHeight(), matrix, true);
}
public void run() {
mImageMatrix = mImageView.getImageMatrix();
Bitmap faceBitmap = prepareBitmap();
mScale = 1.0F / mScale;
if (faceBitmap != null && mDoFaceDetection) {
FaceDetector detector = new FaceDetector(faceBitmap.getWidth(),
faceBitmap.getHeight(), mFaces.length);
mNumFaces = detector.findFaces(faceBitmap, mFaces);
}
if (faceBitmap != null && faceBitmap != mBitmap) {
faceBitmap.recycle();
}
mHandler.post(new Runnable() {
public void run() {
mWaitingToPick = mNumFaces > 1;
if (mNumFaces > 0) {
for (int i = 0; i < mNumFaces; i++) {
handleFace(mFaces[i]);
}
} else {
makeDefault();
}
mImageView.invalidate();
if (mImageView.mHighlightViews.size() == 1) {
mCrop = mImageView.mHighlightViews.get(0);
mCrop.setFocus(true);
}
if (mNumFaces > 1) {
Toast.makeText(CropImage.this,
"Multi face crop help",
Toast.LENGTH_SHORT).show();
}
}
});
}
};
public static final int NO_STORAGE_ERROR = -1;
public static final int CANNOT_STAT_ERROR = -2;
public static void showStorageToast(Activity activity) {
showStorageToast(activity, calculatePicturesRemaining(activity));
}
public static void showStorageToast(Activity activity, int remaining) {
String noStorageText = null;
if (remaining == NO_STORAGE_ERROR) {
String state = Environment.getExternalStorageState();
if (state.equals(Environment.MEDIA_CHECKING)) {
noStorageText = activity.getString(R.string.preparing_card);
} else {
noStorageText = activity.getString(R.string.no_storage_card);
}
} else if (remaining < 1) {
noStorageText = activity.getString(R.string.not_enough_space);
}
if (noStorageText != null) {
Toast.makeText(activity, noStorageText, 5000).show();
}
}
public static int calculatePicturesRemaining(Activity activity) {
try {
/*if (!ImageManager.hasStorage()) {
return NO_STORAGE_ERROR;
} else {*/
String storageDirectory = "";
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state)) {
storageDirectory = Environment.getExternalStorageDirectory().toString();
}
else {
storageDirectory = activity.getFilesDir().toString();
}
StatFs stat = new StatFs(storageDirectory);
float remaining = ((float) stat.getAvailableBlocks()
* (float) stat.getBlockSize()) / 400000F;
return (int) remaining;
//}
} catch (Exception ex) {
// if we can't stat the filesystem then we don't know how many
// pictures are remaining. it might be zero but just leave it
// blank since we really don't know.
return CANNOT_STAT_ERROR;
}
}
}

Try to add this before calling recycle() methods to make sure bitmap isn't already recycled:
if (mBitmap != null && !mBitmap.isRecycled()) {
mBitmap.recycle();
mBitmap = null;
}

For those that did not find a solution so far. I had the same problem. I tried to recycle a bitmap in onPause like this:
final Drawable drawable = mImageView.getDrawable();
if (drawable instanceof BitmapDrawable) {
BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable;
Bitmap bitmap = bitmapDrawable.getBitmap();
bitmap.recycle();
}
if (preView != null && !preView.isRecycled()) {
preView.recycle();
preView = null;
}
After returning back i got the exception: Canvas: trying to use a recycled bitmap
Solution for me: I had to add the following
mImageView.setImageBitmap(null);

In my case error was caused because I changed visibility from gone to visible (or vice versa) of an element of the layout.
And as consequence the space for the imageview and the bitmap created changed, so recycling caused app to crash. Avoid this and your problem will be fix.

Android doesn't allows us reuse recycled Bitmap .just comment the bitmap.recycle() to resolve this error.
For more details click here

I have this error that happens randon, I can't reproduce it in a systematic way.
Use this custom ImageView class to fix it:
public class ImageViewExt extends AppCompatImageView {
private static final String TAG = ImageViewExt.class.getSimpleName();
public ImageViewExt(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public ImageViewExt(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ImageViewExt(Context context) {
super(context);
}
#Override
protected void onDraw(Canvas canvas) {
try {
super.onDraw(canvas);
} catch (Exception e) {
//Catch Canvas: trying to use a recycled bitmap
//e.printStackTrace();
}
}
}

In my case, i have inflate a layout containing imageview with src image where i have faced same error. That case, problem could be resolved by adding source image programmatically like:
((ImageView)view.findViewById(R.id.imageview)).setImageBitmap(BitmapFactory.decodeResource(getContext().getResources(),
R.drawable.testimage));

This should fix the issue in accordance to the android documentation here : https://developer.android.com/topic/performance/graphics/manage-memory#java
Here's the code just incase the link doesn't work .
private int cacheRefCount = 0;
private int displayRefCount = 0;
...
// Notify the drawable that the displayed state has changed.
// Keep a count to determine when the drawable is no longer displayed.
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.
// Keep a count to determine when the drawable is no longer being cached.
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()) {
getBitmap().recycle();
}
}
private synchronized boolean hasValidBitmap() {
Bitmap bitmap = getBitmap();
return bitmap != null && !bitmap.isRecycled();
}

for me the problem was i was using an arraylist to store all the bitmap values inorder to parse them into an adapter, what i was doing was that i was resizing the bitmap and then re orienting them ( the same bitmap file ) and adding them both in the arraylist
SOLUTION
Created a temp instance of Bitmap and resized it and then reoriented the temp one and stored the second one in the arraylist and parsed to the adapter

Probably the image/png files which you are using have bigger dimensions then 512pixels e.g width or height is greater than 512pixels.
Try to decrease the dimensions to at least 512 pixels. In my case I was also getting same error like E/BitmapDrawable: Canvas: trying to use a recycled bitmap.
By using above described way, I was able to solve the error in a day.
Keynote
512 pixels maybe not exact value for threshold, so take a look at your logcat too.

Just remove this line mBitmap.recycle(); and it will work.

The Fatal Exception: java.lang.RuntimeException error occurs when your application is trying to use a recycled bitmap. This error is caused by an attempt to use a bitmap that has already been recycled by the garbage collector.
For example:
#BindingAdapter({"restaurantLogo"})
public static void loadRestaurantLogo(CircleImageView imageView, String logoUrl) {
Glide.with(imageView.getContext())
.load(logoUrl)
.asBitmap()
.error(R.drawable.image_tmdone_logo_round)
.placeholder(R.drawable.image_tmdone_logo_round)
.into(new BitmapImageViewTarget(imageView) {
#Override
protected void setResource(Bitmap resource) {
RoundedBitmapDrawable circularBitmapDrawable = RoundedBitmapDrawableFactory.create(
imageView.getContext().getResources(), resource
);
circularBitmapDrawable.setCircular(true);
imageView.setImageDrawable(circularBitmapDrawable);
}
});
}
The setResource function - this is we are getting bitmap, but it was already recycled!
#Override
protected void setResource(Bitmap resource) {
RoundedBitmapDrawable circularBitmapDrawable = RoundedBitmapDrawableFactory.create(
imageView.getContext().getResources(), resource
);
circularBitmapDrawable.setCircular(true);
imageView.setImageDrawable(circularBitmapDrawable);
}
Here is the solution:
#BindingAdapter({"restaurantLogo"})
public static void loadRestaurantLogo(CircleImageView imageView, String logoUrl) {
Context context = imageView.getContext();
Resources resources = context.getResources();
Drawable defaultImage = ContextCompat.getDrawable(context, R.drawable.image_tmdone_logo_round);
try {
Glide.with(context)
.load(logoUrl)
.asBitmap()
.error(defaultImage)
.placeholder(defaultImage)
.into(new BitmapImageViewTarget(imageView) {
#Override
protected void setResource(Bitmap bitmap) {
if (!bitmap.isRecycled()) {
RoundedBitmapDrawable circularBitmapDrawable = RoundedBitmapDrawableFactory.create(resources, bitmap);
circularBitmapDrawable.setCircular(true);
imageView.setImageDrawable(circularBitmapDrawable);
} else {
// The bitmap is recycled, so we need to load it again before displaying it
loadRestaurantLogo(imageView, logoUrl);
}
}
});
} catch (Exception e) {
if (e.getMessage() != null && e.getMessage().contains("Canvas: trying to use a recycled bitmap")) {
loadRestaurantLogo(imageView, logoUrl);
} else {
Log.e("StoreObject", "Error loading restaurant logo", e);
}
}
}

Related

Image captured from camera shrink in size when displayed in ImageView using URL [duplicate]

This question already has answers here:
Capture Image from Camera and Display in Activity
(19 answers)
Closed 5 years ago.
I am new to android pls help me with it. Much appreciated. The problem is that, i get the image from camera, after it is being captured it will be display in the Imageview by getting captured image URL. however, when the image is display in the Imageview, the image is not of its actual size, it shrinks significantly. Could anyone help me with it, please.
The following is the code for cameraActivity:
public class CameraActivity extends AppCompatActivity {
Integer REQUEST_CAMERA =1, GALLERY_KITKAT_INTENT_CALLED=0;
public ImageView capturedPhoto;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_camera);
SelectImage();
capturedPhoto = (ImageView) findViewById(R.id.cameraPhoto);
}
private void SelectImage()
{
final CharSequence[] items ={"Camera","Gallery","Cancel"};
AlertDialog.Builder builder = new AlertDialog.Builder(CameraActivity.this);
builder.setTitle("Add Image");
builder.setItems(items, new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialogInterface, int i) {
if (items[i].equals("Camera")) {
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
startActivityForResult(intent, REQUEST_CAMERA);
//dispatchTakePictureIntent();
} else if (items[i].equals("Gallery")) {
Intent intent2 = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intent2.addCategory(Intent.CATEGORY_OPENABLE);
intent2.setType("image/*");
startActivityForResult(intent2, GALLERY_KITKAT_INTENT_CALLED);
} else if (items[i].equals("Cancel")) {
dialogInterface.dismiss();
}
}
});
builder.show();
}
#Override
public void onActivityResult(int requestCode,int resultCode, Intent data)
{
super.onActivityResult(requestCode,resultCode,data);
if(resultCode == Activity.RESULT_OK && requestCode == REQUEST_CAMERA)
{
Bitmap image =(Bitmap) data.getExtras().get("data");
capturedPhoto.setImageBitmap(image);
//call to get uri from bitmap
Uri tempUri = getImageUri(getApplicationContext(),image);
//call to get actual path
//Toast.makeText(CameraActivity.this,"Here"+getRealPathFromURI(tempUri),Toast.LENGTH_SHORT).show();
GlobalVariables.filepath=getRealPathFromURI(tempUri);
setContentView(new SomeView(CameraActivity.this));
}
if (requestCode == GALLERY_KITKAT_INTENT_CALLED && resultCode == RESULT_OK && null != data )
{
if (requestCode == GALLERY_KITKAT_INTENT_CALLED) {
String filepath2 = "";
Uri originalUri = null;
originalUri = data.getData();
final int takeFlags = data.getFlags()
& (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
// Check for the freshest data.
getContentResolver().takePersistableUriPermission(originalUri,
takeFlags);
filepath2 = getPath(originalUri);
if (filepath2.toString() != null) {
// LoadPicture(filepath);
GlobalVariables.filepath = filepath2;
// cropImageView.setVisibility(View.VISIBLE);
setContentView(new SomeView(CameraActivity.this));
}
}
}
}
#SuppressLint("NewApi")
private String getPath(Uri uri) {
if (uri == null) {
return null;
}
String[] projection = {MediaStore.Images.Media.DATA};
Cursor cursor;
if (Build.VERSION.SDK_INT > 19) {
// Will return "image:x*"
String wholeID = DocumentsContract.getDocumentId(uri);
// Split at colon, use second item in the array
String id = wholeID.split(":")[1];
// where id is equal to
String sel = MediaStore.Images.Media._ID + "=?";
cursor = getContentResolver().query(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI, projection,
sel, new String[]{id}, null);
} else {
cursor = getContentResolver().query(uri, projection, null, null,
null);
}
String path = null;
try {
int column_index = cursor
.getColumnIndex(MediaStore.Images.Media.DATA);
cursor.moveToFirst();
path = cursor.getString(column_index).toString();
cursor.close();
} catch (NullPointerException e) {
}
return path;
}
public Uri getImageUri(Context inContext, Bitmap image )
{
//ByteArrayOutputStream bytes = new ByteArrayOutputStream();
//thumbnail.compress(Bitmap.CompressFormat.JPEG,100,bytes);
String path = MediaStore.Images.Media.insertImage(inContext.getContentResolver(),image,"title",null);
return Uri.parse(path);
}
public String getRealPathFromURI(Uri uri)
{
Cursor cursor = getContentResolver().query(uri,null,null,null,null);
cursor.moveToFirst();
int idx=cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA);
return cursor.getString(idx);
}
}
The following is the xml file:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.mbdp.w.areametric.CameraActivity">
<ImageView
android:id="#+id/cameraPhoto"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</RelativeLayout>
This is the code for SomeView class:
public class SomeView extends View implements View.OnTouchListener{
private Paint paint;
public static List<Point> points;
int DIST = 2;
boolean flgPathDraw = true;
String filepath = GlobalVariables.filepath;
Point mfirstpoint = null;
boolean bfirstpoint = false;
Point mlastpoint = null;
Bitmap bitmap = scaleToActualAspectRatio(rotateBitmap(filepath,
BitmapFactory.decodeFile(filepath)));
Context mContext;
public SomeView(Context c) {
super(c);
Toast.makeText(getContext(), "Crop the coin on image", Toast.LENGTH_LONG)
.show();
mContext = c;
setFocusable(true);
setFocusableInTouchMode(true);
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setStyle(Paint.Style.STROKE);
paint.setPathEffect(new DashPathEffect(new float[]{10, 20}, 0));
paint.setStrokeWidth(5);
paint.setColor(Color.WHITE);
this.setOnTouchListener(this);
points = new ArrayList<Point>();
bfirstpoint = false;
}
public SomeView(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
setFocusable(true);
setFocusableInTouchMode(true);
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(2);
paint.setColor(Color.WHITE);
this.setOnTouchListener(this);
points = new ArrayList<Point>();
bfirstpoint = false;
}
public void onDraw(Canvas canvas) {
canvas.drawBitmap(bitmap, 0, 0, null);
Path path = new Path();
boolean first = true;
for (int i = 0; i < points.size(); i += 2) {
Point point = points.get(i);
if (first) {
first = false;
path.moveTo(point.x, point.y);
} else if (i < points.size() - 1) {
Point next = points.get(i + 1);
path.quadTo(point.x, point.y, next.x, next.y);
} else {
mlastpoint = points.get(i);
path.lineTo(point.x, point.y);
}
}
canvas.drawPath(path, paint);
}
public boolean onTouch(View view, MotionEvent event) {
Point point = new Point();
point.x = (int) event.getX();
point.y = (int) event.getY();
if (flgPathDraw) {
if (bfirstpoint) {
if (comparepoint(mfirstpoint, point)) {
// points.add(point);
points.add(mfirstpoint);
flgPathDraw = false;
showcropdialog();
} else {
points.add(point);
}
} else {
points.add(point);
}
if (!(bfirstpoint)) {
mfirstpoint = point;
bfirstpoint = true;
}
}
invalidate();
Log.e("Hi ==>", "Size: " + point.x + " " + point.y);
if (event.getAction() == MotionEvent.ACTION_UP) {
Log.d("Action up", "called");
mlastpoint = point;
if (flgPathDraw) {
if (points.size() > 12) {
if (!comparepoint(mfirstpoint, mlastpoint)) {
flgPathDraw = false;
points.add(mfirstpoint);
showcropdialog();
}
}
}
}
return true;
}
private boolean comparepoint(Point first, Point current) {
int left_range_x = (int) (current.x - 3);
int left_range_y = (int) (current.y - 3);
int right_range_x = (int) (current.x + 3);
int right_range_y = (int) (current.y + 3);
if ((left_range_x < first.x && first.x < right_range_x)
&& (left_range_y < first.y && first.y < right_range_y)) {
if (points.size() < 10) {
return false;
} else {
return true;
}
} else {
return false;
}
}
public void resetView() {
points.clear();
paint.setColor(Color.WHITE);
paint.setStyle(Paint.Style.STROKE);
flgPathDraw = true;
invalidate();
}
public Bitmap scaleToActualAspectRatio(Bitmap bitmap) {
if (bitmap != null) {
boolean flag = true;
DisplayMetrics metrics = Resources.getSystem().getDisplayMetrics();
int deviceWidth = metrics.widthPixels;
int deviceHeight = metrics.heightPixels;
Matrix matrix = new Matrix();
matrix.postRotate(90);
int bitmapHeight = bitmap.getHeight(); // 563
int bitmapWidth = bitmap.getWidth(); // 900
// aSCPECT rATIO IS Always WIDTH x HEIGHT rEMEMMBER 1024 x 768
if (bitmapWidth > deviceWidth) {
flag = false;
// scale According to WIDTH
int scaledWidth = deviceWidth;
int scaledHeight = (scaledWidth * bitmapHeight) / bitmapWidth;
try {
if (scaledHeight > deviceHeight)
scaledHeight = deviceHeight;
bitmap = Bitmap.createScaledBitmap(bitmap, scaledWidth,
scaledHeight, true);
} catch (Exception e) {
e.printStackTrace();
}
}
if (flag) {
if (bitmapHeight > deviceHeight) {
// scale According to HEIGHT
int scaledHeight = deviceHeight;
int scaledWidth = (scaledHeight * bitmapWidth)
/ bitmapHeight;
try {
if (scaledWidth > deviceWidth)
scaledWidth = deviceWidth;
bitmap = Bitmap.createScaledBitmap(bitmap, scaledWidth,
scaledHeight, true);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
return bitmap;
}
private void showcropdialog() {
DialogInterface.OnClickListener dialogClickListener = new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
Intent intent;
switch (which) {
case DialogInterface.BUTTON_NEUTRAL:
resetView();
break;
case DialogInterface.BUTTON_POSITIVE:
// Yes button clicked
// bfirstpoint = false;
intent = new Intent(mContext, CropActivity.class);
intent.putExtra("crop", true);
mContext.startActivity(intent);
break;
case DialogInterface.BUTTON_NEGATIVE:
// No button clicked
intent = new Intent(mContext, CropActivity.class);
intent.putExtra("crop", false);
mContext.startActivity(intent);
bfirstpoint = false;
// resetView();
break;
}
}
};
AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
builder.setMessage("Please select Crop or Inverse crop")
.setNeutralButton("Re-Crop", dialogClickListener)
.setPositiveButton("Crop", dialogClickListener)
.setNegativeButton("Inverse Crop", dialogClickListener).show()
.setCancelable(false);
}
public boolean onKeyDown(int keyCode, KeyEvent event) {
// Handle the back button
if (keyCode == KeyEvent.KEYCODE_BACK) {
// Ask the user if they want to quit
// restart app
Intent i = getContext().getPackageManager()
.getLaunchIntentForPackage(getContext().getPackageName());
i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
// startActionMode(i);
SomeView.this.getContext().startActivity(i);
return true;
} else {
return super.onKeyDown(keyCode, event);
}
}
public static Bitmap rotateBitmap(String src, Bitmap bitmap) {
try {
int orientation = getExifOrientation(src);
if (orientation == 1) {
return bitmap;
}
Matrix matrix = new Matrix();
switch (orientation) {
case 2:
matrix.setScale(-1, 1);
break;
case 3:
matrix.setRotate(180);
break;
case 4:
matrix.setRotate(180);
matrix.postScale(-1, 1);
break;
case 5:
matrix.setRotate(90);
matrix.postScale(-1, 1);
break;
case 6:
matrix.setRotate(90);
break;
case 7:
matrix.setRotate(-90);
matrix.postScale(-1, 1);
break;
case 8:
matrix.setRotate(-90);
break;
default:
return bitmap;
}
try {
Bitmap oriented = Bitmap.createBitmap(bitmap, 0, 0,
bitmap.getWidth(), bitmap.getHeight(), matrix, true);
bitmap.recycle();
return oriented;
} catch (OutOfMemoryError e) {
e.printStackTrace();
return bitmap;
}
} catch (IOException e) {
e.printStackTrace();
}
return bitmap;
}
private static int getExifOrientation(String src) throws IOException {
int orientation = 1;
try {
if (Build.VERSION.SDK_INT >= 5) {
Class<?> exifClass = Class
.forName("android.media.ExifInterface");
Constructor<?> exifConstructor = exifClass
.getConstructor(new Class[]{String.class});
Object exifInstance = exifConstructor
.newInstance(new Object[]{src});
Method getAttributeInt = exifClass.getMethod("getAttributeInt",
new Class[]{String.class, int.class});
Field tagOrientationField = exifClass
.getField("TAG_ORIENTATION");
String tagOrientation = (String) tagOrientationField.get(null);
orientation = (Integer) getAttributeInt.invoke(exifInstance,
new Object[]{tagOrientation, 1});
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
return orientation;
}
}
Links to image of what happened:
Actual image on camera
Displayed image in ImageView gets shrink
That small image from camera indicates thumbnail since you use reading bitmap from extra..
This example will help you get full image:  
How to capture an image and store it with the native Android Camera

Grow heap (frag case) to 8.837MB for 6220816-byte allocation error

I am trying to use 15 to 20 images to show on livewallpaper background one by one on a fix time ,but I am facing grow heap problem (Grow heap (frag case) to 8.987MB for 6220816-byte allocation) I have search everything but couldn't get through this
Any help pleas.
here is my code
public class LiveWallpaperService extends WallpaperService {
private float x;
private int y;
private int angle;
private int speed;
#Override
public void onCreate() {
super.onCreate();
}
#Override
public void onDestroy() {
super.onDestroy();
}
#Override
public Engine onCreateEngine() {
stopSelf();
return new MyWallpaperEngine();
}
class MyWallpaperEngine extends Engine {
private final Handler handle = new Handler();
private final Runnable drawRunner = new Runnable() {
#Override
public void run() {
draw();
}
};
private Boolean visble = true;
public Bitmap mBackgroundImage;
public String name;
int[] mImagesArray;
private int mImagesArrayIndex = 0;
MyWallpaperEngine() {
mImagesArray = new int[] { R.drawable.love_1_mini,
R.drawable.love_2_mini, R.drawable.love_3_mini,
R.drawable.love_4_mini, R.drawable.love_5_mini,
R.drawable.love_6_mini, R.drawable.love_7_mini,
R.drawable.love_8_mini, R.drawable.love_9_mini,
R.drawable.love_10_mini,
R.drawable.love_11_mini, R.drawable.love_12_mini,
R.drawable.love_13_mini, R.drawable.love_14_mini,
R.drawable.love_15_mini, R.drawable.love_16_mini,
R.drawable.love_17_mini, R.drawable.love_18_mini,
R.drawable.love_19_mini, R.drawable.love_20_mini };
if (mImagesArrayIndex == 0) {
x = -330; // initialize x position
y = 0; // initialize y position
} else {
x = -330; // initialize x position
y = 0; // initialize y position
}
}
#Override
public Bundle onCommand(String action, int x, int y, int z,
Bundle extras, boolean resultRequested) {
return super.onCommand(action, x, y, z, extras, resultRequested);
}
#Override
public void onCreate(SurfaceHolder surfaceHolder) {
super.onCreate(surfaceHolder);
}
#Override
public void onOffsetsChanged(float xOffset, float yOffset,
float xOffsetStep, float yOffsetStep, int xPixelOffset,
int yPixelOffset) {
draw();
}
#Override
public void onSurfaceCreated(SurfaceHolder holder) {
super.onSurfaceCreated(holder);
this.visble = false;
}
#Override
public void onTouchEvent(MotionEvent event) {
}
#Override
public void onVisibilityChanged(boolean visible) {
this.visble = visible;
if (visible) {
handle.post(drawRunner);
} else {
handle.removeCallbacks(drawRunner);
}
super.onVisibilityChanged(visible);
}
private void incrementCounter() {
mImagesArrayIndex++;
if (mImagesArrayIndex >= mImagesArray.length) {
mImagesArrayIndex = 0;
}
}
#SuppressWarnings("deprecation")
void draw() {
int count = mImagesArray[mImagesArrayIndex];
final SurfaceHolder holder = getSurfaceHolder();
Canvas c = null;
int scale = 2;
Resources res = getResources();
BitmapFactory.Options o2 = new BitmapFactory.Options();
o2.inPreferredConfig = Bitmap.Config.RGB_565;
o2.inSampleSize = scale;
o2.inPurgeable = true;
o2.inInputShareable = true;
Bitmap image = BitmapFactory.decodeResource(res, count, o2);
Calendar mCalendar = Calendar.getInstance();
int h = mCalendar.get(Calendar.HOUR);
int m = mCalendar.get(Calendar.MINUTE);
int s = mCalendar.get(Calendar.SECOND);
if (s == 10) {
mImagesArrayIndex = 0;
}
try {
Paint paint = new Paint(Paint.FILTER_BITMAP_FLAG);
c = holder.lockCanvas();
c.drawColor(Color.RED);
if (c != null) {
int a = 0;
int counting = 0;
if (counting == a) {
a++;
counting++;
c.drawBitmap(image, x, y, paint);
int widthhhh = c.getWidth();
x = (float) (x + 10.10);
if (x > widthhhh + 400) {
image.recycle();
image = null;
incrementCounter();
}
}
paint.setColor(Color.RED);
}
} catch (IllegalArgumentException e) {
e.printStackTrace();
} finally {
try {
if (c != null) {
holder.unlockCanvasAndPost(c);
}
handle.removeCallbacks(drawRunner);
if (visble) {
handle.postDelayed(drawRunner, 10);
}
} catch (IllegalArgumentException e) {
e.printStackTrace();
}
}
}
}
}
Bitmap image = BitmapFactory.decodeResource(getResources(), mImagesArray[mImagesArrayIndex]);
When your done with it call image.recycle() (see javadoc).
In your draw() method, you have this code:
Bitmap image = BitmapFactory.decodeResource(getResources(),
mImagesArray[mImagesArrayIndex]);
int count = mImagesArray[mImagesArrayIndex];
final SurfaceHolder holder = getSurfaceHolder();
Canvas c = null;
int scale = 4;
Resources res = getResources();
BitmapFactory.Options o2 = new BitmapFactory.Options();
o2.inPreferredConfig = Bitmap.Config.RGB_565;
o2.inSampleSize = scale;
o2.inPurgeable = true;
o2.inInputShareable = true;
image = BitmapFactory.decodeResource(res, count, o2);
Notice the first line calls BitmapFactory.decodeResource() and assigns the result to variable image and then later, the last line again calls BitmapFactory.decodeResource() and assigns the result to variable image. You never called recycle() on the first decoded image, so this is giving you a memory leak.

DiskLruCache from android tutorial is missing lots of methods.

Here is the disk cache tutorial I'm following. I've downloaded the source code to DiskLruCache but none of the methods used in this example exist in the source code.
http://developer.android.com/training/displaying-bitmaps/cache-bitmap.html#disk-cache
Do I need to implement these methods myself or is there a version of DiskLruCache that I'm missing somewhere?
Here is the complete implementation of DiskLruCache.
First download DiskLruCache.java from AOSP.
Here is my DiskCache.java a helper class for basic cache operations.
/**
* Created by Babar on 12-Aug-15.
*/
public class DiskCache
{
private Context context;
private static DiskCache diskCache;
public DiskLruCache mDiskLruCache;
private final Object mDiskCacheLock = new Object();
private BitmapProcessor bitmapProcessor;
private static final int DISK_CACHE_INDEX = 0;
public static boolean mDiskCacheStarting = true;
private static final String DISK_CACHE_SUBDIR = "ooredoo_thumbnails";
private static final int DISK_CACHE_SIZE = 1024 * 1024 * 100; // 100MB
public static DiskCache getInstance()
{
if(diskCache == null)
{
diskCache = new DiskCache();
}
return diskCache;
}
private DiskCache() {}
public void requestInit(Context context)
{
this.context = context;
bitmapProcessor = new BitmapProcessor();
new DiskCacheTask(this).execute(DiskCacheTask.INIT);
}
public void init() throws IOException {
synchronized (mDiskCacheLock)
{
if(mDiskLruCache == null || mDiskLruCache.isClosed())
{
File cacheDir = FileUtils.getDiskCacheDir(context, DISK_CACHE_SUBDIR);
if(!cacheDir.exists())
{
cacheDir.mkdir();
}
if(FileUtils.getUsableSpace(cacheDir) > DISK_CACHE_SIZE)
{
mDiskLruCache = DiskLruCache.open(cacheDir, 1, 1, DISK_CACHE_SIZE);
}
else
{
Logger.print("InitDiskCache failed: NOT enough space on disk");
}
}
mDiskCacheStarting = false; // Finished initialization
mDiskCacheLock.notifyAll(); // Wake any waiting threads
}
}
public void addBitmapToDiskCache(final String key, final Bitmap value) {
if (key == null || value == null) {
return;
}
synchronized (mDiskCacheLock)
{
if (mDiskLruCache != null) {
OutputStream out = null;
String encryptedKey = CryptoUtils.encryptToMD5(key);
Logger.print("addBitmapToDiskCache encryptToMD5: " + encryptedKey);
try {
DiskLruCache.Snapshot snapshot = mDiskLruCache.get(encryptedKey);
if (snapshot == null) {
final DiskLruCache.Editor editor = mDiskLruCache.edit(encryptedKey);
if (editor != null) {
out = editor.newOutputStream(DISK_CACHE_INDEX);
value.compress(Bitmap.CompressFormat.JPEG, 100, out);
editor.commit();
out.close();
}
} else {
snapshot.getInputStream(DISK_CACHE_INDEX).close();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (out != null) {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
}
/**
* Get from disk cache.
*
* #param key Unique identifier for which item to get
* #return The bitmap if found in cache, null otherwise
*/
public Bitmap getBitmapFromDiskCache(final String key)
{
Bitmap bitmap = null;
String encryptedKey = CryptoUtils.encryptToMD5(key);
Logger.print("getBitmapFromDiskCache encryptToMD5: " + encryptedKey);
synchronized (mDiskCacheLock)
{
Logger.print("mDiskcachestarting: "+mDiskCacheStarting);
// Wait while disk cache is started from background thread
while (mDiskCacheStarting)
{
try
{
mDiskCacheLock.wait();
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
if(mDiskLruCache != null)
{
InputStream inputStream = null;
try
{
final DiskLruCache.Snapshot snapshot = mDiskLruCache.get(encryptedKey);
if(snapshot != null)
{
Logger.print("Disk cache hit");
inputStream = snapshot.getInputStream(DISK_CACHE_INDEX);
if(inputStream != null)
{
FileDescriptor fd = ((FileInputStream) inputStream).getFD();
// Decode bitmap, but we don't want to sample so give
// MAX_VALUE as the target dimensions
bitmap = bitmapProcessor.decodeSampledBitmapFromDescriptor(fd);
}
}
}
catch (IOException e)
{
e.printStackTrace();
}
finally
{
if(inputStream != null)
{
try
{
inputStream.close();
}
catch (IOException e)
{
e.printStackTrace();
}
}
}
}
Logger.print("dCache getBitmapFromDiskCache synchronized completed");
}
Logger.print("dCache getBitmapFromDiskCache returning Bitmap");
return bitmap;
}
public void requestFlush()
{
new DiskCacheTask(this).execute(DiskCacheTask.FLUSH);
}
/**
* Flushes the disk cache associated with this ImageCache object. Note that this includes
* disk access so this should not be executed on the main/UI thread.
*/
public void flush()
{
synchronized (mDiskCacheLock)
{
if(mDiskLruCache != null)
{
try
{
mDiskLruCache.flush();
Logger.print("flush: disk cache flushed");
}
catch (IOException e)
{
e.printStackTrace();
}
}
}
}
public void requestClose()
{
new DiskCacheTask(this).execute(DiskCacheTask.CLOSE);
}
/**
* Closes the disk cache associated with this ImageCache object. Note that this includes
* disk access so this should not be executed on the main/UI thread.
*/
public void close()
{
synchronized (mDiskCacheLock)
{
if(mDiskLruCache != null)
{
if(!mDiskLruCache.isClosed())
{
try
{
mDiskLruCache.close();
mDiskLruCache = null;
Logger.print("disk cache closed");
}
catch (IOException e)
{
e.printStackTrace();
}
}
}
}
}
/**
* Do not call this method unless you need to explicitly clear disk cache
*/
public void requestTearDown()
{
new DiskCacheTask(this).execute(DiskCacheTask.TEAR_DOWN);
}
public final void tearDown()
{
synchronized (mDiskCacheLock)
{
mDiskCacheStarting = true;
if(mDiskLruCache != null && !mDiskLruCache.isClosed())
{
try
{
mDiskLruCache.delete();
Logger.print("disk cache cleared");
}
catch (IOException e)
{
e.printStackTrace();
}
mDiskLruCache = null;
}
}
}
}
Here is BitmapProcesser.java
/**
* #author Babar
* #since 15-Jun-15.
*/
public class BitmapProcessor
{
public Bitmap decodeSampledBitmapFromStream(InputStream inputStream, URL url,
int reqWidth, int reqHeight) throws IOException {
Bitmap bitmap;
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeStream(inputStream, null, options);
int width = options.outWidth;
int height = options.outHeight;
Logger.print("#Req Width: "+reqWidth);
Logger.print("#Req Height: " + reqHeight);
Logger.print("#Actual Width: "+width);
Logger.print("#Actual Height: " + height);
int sampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
options.inJustDecodeBounds = false;
options.inSampleSize = sampleSize;
inputStream = url.openStream();
bitmap = BitmapFactory.decodeStream(inputStream, null, options);
if(bitmap != null)
{
width = bitmap.getWidth();
height = bitmap.getHeight();
Logger.print("#inSample:"+sampleSize);
Logger.print("#Modified Width: "+width);
Logger.print("#Modified Height: " + height);
}
return bitmap;
}
public Bitmap decodeSampledBitmapFromDescriptor(FileDescriptor fd)
{
/*final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFileDescriptor(fd, null, options);
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
options.inJustDecodeBounds = false;*/
return BitmapFactory.decodeFileDescriptor(fd);
}
public Bitmap decodeSampledBitmapFromFile(String pathName, int reqWidth, int reqHeight) throws IOException {
Bitmap bitmap;
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(pathName, options);
int width = options.outWidth;
int height = options.outHeight;
Logger.print("#Req Width: "+reqWidth);
Logger.print("#Req Height: " + reqHeight);
Logger.print("#Actual Width: "+width);
Logger.print("#Actual Height: " + height);
int sampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
options.inJustDecodeBounds = false;
options.inSampleSize = sampleSize;
bitmap = BitmapFactory.decodeFile(pathName, options);
if(bitmap != null)
{
width = bitmap.getWidth();
height = bitmap.getHeight();
Logger.print("#inSample:"+sampleSize);
Logger.print("#Modified Width: "+width);
Logger.print("#Modified Height: " + height);
}
return bitmap != null ? rotateBitmapIfNeeded(pathName, bitmap) : null;
}
public int calculateInSampleSize(BitmapFactory.Options options,
int requiredWidth, int requiredHeight)
{
final int width = options.outWidth;
final int height = options.outHeight;
int inSampleSize = 1;
if(width > requiredWidth || height > requiredHeight)
{
final int halfWidth = width / 2;
final int halfHeight = height / 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 ((halfWidth / inSampleSize) > requiredWidth
&&
(halfHeight / inSampleSize) > requiredHeight)
{
inSampleSize *= 2;
}
}
return inSampleSize;
}
public static Bitmap rotateBitmapIfNeeded(String pathName, Bitmap bitmap) throws IOException {
ExifInterface exifInterface = new ExifInterface(pathName);
int orientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION,
ExifInterface.ORIENTATION_NORMAL);
Logger.logI("BITMAP_ORIENTATION: " + orientation, pathName);
switch (orientation)
{
case ExifInterface.ORIENTATION_ROTATE_90:
return rotateBitmap(bitmap, 90);
case ExifInterface.ORIENTATION_ROTATE_180:
return rotateBitmap(bitmap, 180);
case ExifInterface.ORIENTATION_ROTATE_270:
return rotateBitmap(bitmap, 270);
}
return bitmap;
}
public Bitmap makeBitmapRound(Bitmap src)
{
int width = src.getWidth();
int height = src.getHeight();
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
// Canvas canvas = new Canvas(bitmap);
BitmapShader shader = new BitmapShader(src, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setShader(shader);
RectF rectF = new RectF(0.0f, 0.0f, width, height);
// rect contains the bounds of the shape
// radius is the radius in pixels of the rounded corners
// paint contains the shader that will texture the shape
Canvas canvas = new Canvas(src);
canvas.drawRoundRect(rectF, 30, 30, paint);
return src;
}
public static Bitmap rotateBitmap(Bitmap bitmap, int degree) {
Matrix matrix = new Matrix();
matrix.postRotate(degree);
return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
}
}
Here is CryptoUtils.java
/**
* #author Babar
* #since 29-Jun-15.
*/
public class CryptoUtils
{
private static final String MD5_ALGO = "MD5";
public static String encryptToMD5(String text)
{
try
{
java.security.MessageDigest md = java.security.MessageDigest.getInstance(MD5_ALGO);
md.update(text.getBytes());
byte[] bytes = md.digest();
String hex = bytesToHexString(bytes);
return hex;
}
catch (NoSuchAlgorithmException e)
{
e.printStackTrace();
return String.valueOf(text.hashCode());
}
}
private static String bytesToHexString(byte[] bytes)
{
StringBuffer sb = new StringBuffer();
for(int i = 0; i < bytes.length; i++)
{
String hex = Integer.toHexString(0xFF & bytes[i]);
if(hex.length() == 1)
{
sb.append('0');
}
sb.append(hex);
}
return sb.toString();
}
public static String encodeToBase64(String str) {
String tmp = "";
if(isNotNullOrEmpty(str)) {
try {
tmp = new String(Base64.encode(str.getBytes(), Base64.DEFAULT)).trim();
} catch(Throwable e) {
e.printStackTrace();
}
}
return tmp;
}
}
Here is DiskCacheTask.java
/**
* Created by Babar on 12-Aug-15.
*/
public class DiskCacheTask extends BaseAsyncTask<Integer, Void, Void>
{
private DiskCache diskCache;
public static final int INIT = 1;
public static final int FLUSH = 2;
public static final int CLOSE = 3;
public static final int TEAR_DOWN = 4;
public static final int REMOVE = 5;
public DiskCacheTask(DiskCache diskCache)
{
this.diskCache = diskCache;
}
#Override
protected Void doInBackground(Integer... params)
{
switch (params[0])
{
case INIT:
try
{
diskCache.init();
}
catch (IOException e)
{
e.printStackTrace();
}
break;
case FLUSH:
diskCache.flush();
break;
case CLOSE:
diskCache.close();
break;
case TEAR_DOWN:
diskCache.tearDown();
break;
case REMOVE:
diskCache.remove();
break;
}
return null;
}
}
To initialize the cache simply call new DiskCache().getInstance().requestInit(); ideally from your MainActivity's onCreate(). Also note Disk operation should be done in separate thread for example Handler , AsyncTask. So when ever you want to add/get bitmap to/from disk cache do this from a worker thread.
You will need to wrap the DiskLruCache with your own cache that offers higher level abstractions. Someone has already done this for bitmaps, see Using DiskLruCache in android 4.0 does not provide for openCache method
Alternatively, you can use an open source library such as Picasso.

Efficiently load 5 larger than fullscreen Bitmaps into my Android Live Wallpaper and scroll them parralax

So I'm new to Java and this kind of coding.
I'm trying to make a Parrallax scrolling Live Wallpaper. But I'm having memory issues.
Well I have made it, and it works on on the phone I have. But I think the way i have done it is not very efficient at all. Because when I try it on other phones it doesn't work. It breaks at my Out Of Memory catcher. I added another layer and now it does the same thing on my phone too. So I am able to debug it. Basically I guess I'm using up-to or over 16 meg of memory.
If someone could take a look at my code and help me load in the bitmaps more efficiently that would be greatly appreciated.
Here is how I'm currently doing it:
static class Layer {
public Bitmap bitmap;
private float scale = 1.0f;
private Matrix matrix = new Matrix();
public Layer(Bitmap b) {
this.bitmap = b;
}
public void setScale(float factor) {
scale = factor;
}
public Matrix getMatrix(float x, float y) {
if (scale == 1) {
matrix.reset();
} else {
matrix.setScale(scale, scale);
}
matrix.postTranslate(x, y);
return matrix;
}
}
public static List<Integer> findLayers(Integer path) {
List<Integer> files = new ArrayList<Integer>();
files.add(R.drawable.planet_layer4);
files.add(R.drawable.planet_layer3);
files.add(R.drawable.planet_layer2);
files.add(R.drawable.planet_layer1);
files.add(R.drawable.planet_layer0);
return files;
}
private void loadLayers() {
try {
clearLayers();
for (Integer file: layerFiles) {
addLayer(file);
}
recalibrateLayers();
} catch (IOException e) {
layers.clear();
Toast.makeText(LiveWallpaper.this, "There was a problem loading the wallpaper. Please contact the developer.", Toast.LENGTH_LONG).show();
} catch (OutOfMemoryError oom) {
layers.clear();
Toast.makeText(LiveWallpaper.this, "Whoops, we ran out of memory trying to load the images. ", Toast.LENGTH_LONG).show();
}
}
private void addLayer(int name) throws IOException {
Bitmap layer = BitmapFactory.decodeResource(getResources(), name);
if (layer == null) {
throw new IOException("BitmapFactory couldn't decode asset " + name);
}
synchronized(layers) {
layers.add(new Layer(layer));
}
}
private void clearLayers() {
synchronized(layers) {
layers.clear();
}
}
private void recalibrateLayers() {
for (Layer layer : layers) {
final int bitmapHeight = layer.bitmap.getHeight();
layer.setScale((float)mHeight / (float)bitmapHeight);
}
}
#Override
public void onDestroy() {
super.onDestroy();
mHandler.removeCallbacks(mDrawParallax);
}
#Override
public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) {
super.onSurfaceChanged(holder, format, width, height);
mHeight = height;
recalibrateLayers();
drawBackgrounds();
}
And here is where I draw them.
/*
* Draw one frame of the animation. This method gets called repeatedly
* by posting a delayed Runnable. You can do any drawing you want in
* here.
*/
void drawBackgrounds() {
final SurfaceHolder holder = getSurfaceHolder();
final Rect frame = holder.getSurfaceFrame();
mFrame = frame;
Canvas c = null;
try {
c = holder.lockCanvas();
if (c != null) {
// draw something
drawParallax(c);
}
} finally {
if (c != null) holder.unlockCanvasAndPost(c);
}
}
void drawParallax(Canvas c) {
int frameWidth = mFrame.width();
for (int i=layers.size()-1; i>=0; i--) {
Layer layer = layers.get(i);
Bitmap bitmap = layer.bitmap;
float bitmapWidth = bitmap.getWidth() * layer.scale;
float max = frameWidth - bitmapWidth;
float offset = mOffset * max;
final Matrix m = layer.getMatrix(offset, 0);
c.drawBitmap(bitmap, m, null);
}
}

Combine two image and save to sd card

I am having code for combining two image and show them together in a canvas as follows. Can you say how to store that as a single image.
public class ChoosePictureComposite extends Activity implements OnClickListener {
static final int PICKED_ONE = 0;
static final int PICKED_TWO = 1;
boolean onePicked = false;
boolean twoPicked = false;
Button choosePicture1, choosePicture2;
ImageView compositeImageView;
Bitmap bmp1, bmp2;
Canvas canvas;
Paint paint;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
compositeImageView = (ImageView) this
.findViewById(R.id.CompositeImageView);
choosePicture1 = (Button) this.findViewById(R.id.ChoosePictureButton1);
choosePicture2 = (Button) this.findViewById(R.id.ChoosePictureButton2);
choosePicture1.setOnClickListener(this);
choosePicture2.setOnClickListener(this);
}
public void onClick(View v) {
int which = -1;
if (v == choosePicture1) {
which = PICKED_ONE;
} else if (v == choosePicture2) {
which = PICKED_TWO;
}
Intent choosePictureIntent = new Intent(Intent.ACTION_PICK,
android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
startActivityForResult(choosePictureIntent, which);
}
protected void onActivityResult(int requestCode, int resultCode,
Intent intent) {
super.onActivityResult(requestCode, resultCode, intent);
if (resultCode == RESULT_OK) {
Uri imageFileUri = intent.getData();
if (requestCode == PICKED_ONE) {
bmp1 = loadBitmap(imageFileUri);
onePicked = true;
} else if (requestCode == PICKED_TWO) {
bmp2 = loadBitmap(imageFileUri);
twoPicked = true;
}
if (onePicked && twoPicked) {
Bitmap drawingBitmap = Bitmap.createBitmap(bmp1.getWidth(),
bmp1.getHeight(), bmp1.getConfig());
canvas = new Canvas(drawingBitmap);
paint = new Paint();
canvas.drawBitmap(bmp1, 90, 0, paint);
// paint.setXfermode(new PorterDuffXfermode(
// android.graphics.PorterDuff.Mode.MULTIPLY));
canvas.drawBitmap(bmp2, 30, 40, paint);
compositeImageView.setImageBitmap(drawingBitmap);
}
}
}
private Bitmap loadBitmap(Uri imageFileUri) {
Display currentDisplay = getWindowManager().getDefaultDisplay();
float dw = currentDisplay.getWidth();
float dh = currentDisplay.getHeight();
Bitmap returnBmp = Bitmap.createBitmap((int) dw, (int) dh,
Bitmap.Config.ARGB_4444);
try {
// Load up the image's dimensions not the image itself
BitmapFactory.Options bmpFactoryOptions = new BitmapFactory.Options();
bmpFactoryOptions.inJustDecodeBounds = true;
returnBmp = BitmapFactory.decodeStream(getContentResolver()
.openInputStream(imageFileUri), null, bmpFactoryOptions);
int heightRatio = (int) Math.ceil(bmpFactoryOptions.outHeight / dh);
int widthRatio = (int) Math.ceil(bmpFactoryOptions.outWidth / dw);
Log.v("HEIGHTRATIO", "" + heightRatio);
Log.v("WIDTHRATIO", "" + widthRatio);
// If both of the ratios are greater than 1, one of the sides of the
// image is greater than the screen
if (heightRatio > 1 && widthRatio > 1) {
if (heightRatio > widthRatio) {
// Height ratio is larger, scale according to it
bmpFactoryOptions.inSampleSize = heightRatio;
} else {
// Width ratio is larger, scale according to it
bmpFactoryOptions.inSampleSize = widthRatio;
}
}
// Decode it for real
bmpFactoryOptions.inJustDecodeBounds = false;
returnBmp = BitmapFactory.decodeStream(getContentResolver()
.openInputStream(imageFileUri), null, bmpFactoryOptions);
} catch (FileNotFoundException e) {
Log.v("ERROR", e.toString());
}
return returnBmp;
}
}
Try this:
public class Aura extends Activity {
protected static final String TAG = Aura.class.getName();
private static String mTempDir;
Bitmap mBackImage, mTopImage, mBackground, mInnerImage, mNewSaving;
Canvas mComboImage;
FileOutputStream mFileOutputStream;
BitmapDrawable mBitmapDrawable;
private String mCurrent = null;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.aura);
mTempDir = Environment.getExternalStorageDirectory() + "/" + "Aura" + "/";
mCurrent = "Aura.png";
prepareDirectory();
mBackground = Bitmap.createBitmap(604, 1024, Bitmap.Config.ARGB_8888);
mBackImage = BitmapFactory.decodeResource(getResources(), R.drawable.aura);
mTopImage = BitmapFactory.decodeResource(getResources(), R.drawable.test);
mInnerImage = BitmapFactory.decodeResource(getResources(), R.drawable.anothertest);
mComboImage = new Canvas(mBackground);
mComboImage.drawBitmap(mBackImage, 0f, 0f, null);
mComboImage.drawBitmap(mTopImage, 0f, 0f, null);
mComboImage.drawBitmap(mInnerImage, 0f, 0f, null);
mFileOutputStream = null;
mSave.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
Log.v(TAG, "Save Tab Clicked");
try {
mBitmapDrawable = new BitmapDrawable(mBackground);
mNewSaving = ((BitmapDrawable) mBitmapDrawable).getBitmap();
String FtoSave = mTempDir + mCurrent;
File mFile = new File(FtoSave);
mFileOutputStream = new FileOutputStream(mFile);
mNewSaving.compress(CompressFormat.PNG, 95, mFileOutputStream);
mFileOutputStream.flush();
mFileOutputStream.close();
} catch (FileNotFoundException e) {
Log.v(TAG, "FileNotFoundExceptionError " + e.toString());
} catch (IOException e) {
Log.v(TAG, "IOExceptionError " + e.toString());
}
}
});
}//onCreate
private boolean prepareDirectory() {
try {
if (makeDirectory()) {
return true;
} else {
return false;
}
} catch (Exception e) {
e.printStackTrace();
Toast.makeText(this, getString(R.string.sdcard_error), 1000).show();
return false;
}
}
private boolean makeDirectory() {
File mTempFile = new File(mTempDir);
if (!mTempFile.exists()) {
mTempFile.mkdirs();
}
if (mTempFile.isDirectory()) {
File[] mFiles = mTempFile.listFiles();
for (File mEveryFile : mFiles) {
if (!mEveryFile.delete()) {
System.out.println(getString(R.string.failed_to_delete) + mEveryFile);
}
}
}
return (mTempFile.isDirectory());
}
#Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if ((!(android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.DONUT)
&& keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0)) {
onBackPressed();
}
return super.onKeyDown(keyCode, event);
}
public void onBackPressed() {
finish();
}
}
create a new Bitmap with the height sum of the individual height of your images.
Bitmap newBitmap = Bitmap.createBitmap(widht
, totalHeight, Config.ARGB_8888);
Canvas canvas = new Canvas(newBitmap);
canvas.drawBitmap(image11, 0, 0, null);
canvas.drawBitmap(image2, 0, image1Height, null);
this should draw the 2 images into the newBitmap, one below the other. change height parameters to widht if you want them side by side.
Is this what you were looking for ?

Categories

Resources