Android saving large bitmaps - android

I'm new to android and I'm confused about how to deal with Bitmaps.
I want to download a Bitmap, it could be quite large, and save it to a temporary internal file. I'm then going to draw this Bitmap to a Canvas later.
My current method is to
1. Download the input stream
2. copy the stream
3. use one stream to work out bounds using bitmapFactory.options
4. use the other stream to decode the full bitmap with the sample size
However, I need landscape and portrait versions, so now I will have to do this twice and save two images.
Or - I have seen people use bm.compress(Bitmap.CompressFormat.JPEG, 50, bos); to save a file instead. This by-passes the decoding with sample size as its saved direct from a stream. I guess then I would use a matrix to scale when I draw to my Canvas.
Basically, I am confused as the best approach for this task , which method is less likely to run into out of memory and is the more commonly used approach?
Cheers

byte[] imagesByte = getLogoImage(Your url);
set to imageview...
imgView.setImageBitmap(BitmapFactory.decodeByteArray( imagesByte, 0, imagesByte.length));
Method for Download
public static byte[] getLogoImage(String url){
try {
URL imageUrl = new URL(url);
URLConnection ucon = imageUrl.openConnection();
InputStream is = ucon.getInputStream();
BufferedInputStream bis = new BufferedInputStream(is);
ByteArrayBuffer baf = new ByteArrayBuffer(500);
int current = 0;
while ((current = bis.read()) != -1) {
baf.append((byte) current);
}
return baf.toByteArray();
} catch (Exception e) {
Log.d("ImageManager", "Error: " + e.toString());
}
return null;
}

In Android you have to e aware of limited memory, so large images would't fit in memory and you will have OutOfMemory exceptions.
The key is, after saving te image in internal storage, load it at the display resolution:
First download te image, this should be done outside the UI thread, let _url an URL intance with the image addres and _file the String containing destination file :
URLConnection conn = _url.openConnection();
conn.connect();
InputStream is = conn.getInputStream();
boolean success = false; //track succesful operation
if( _file != null)
{
try
{
FileOutputStream fos = new FileOutputStream(_file);
byte data[] = new byte[4086]; //use 4086 bytes buffer
int count = 0;
while ((count = is.read(data)) != -1)
{
fos.write(data, 0, count);//write de data
}
is.close();
fos.flush();
fos.close();
int len = conn.getContentLength();
File f = new File( _file);//check fie length is correct
if( len== f.length())
{
success = true;
}
else
{
//error downloading, delete de file
File tmp = new File( _file);
if( tmp.exists())
{
tmp.delete();
}
}
}catch (Exception e )
{
try
{
e.printStackTrace();
//delete file with errors
File tmp = new File( _file);
if( tmp.exists())
{
tmp.delete();
}
}
catch (Exception ex)
{
ex.printStackTrace();
}
}
finally
{
is.close();//cleanup
}
Then when you have to load the image at the desired resolution, here the key is use BitmapFactory to read bitmap info and get scaled bitmap:
public static Bitmap bitmapFromFile(int width, int height, String file)
{
Bitmap bitmap = null;
final BitmapFactory.Options options = new BitmapFactory.Options();
if( height >0 && width > 0 ) {
options.inJustDecodeBounds = true;//only read bitmap metadata
BitmapFactory.decodeFile(file,options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, width, height);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
}
try
{
bitmap = BitmapFactory.decodeFile(file, options);//decode scaled bitmap
}catch (Throwable t)
{
if( bitmap != null)
{
bitmap.recycle();//cleanup memory, very important!
}
return null;
}
return bitmap
}
The final step is to calculate the scale factor:
public 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;
final int halfWidth = width;
// 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 ((couldShrink(halfWidth, reqWidth, inSampleSize)&&
couldShrink(halfHeight,reqHeight, inSampleSize))
//&&(halfHeight*halfWidth)/inSampleSize > maxsize)
)
{
inSampleSize *= 2;
}
}
return inSampleSize;
}
private static boolean couldShrink ( int dimension, int req_dimension, int divider)
{
int actual = dimension / divider;
int next = dimension / (divider*2);
int next_error = Math.abs(next - req_dimension);
int actual_error = Math.abs(actual-req_dimension);
return next > req_dimension ||
(actual > req_dimension && (next_error < actual_error) )
;
}
That is if you want to do it by hand, I recommend you to use Picasso that will handle donwloading, disk caching and memory caching of your image:
To load into a ImageView called image showing a backgroud (R.drawable.img_bg) while downloading :
Picasso.with(image.getContext())
.load(url).placeholder(R.drawable.img_bg).fit()
.into(image, new Callback.EmptyCallback()
{
#Override
public void onSuccess()
{
holder.progress.setVisibility(View.GONE); //hide progress bar
}
#Override
public void onError()
{
holder.progress.setVisibility(View.GONE); //hide progress bar
//do whatever you design to show error
}
});
to handle yourself a bitmap:
//first declare a target
_target = new Target()
{
#Override
public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from)
{
//handle your bitmap (store it and use it on you canvas
}
#Override
public void onBitmapFailed(Drawable errorDrawable)
{
//handle your fail state
}
#Override
public void onPrepareLoad(Drawable placeHolderDrawable)
{//for example for drawing a placeholder while downloading
}
};
Now you just have to load and resize your image:
Picasso.with(context).load(url).resize(192, 192).centerCrop().into(_target);
Hope that helps.

Related

Recycler NetworkImageView Scrolling back and forward, out of memory and resizing issues

I am working to add an image from the gallery horizontally using recycler view as follows. I could able to add images next to each other.
The following code works but crashes sometimes because of out of memory concerns.
RecyclerViewAdapter, I am calling setSelectedPic method to load the image.
#Override
public void onBindViewHolder(final ListViewHolder holder, int position) {
PostProductImageLIst item = mItems.get(position);
ImageUtil.setSelectedPic(holder.imgViewIcon, item.getPath());
}
ImageUtil Class
public static void setSelectedPic(CustomNetworkImageView view, String url) {
Context context = view.getContext();
ImageLoader imageLoader = CustomVolleyRequest.getInstance(context).getImageLoader();
if (!TextUtils.isEmpty(url)) {
if (url.startsWith("http")) {
imageLoader.get(url, ImageLoader.getImageListener(view,
R.drawable.image, android.R.drawable
.ic_dialog_alert));
view.setImageUrl(url, imageLoader);
} else {
try {
Uri uri = Uri.parse(url);
InputStream is = context.getContentResolver().openInputStream(uri);
Bitmap bitmap = BitmapFactory.decodeStream(is);
view.setLocalImageBitmap(bitmap);
if (is!=null) {
is.close();
}
} catch (Exception ex) {
Log.e("Image", ex.getMessage(), ex);
}
}
} else {
view.setImageUrl("", CustomVolleyRequest.getInstance(view.getContext()).getImageLoader());
}
}
And then I decide to resize the images to solve the aferomentioned issue, out of memory-.The following code works. BUT, when I scroll back to first image, that image shows blank or white, but when I delete the second image, then I could able to see first image. I could not able to figure out the root of the problem. I am using NetworkImageView.
RecyclerViewAdapter, I am calling setSelectedPic method to load the image.
#Override
public void onBindViewHolder(final ListViewHolder holder, int position) {
PostProductImageLIst item = mItems.get(position);
ImageUtil.setSelectedPic(holder.imgViewIcon, item.getPath());
}
ImageUtil Class
public static void setSelectedPic(CustomNetworkImageView view, String url) {
Context context = view.getContext();
ImageLoader imageLoader = CustomVolleyRequest.getInstance(context).getImageLoader();
if (!TextUtils.isEmpty(url)) {
if (url.startsWith("http")) {
imageLoader.get(url, ImageLoader.getImageListener(view,
R.drawable.image, android.R.drawable
.ic_dialog_alert));
view.setImageUrl(url, imageLoader);
} else {
try {
int targetW = view.getWidth();
int targetH = view.getHeight();
Uri uri = Uri.parse(url);
InputStream is = context.getContentResolver().openInputStream(uri);
BitmapFactory.Options bmOptions = new BitmapFactory.Options();
bmOptions.inJustDecodeBounds = true;
BitmapFactory.decodeFile(url, bmOptions);
int photoW = bmOptions.outWidth;
int photoH = bmOptions.outHeight;
// Determine how much to scale down the image
int scaleFactor = Math.min(photoW / targetW, photoH / targetH);
// Decode the image file into a Bitmap sized to fill the View
bmOptions.inJustDecodeBounds = false;
bmOptions.inSampleSize = scaleFactor;
Bitmap bitmap = BitmapFactory.decodeStream(is);
Matrix matrix = new Matrix();
matrix.postRotate(getImageOrientation(url));
Bitmap rotatedBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(),bitmap.getHeight(), matrix, true);
view.setLocalImageBitmap(rotatedBitmap);
if (is!=null) {
is.close();
} catch (Exception ex) {
Log.e("Image", ex.getMessage(), ex);
}
}
} else {
view.setImageUrl("", CustomVolleyRequest.getInstance(view.getContext()).getImageLoader());
}
}
You should scale your Bitmap and use caching for reducing loading time. Here on this repository I've put reference code for approaching image loading with scaling and caching.
I report here code to be used for setPic method. This essentially uses an LruCache for improving Bitmap loading while scrolling. This could be a good starting point. Notice also that Bitmap are scaled before putting them in cache.
private static final int WIDTH = 100;
private LruCache<String, Bitmap> mMemoryCache;
public void setPic(MyImageView view, String url) {
Context context = view.getContext();
if (!TextUtils.isEmpty(url)) {
if (url.startsWith("http")) {
view.setImageUrl(url, VolleyHandler.getInstance(context).getImageLoader());
} else {
try {
Uri uri = Uri.parse(url);
Bitmap bitmap = mMemoryCache.get(url);
if (bitmap == null) {
InputStream is = context.getContentResolver().openInputStream(uri);
bitmap = BitmapFactory.decodeStream(is);
Bitmap scaled = ImageUtils.getInstance().scaleBitmap(bitmap);
mMemoryCache.put(url, scaled);
if (is!=null) {
is.close();
}
}
view.setLocalImageBitmap(bitmap);
} catch (Exception ex) {
Log.e("Image", ex.getMessage(), ex);
}
}
} else {
view.setImageUrl("", VolleyHandler.getInstance(view.getContext()).getImageLoader());
}
}
public Bitmap scaleBitmap(Bitmap bitmap) {
int width = WIDTH;
int height = (WIDTH * bitmap.getHeight()) / bitmap.getWidth();
return Bitmap.createScaledBitmap(bitmap, width, height, false);
}
I suggest also to refer to Android Developers documentation here (for efficient Bitmap loading) and here (for Bitmap caching with Volley).

OutofMemory on BitmapFactory.decodeFile

In my app I have to upload selected images to parse.com for taking their Printout . I have to maintain image quality and I could not resize the images.
I have to upload images in the parse.com ..I do not need to show them on device screen (images are form image gallery or from facebook album..or from sdcard) . I could not scale down them as per requirement.
I am getting OutOfMemory error on BitmapFactory.decodeFile(). How to solve this bug ?
is using android:largeHeap="true" could sove my issue ?
I am getting this crash on Samsung SM-G900T, But not on emulator ..
I tried to put
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = false;
options.inPreferredConfig = Config.RGB_565;
But it is not working.
Below is my AsyncTask class for uploading images to Parse.com
class UploadFileFromURL extends AsyncTask<String, String, String> {
ProgressDialog dialog;
String albumId = "";
#Override
protected void onPreExecute() {
super.onPreExecute();
}
#Override
protected String doInBackground(String... f_url) {
try {
for (int i = 0; i < arrListImgBean.size(); i++) {
if (!isUploading || objAsyncUpload.isCancelled()) {
break;
}
try {
if (arrListImgBean.get(i).imageStatus == 1)
continue;
else if (arrListImgBean.get(i).imageStatus == 2) {
isPhotodeleted = true;
publishProgress("" + countUploaded);
deletePhoto(i);
}
else {
isPhotodeleted = false;
try {
Bitmap b = null;
InputStream is = null;
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = false;
options.inPreferredConfig = Config.RGB_565; // to
// reduce
// the
// memory
options.inDither = true;
if (arrListImgBean.get(i).imgURL
.startsWith("http")) {
try {
URL url = new URL(
arrListImgBean.get(i).imgURL);
is = url.openConnection()
.getInputStream();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
b = BitmapFactory.decodeStream(is, null,
options);
} else {
b = BitmapFactory.decodeFile(
arrListImgBean.get(i).imgURL,
options);
}
// Convert it to byte
ByteArrayOutputStream stream = new ByteArrayOutputStream();
// Bitmap out = Bitmap.createScaledBitmap(b,
// 1500, 2100, false);
b.compress(Bitmap.CompressFormat.PNG, 100,
stream);
byte[] image = stream.toByteArray();
ParseFile file = new ParseFile("Android.png",
image);
file.save();
String uploadedUrl = file.getUrl();
if (uploadedUrl != null) {
ParseObject imgupload = new ParseObject(
"Photo");
imgupload.put("userName", ParseUser
.getCurrentUser().getEmail());
imgupload.put("photoURL", file);
imgupload.put("photoID",
arrListImgBean.get(i).imageId);
imgupload.put("count", 1);
imgupload.put("albumName", albumId);
imgupload.save();
String objId = imgupload.getObjectId();
if (objId != null && !objId.isEmpty()) {
countUploaded++;
publishProgress("" + countUploaded);
database.updateImageStatus(
arrListImgBean.get(i).imageId,
Constants.STATUS_UPLOADED,
objId, uploadedUrl);
}
}
} catch (Exception e) {
}
}
} catch (Exception e) {
isUploading = false;
e.printStackTrace();
}
}
} catch (Exception e) {
Log.e("Error: ", e.getMessage());
}
return null;
}
#Override
protected void onPostExecute(String file_url) {
// dismissDialog(progress_bar_type);
isUploading = false;
btnUploadImages.setBackgroundResource(R.drawable.upload_photo);
vprogress.setCompoundDrawables(null, null, null, null);
// stopLoading();
setProgressMsg();
}
}
android:largeHeap="true"
This line of code can solve your problem but its a temporary solution but crash may occurs again if number of images or the size of images will increase. Better to Use Picasso library to deals with Images
Consider you have an image of 1024x1024dp and a device with 512x512dp (both figures are just for understanding). So, in this case, loading a full resolution image on a smaller scale device is waste of memory. What you can do is to scale down the image so that it fits the device screen. In this way not only you will save a lot of memory but also get a proper, clear and sharp image.
I am adding code for scaling the image which I am using currently in my project.
final FileInputStream streamIn = new FileInputStream(file);
final BitmapFactory.Options ops = new BitmapFactory.Options();
ops.inJustDecodeBounds = true;
// Find the correct scale value. It should be the power of 2.
final int REQUIRED_SIZE = 300;
int width_tmp = ops.outWidth, height_tmp = ops.outHeight;
int scale = 1;
while (true) {
if (width_tmp / 2 < REQUIRED_SIZE || height_tmp / 2 < REQUIRED_SIZE) {
break;
}
width_tmp /= 2;
height_tmp /= 2;
scale *= 2;
}
ops.inJustDecodeBounds = false;
ops.inSampleSize = scale;
bitmap = BitmapFactory.decodeStream(streamIn, null, ops); //This gets the image
streamIn.close();
Choose a REQUIRED_SIZE value depending on the device's screen display size.
try {
image = readInFile(path);
}
catch(Exception e) {
e.printStackTrace();
}
// Create the ParseFile
ParseFile file = new ParseFile("picturePath", image);
// Upload the image into Parse Cloud
file.saveInBackground();
// Create a New Class called "ImageUpload" in Parse
ParseObject imgupload = new ParseObject("Image");
// Create a column named "ImageName" and set the string
imgupload.put("Image", "picturePath");
// Create a column named "ImageFile" and insert the image
imgupload.put("ImageFile", file);
// Create the class and the columns
imgupload.saveInBackground();
// Show a simple toast message
Toast.makeText(LoadImg.this, "Image Saved, Upload another one ",Toast.LENGTH_SHORT).show();
private byte[] readInFile(String path) throws IOException {
// TODO Auto-generated method stub
byte[] data = null;
File file = new File(path);
InputStream input_stream = new BufferedInputStream(new FileInputStream(
file));
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
data = new byte[16384]; // 16K
int bytes_read;
while ((bytes_read = input_stream.read(data, 0, data.length)) != -1) {
buffer.write(data, 0, bytes_read);
}
input_stream.close();
return buffer.toByteArray();
}

download Bitmap and write into existing one

I'm downloading a bitmap from an URL with the following code. If I do this cyclic (like streaming images from a camera) then the bitmap will be reallocated again and again. So I wonder if there is a way to write the newly downloaded byte-array into the existing bitmap which is already allocated in memory.
public static Bitmap downloadBitmap(String url) {
try {
URL newUrl = new URL(url);
return BitmapFactory.decodeStream(newUrl.openConnection()
.getInputStream());
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
Within this segment in the bitmap memory management section entitled 'Manage Memory on Android 3.0 and Higher' they start to speak of how to manipulate the bitmaps so that you can reuse the bitmap space so that the location for the Bitmap itself does not need to be re-allocated. If you are indeed looking at using the stream from the camera then this will cover back to Honeycomb since they will be the same sizes. Otherwise, it may only help out past 4.4 Kitkat.
But, you could store a local WeakReference (if you want it to be collected in case of memory issues) within the downloadBitmap class and then re-assign to that space and return there instead of creating a bitmap each time in a single line.
The app is slowed down because it allocates and de-allocates memory in each cycle. There are three ways to avoid that.
The first version works without OpenCV but still allocates some memory in each cycle. But the amount is much smaller and therefore it is at least two times faster. How? By re-using an existing and allready allocated buffer (byte[]). I'm using it with a pre-allocated SteamInfo buffer of 1.000.000 length (about double the size than I'm expecting).
By the way - reading the input stream in chunks and using BitmapFactory.decodeByteArray is much faster than putting the URL's input stream directly into BitmapFactory.decodeStream.
public static class StreamInfo {
public byte[] buffer;
public int length;
public StreamInfo(int length) {
buffer = new byte[length];
}
}
public static StreamInfo imageByte(StreamInfo buffer, String url) {
try {
URL newUrl = new URL(url);
InputStream is = (InputStream) newUrl.getContent();
byte[] tempBuffer = new byte[8192];
int bytesRead;
int position = 0;
if (buffer != null) {
// re-using existing buffer
while ((bytesRead = is.read(tempBuffer)) != -1) {
System.arraycopy(tempBuffer, 0, buffer.buffer, position,
bytesRead);
position += bytesRead;
}
buffer.length = position;
return buffer;
} else {
// allocating new buffer
ByteArrayOutputStream output = new ByteArrayOutputStream();
while ((bytesRead = is.read(tempBuffer)) != -1) {
output.write(tempBuffer, 0, bytesRead);
position += bytesRead;
}
byte[] result = output.toByteArray();
buffer = new StreamInfo(result.length * 2, false);
buffer.length = position;
System.arraycopy(result, 0, buffer.buffer, 0, result.length);
return buffer;
}
} catch (MalformedURLException e) {
e.printStackTrace();
return null;
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
The second version uses OpenCV Mat and a pre-allocated Bitmap. Receiving the stream is done as in version one. So it does not need further memory allocation anymore (for details check out this link). This version works fine but it is a bit slower because it contains conversions between OpenCV Mat and Bitmap.
private NetworkCameraFrame frame;
private HttpUtils.StreamInfo buffer = new HttpUtils.StreamInfo(1000000);
private MatOfByte matForConversion;
private NetworkCameraFrame receive() {
buffer = HttpUtils.imageByte(buffer, uri);
if (buffer == null || buffer.length == 0)
return null;
Log.d(TAG, "Received image with byte-array of length: "
+ buffer.length / 1024 + "kb");
if (frame == null) {
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
Bitmap bmp = BitmapFactory.decodeByteArray(buffer.buffer, 0,
buffer.length);
frame = new NetworkCameraFrame(bmp.getWidth(), bmp.getHeight());
Log.d(TAG, "NetworkCameraFrame created");
bmp.recycle();
}
if (matForConversion == null)
matForConversion = new MatOfByte(buffer.buffer);
else
matForConversion.fromArray(buffer.buffer);
Mat newImage = Highgui.imdecode(matForConversion,
Highgui.IMREAD_UNCHANGED);
frame.put(newImage);
return frame;
}
private class NetworkCameraFrame implements CameraFrame {
Mat mat;
private int mWidth;
private int mHeight;
private Bitmap mCachedBitmap;
private boolean mBitmapConverted;
public NetworkCameraFrame(int width, int height) {
this.mWidth = width;
this.mHeight = height;
this.mat = new Mat(new Size(width, height), CvType.CV_8U);
this.mCachedBitmap = Bitmap.createBitmap(width, height,
Bitmap.Config.ARGB_8888);
}
#Override
public Mat gray() {
return mat.submat(0, mHeight, 0, mWidth);
}
#Override
public Mat rgba() {
return mat;
}
// #Override
// public Mat yuv() {
// return mYuvFrameData;
// }
#Override
public synchronized Bitmap toBitmap() {
if (mBitmapConverted)
return mCachedBitmap;
Mat rgba = this.rgba();
Utils.matToBitmap(rgba, mCachedBitmap);
mBitmapConverted = true;
return mCachedBitmap;
}
public synchronized void put(Mat frame) {
mat = frame;
invalidate();
}
public void release() {
mat.release();
mCachedBitmap.recycle();
}
public void invalidate() {
mBitmapConverted = false;
}
};
The third version uses the instructions "Usage of BitmapFactory" on BitmapFactory.Options and a mutable Bitmap that is then re-used while decoding. It even work ed for me on Android JellyBean. Make sure you're using the correct BitmapFactory.Options when created the very first Bitmap.
BitmapFactory.Options options = new BitmapFactory.Options();
options.inBitmap = bmp; // the old Bitmap that should be reused
options.inMutable = true;
options.inSampleSize = 1;
Bitmap bmp = BitmapFactory.decodeByteArray(buffer, 0, buffer.length, options);
options.inBitmap = bmp;
This was actually the fastest streaming then.

BitmapFactory.decodeStream returns null, when downloading a image form web

I'm trying to download a image from a URL, using the Google Example Page. I've read when I use a InputStream in the BitmapFactory.decodeStream method, I can't use twice. I'm trying to do that, but it doesn't work 'cause it returns null in the decoded image, and I don't know what I can do.
This is my code:
This part is in a doInBackground method in a AsyncTask class
Bitmap bitmapImage;
URL imageUrl = null;
try {
imageUrl = new URL(url[0]);
HttpGet httpRequest = null;
httpRequest = new HttpGet(imageUrl.toURI());
HttpClient httpclient = new DefaultHttpClient();
HttpResponse response = (HttpResponse) httpclient.execute(httpRequest);
HttpEntity entity = response.getEntity();
BufferedHttpEntity bufHttpEntity = new BufferedHttpEntity(entity);
InputStream instream = bufHttpEntity.getContent();
bitmapImage = CommonMethods.decodeSampledBitmapFromResource(instream, thumb_width, thumb_width);
instream.close();
return bitmapImage;
} catch (URISyntaxException e) {
e.printStackTrace();
return null;
} catch (MalformedURLException e) {
e.printStackTrace();
return null;
} catch (IOException e) {
e.printStackTrace();
return null;
}
public static Bitmap decodeSampledBitmapFromResource(InputStream instream,
int reqWidth, int reqHeight) throws IOException {
//Copy instream for decode twice
ByteArrayOutputStream out = new ByteArrayOutputStream();
copy(instream,out);
ByteArrayInputStream instream2 = new ByteArrayInputStream(out.toByteArray());
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeStream(instream, null, options);
instream2.close();
options.inJustDecodeBounds = false;
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
return BitmapFactory.decodeStream(instream, null, options);
}
public 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) {
if (width > height) {
inSampleSize = Math.round((float) height / (float) reqHeight);
} else {
inSampleSize = Math.round((float) width / (float) reqWidth);
}
}
return inSampleSize;
}
//Copy instream method
public static void copy(InputStream input, OutputStream output) throws IOException{
byte[] buffer = new byte[Constants.IO_BUFFER_SIZE];
int n = 0;
while (-1 != (n = input.read(buffer))) {
output.write(buffer, 0, n);
}
}
BitmapFactory.decodeStream returns null because the inputstream is used twice, I've not tried your code, but it seams OK, or maybe I'm wrong.
Anyway, I've a better solution. Just use BufferedInputStream to wrap the inputStream, and before your second read, call "reset" first. Note ordinary inputStreams donnot support "reset", you may call it but nothing will happen.
My code:
public static Bitmap decodeSampledBitmapFromStream(InputStream inputStream,
int reqWidth, int reqHeight)
throws IOException {
if (!widthHeightCheck(reqWidth, reqHeight))
return BitmapFactory.decodeStream(inputStream);
// First decode with inJustDecodeBounds=true to check dimensions
if (!(inputStream instanceof BufferedInputStream)) {
inputStream = new BufferedInputStream(inputStream);
}
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
Rect rect = new Rect(-1, -1, -1, -1);
BitmapFactory.decodeStream(inputStream, rect, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
inputStream.reset();
return BitmapFactory.decodeStream(inputStream, rect, options);
}
I think you can achieve this by wrapping the stream you get from the httpEntity in a custom WrappedStream. This WrappedStream will feed a second input stream while reading the original stream. (this is done with PipedStream)
After getting the image size with this code :
options.inJustDecodeBounds = true;
WrappedStream wrappedStream = new WrappedStream(instream);
BitmapFactory.decodeStream(wrappedStream, null, options);
You can call
InputStream reReadStream = wrappedStream.getReReadStream();
options.inJustDecodeBounds = false;
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
return BitmapFactory.decodeStream(reReadStream, null, options);
And finally, here is the implementation of WrappedStream (it simply delegates all calls to the wrapped inputStream, and writes all bytes that are read (or skipped) in a pipedOutputStream)
import java.io.IOException;
import java.io.InputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
/** Simple class wrapping an InputStream and feeding a secondary InputStream
* to re-read the data that was originally available in the inputStream.
**/
public class WrappedStream extends InputStream {
private InputStream urlStream;
private PipedOutputStream pipedStream;
public WrappedStream(InputStream urlStream) {
this.urlStream = urlStream;
this.pipedStream = new PipedOutputStream();
}
/**
* return a fresh InputStream to re-read the data
*/
public InputStream getReReadStream() throws IOException {
return new PipedInputStream(pipedStream);
}
#Override
public int available() throws IOException {
return urlStream.available();
}
#Override
public void close() throws IOException {
urlStream.close();
}
#Override
public void mark(int readlimit) {
urlStream.mark(readlimit);
}
#Override
public boolean markSupported() {
return urlStream.markSupported();
}
#Override
public int read() throws IOException {
int b = urlStream.read();
pipedStream.write(b);
return b;
}
#Override
public int read(byte[] buffer) throws IOException {
int l = urlStream.read(buffer);
pipedStream.write(buffer);
return l;
}
#Override
public int read(byte[] buffer, int offset, int length) throws IOException {
int l = urlStream.read(buffer, offset, length);
pipedStream.write(buffer, offset, length);
return l;
}
#Override
public void reset() throws IOException {
urlStream.reset();
}
#Override
//bytes skipped must available on the re-read stream so we read and write them.
public long skip(long byteCount) throws IOException {
long bytesToSkip = byteCount;
long skippedBytes = 0;
//ugly trick required to not loosing bytes if we ever skip more than Integer.MAX_VALUE bytes
while(bytesToSkip>Integer.MAX_VALUE){
_skip(Integer.MAX_VALUE);
bytesToSkip -=Integer.MAX_VALUE;
skippedBytes +=Integer.MAX_VALUE;
}
byte[] b = new byte[(int)bytesToSkip];
skippedBytes += read(b);
return skippedBytes;
}
private int _skip(int byteCount) throws IOException {
byte[] b = new byte[(int)byteCount];
return read(b);
}
}
Please note that I didn't test this code. This is just to give you some ideas on how to solve your problem.
Another point: even if this code never create a huge bitmap, the whole stream will be kept in memory until the scaled Bitmap is build.
Found the code which will work for you
final HttpEntity entity = response.getEntity();
if (entity != null) {
InputStream inputStream = null;
try {
inputStream = entity.getContent();
BitmapFactory.Options options = new BitmapFactory.Options();
//options.inSampleSize = 2;
final Bitmap bitmap = BitmapFactory
.decodeStream(inputStream, null, options);
return bitmap;
} catch (Exception e) {
e.printStackTrace();
} finally {
if (inputStream != null) {
inputStream.close();
}
entity.consumeContent();
}
}
Please replace the proper variable and if you wanted to scale the image you can scale it after getting the bitmap.
here is the method to download bitmap from server with less code you can fulfill your requirement
Bitmap downloadBitmap(String url)
{
Bitmap image = null;
InputStream in = null;
try
{
in = new java.net.URL(url).openStream();
BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inSampleSize = 2;
image = BitmapFactory.decodeStream(new FlushedInputStream(in),null,opts);
in.close();
}
catch (MalformedURLException e)
{
e.printStackTrace();
}
catch (IOException e)
{
e.printStackTrace();
}
return image;
}
in the above code we use
opts.inSampleSize = 2;
it means the bitmap will be reduced to half size of its original size to avoid memory exception we have to do this if we are loading lot of images
some other class used in it
static class FlushedInputStream extends FilterInputStream
{
public FlushedInputStream( InputStream inputStream )
{
super(inputStream);
}
#Override
public long skip(long n) throws IOException
{
long totalBytesSkipped = 0L;
while (totalBytesSkipped < n)
{
long bytesSkipped = in.skip(n - totalBytesSkipped);
if (bytesSkipped == 0L)
{
int byte1 = read();
if (byte1 < 0)
{
break; // we reached EOF
}
else
{
bytesSkipped = 1; // we read one byte
}
}
totalBytesSkipped += bytesSkipped;
}
return totalBytesSkipped;
}
}
Please Use below code for download and display image into imageview.
public class image extends Activity {
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Bitmap bitmap = DownloadImage("http://www.gophoto.it/view.php?i=http://1.bp.blogspot.com/-2LTvCCufBKc/T3L3KgcTj2I/AAAAAAAABbQ/Ki60e1LU9sE/s1600/Sachin%2BTendulkar.png");
ImageView img = (ImageView) findViewById(R.id.img);
img.setImageBitmap(bitmap);
}
private InputStream OpenHttpConnection(String urlString) throws IOException {
InputStream in = null;
int response = -1;
URL url = new URL(urlString);
URLConnection conn = url.openConnection();
if (!(conn instanceof HttpURLConnection))
throw new IOException("Not an HTTP connection");
try {
HttpURLConnection httpConn = (HttpURLConnection) conn;
httpConn.setAllowUserInteraction(false);
httpConn.setInstanceFollowRedirects(true);
httpConn.setRequestMethod("GET");
httpConn.connect();
response = httpConn.getResponseCode();
if (response == HttpURLConnection.HTTP_OK) {
in = httpConn.getInputStream();
}
} catch (Exception ex) {
throw new IOException("Error connecting");
}
return in;
}
private Bitmap DownloadImage(String URL) {
Bitmap bitmap = null;
InputStream in = null;
try {
in = OpenHttpConnection(URL);
bitmap = BitmapFactory.decodeStream(in);
in.close();
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
return bitmap;
}
}

OutOfMemoryError: bitmap size exceeds VM budget

Sorry it seems like a repeated question, BUT I think I don't qualify to any of the recommendations already posted.
I've a Gallery of maximum 20 images on my application. After playing a while flinging back and forth I'm getting OutOfMemoryError.
The strange thing is that I don't hold any static references, and I've searched for possible memory leaks I can assure that I've not found one so far.
Anyway, 20 images (PNG of 100KB on average) doesn't be like that much. And I've implemented a view cache, SoftReference holders for the bitmaps, etc.
Is it 20 PNG images of 100KB on average enough to kill my app?? seriously? how can I get rid of this? I've followed this great post also
http://blog.jteam.nl/2009/09/17/exploring-the-world-of-android-part-2/
Any more ideas?
This is the ImageCache:
public class AsyncImageLoader {
private final String TAG = getClass().getSimpleName();
private Context mContext;
private HashMap<String, SoftReference<Bitmap>> mImageCache;
public AsyncImageLoader(Context context) {
mContext = context;
mImageCache = new HashMap<String, SoftReference<Bitmap>>();
}
public Bitmap loadImage(final String identifier, final String imagePath, final ImageCallback imageCallback) {
if (mImageCache.containsKey(imagePath)) {
SoftReference<Bitmap> softReference = mImageCache.get(imagePath);
Bitmap bitmap = softReference.get();
if (bitmap != null) {
Log.i(TAG, "Retrieving image from cache: " + imagePath);
return bitmap;
}
}
final Handler handler = new Handler() {
#Override
public void handleMessage(Message message) {
imageCallback.imageLoaded((Bitmap) message.obj, imagePath, identifier);
}
};
new Thread() {
#Override
public void run() {
Bitmap bitmap = loadImageFromPath(imagePath);
mImageCache.put(imagePath, new SoftReference<Bitmap>(bitmap));
Message message = handler.obtainMessage(0, bitmap);
handler.sendMessage(message);
}
}.start();
return null;
}
public Bitmap loadImageFromPath(String path) {
if(!GeneralUtilities.isEmpty(path)) {
Log.i(TAG, "Loading image: " + path);
InputStream imageInputStream = null;
try {
final AssetManager assetManager = mContext.getResources().getAssets();
imageInputStream = assetManager.open(path);
Bitmap bitmap = GeneralUtilities.decodeFile(imageInputStream);
imageInputStream.close();
return bitmap;
} catch (final IOException e) {
Log.e(TAG, e.getMessage());
}
}
return null;
}
public interface ImageCallback {
public void imageLoaded(Bitmap imageBitmap, String imagePath, String identifier);
}
}
and the method GeneralUtilities.decodeFile is:
public static Bitmap decodeFile(InputStream is){
//Decode image size
BitmapFactory.Options o = new BitmapFactory.Options();
o.inJustDecodeBounds = true;
BitmapFactory.decodeStream(is, null, o);
//The new size we want to scale to
final int REQUIRED_SIZE=140;
//Find the correct scale value. It should be the power of 2.
int width_tmp = o.outWidth, height_tmp = o.outHeight;
int scale = 1;
while(true) {
if(width_tmp / 2 < REQUIRED_SIZE || height_tmp / 2 < REQUIRED_SIZE)
break;
width_tmp /= 2;
height_tmp /= 2;
scale *= 2;
}
//Decode with inSampleSize
BitmapFactory.Options o2 = new BitmapFactory.Options();
o2.inSampleSize = scale;
return BitmapFactory.decodeStream(is, null, o2);
}
And in the getView of the ArrayAdapter I've something like this:
final ImageView itemImage = cache.getHistoryImage();
//final ImageView itemFrame = cache.getFrame();
String filename = item.getFilename().trim();
itemImage.setTag("front_" + filename);
Bitmap cachedImage = mAsyncImageLoader.loadImage("front_" + filename, filename, new ImageCallback() {
public void imageLoaded(Bitmap imageBitmap, String imagePath, String identifier) {
ImageView imageViewByTag = (ImageView) mGallery.findViewWithTag(identifier);
if (imageViewByTag != null) {
imageViewByTag.setImageBitmap(imageBitmap);
}
}
});
itemImage.setImageBitmap(cachedImage);
There seems to be a bug in the Android framework, although Google seems to deny it.
Did you read through issue 8488?
http://code.google.com/p/android/issues/detail?id=8488
I am not sure if this applies to your code - but you might try the recommendations before setting/updating the image on the ImageView.
Basically, it boils down to calling Bitmap.recycle(), nulling references (probably irrellevant in your case) and explicitly calling calling System.gc().
The garbage collector seems to run asynchronously and a new might fail even though memory could be freed.

Categories

Resources