I've written a multimedia app. In this I have made a gallery with a viewpager, and a FragmentStatePagerAdapter. I used FragmentStatePagerAdapter so that each time I'have in memory only two images and each time the user scrolls the pager the old picture is destroyed and the new is decoded in bitmap.
I have read lots of tutorials of how to efficiently load a bitmap using in a smart way the inSampleSize option and I've implemented it in such a way. In my phone runs great, but in some phones I get an out of memory exception so this is how I decode my ImageView in each fragment
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
try {
ImageArray=org.apache.commons.io.FileUtils.readFileToByteArray(new File(_imagePaths.get(mImageNum)));
if(MyApplication.getDeviceWidth() !=0 || MyApplication.getDeviceHeight()!=0)
bitmap=decodeSampledBitmapFromResource(ImageArray,MyApplication.getDeviceWidth(), MyApplication.getDeviceHeight());
else{
DisplayMetrics metrics = MyApplication.getAppContext().getResources().getDisplayMetrics();
bitmap=decodeSampledBitmapFromResource(ImageArray,metrics.widthPixels, metrics.heightPixels);
}
mImageView.setImageBitmap(bitmap);
} catch (IOException e) {
Log.e("ImageDetailFragment",e.getLocalizedMessage());
}
}
public static Bitmap decodeSampledBitmapFromResource(byte[] array,int reqWidth, int reqHeight) {
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
Bitmap b =BitmapFactory.decodeByteArray(array, 0, array.length,options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
if(b!=null)
b.recycle();
Log.i("inSampleSize", String.valueOf(options.inSampleSize));
return BitmapFactory.decodeByteArray(array, 0, array.length,options);
}
public static int calculateInSampleSize(BitmapFactory.Options options,
int reqWidth,int reqHeight) {
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
// Calculate ratios of height and width to requested height and width
final int heightRatio = Math.round((float) height / (float) reqHeight);
final int widthRatio = Math.round((float) width / (float) reqWidth);
// Choose the smallest ratio as inSampleSize value, this will guarantee
// a final image with both dimensions larger than or equal to the
// requested height and width.
inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
}
Log.i("inSample size", String.valueOf(inSampleSize));
if(sampleLimit==-1)
return 2;
return inSampleSize;
}
So as you can see (I think) I load each bitmap efficiently and if a screen is smaller inSampleSize goes to 2
My question is how can I compute inSampleSize not depending on screen size only but also on the memory that is available to my app, cause in my phone I get 64MB but i've come across tablets with 38MB or less
Related
I am getting pretty frustrated with all these official Android docs that conveniently gloss over the fact that even when you apply a Bitmap to an ImageView, the ImageView's dimensions are 0, 0, and yet you can't get the dimensions prior to mapping, either!
https://developer.android.com/training/displaying-bitmaps/load-bitmap.html
All these methods assume you already know fixed dimensions for the ImageView's height and width in terms of pixels, but this is almost useless because in practice you're supposed to either use dp units or layout_weight so things scale to the viewable region so things look right on various phones.
The only way, it would seem, to get the dimensions is to go to the trouble of inefficiently mapping the bitmap and then getting the dimensions that way, but even if you do, the dimensions will show up as 0 if you do imageView.getWidth() or getHeight()! You have to do a bunch of weird async stuff to wait until the dimensions somehow "settle" and THEN you can finally get the dimensions, but at this stage, what's the point when you've already wasted time doing an inefficient mapping?
Is there some well known workaround to all this that Google isn't explaining in the docs? How are you supposed to know the ImageView dimensions when you're not working with pixels in the XML? It's mind-boggling to me that this isn't documented more.
Here is a sample of the problem:
<ImageView
android:id="#+id/my_imageview"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"/>
I have this ImageView inside a DialogFragment. The idea is that I can map a Bitmap to this ImageView and it will fit inside the DialogFragment by means of something like myImageView.setImageBitmap(BitmapFactory.decodeFile(path_to_file));, but this by itself is inefficient if the Bitmap is large.
However, in order to load the Bitmap efficiently, you need to already know the dimensions of the ImageView -- and in pixels! But you can't get the dimensions of the ImageView until you've already mapped the Bitmap inefficiently and waited for some kind of post-execute stage where the dimensions have settled in.
These are the methods I am using to do efficient mapping (assuming you already know the sizes -- it breaks if one of the dimensions is 0, so I added a fix). This code is from Google:
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 == 0 || width == 0) {
return 0; //my fix
}
if (height > reqHeight || width > reqWidth) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
// height and width larger than the requested height and width.
while ((halfHeight / inSampleSize) >= reqHeight
&& (halfWidth / inSampleSize) >= reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
public static Bitmap decodeSampledBitmapFromFilePath(String pathName, int reqWidth, int reqHeight) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(pathName, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeFile(pathName, options);
}
And for example this does not work:
myImageView.post(new Runnable() {
#Override
public void run() {
int reqHeight = myImageView.getHeight(); //height = 0
int reqWidth = myImageView.getWidth(); //width = 528
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(picFullPath, options);
int realWidth = options.outWidth;
int realHeight = options.outHeight;
reqHeight = realHeight * reqWidth / realWidth; //reqHeight = 396, reqWidth = 528
Bitmap bm = decodeSampledBitmapFromFilePath(picFullPath, reqWidth, reqHeight);
myImageView.setImageBitmap(bm);
}
});
myImageView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
int finalHeight = myImageView.getHeight(); //480!
int finalWidth = myImageView.getWidth(); //608!
}
});
You Should get height and Width of view after inflate any view from XML or Dynamically and then use bellow code to get rendered view height and width :
imageView.measure(0,0);
imageView.getMeasuredHeight();
imageView.getMeasuredWidth();
But Above method may given 0 for height and width , if you are not passing any height and width from XML or you are not used any relative property in Relative layout
I use this approach when I need to load bitmap after I know ImageView size:
myImageView.post(new Runnable() {
#Override
public void run() {
int requiredHeight = myImageView.getHeight(); //height is ready, but is 0 - not calculated.
int requiredWidth = myImageView.getWidth(); //width ready
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(pathName, options);
int realWidth = option.outWidth;
int realHeight = option.outHeight;
//calculating required Height
requiredHeight = realHeight / realWidth * requiredWidth;
//now we have both - required height and width.
Bitmap bm = decodeSampledBitmapFromFilePath(pathName, requiredWidth, requiredHeight);
myImageView.setImageBitmap(bm);
}
});
Getting OOM big time in my game UI (I'm only targeting kitkat and above)
I noticed that if I use a android:src="#drawable/empty_screen_game_setup" in XML, leaving and entering the activity always has the same memory usage.
If I DON'T set src in XML, but rather use the following google code, leaving and entering the activity makes the memory usage climb like crazy. This is a test app with no other code because I'm trying to understand the OOM. So much conflicting info out there!!
Is it better to just use src in XML memory-wise? I was hoping to use one set of ressources in no-dpi and scale them to size with bitmapfactory given the screen size but I guess that's not best :(
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 / 2;
final int halfWidth = width / 2;
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
// height and width larger than the requested height and width.
while ((halfHeight / inSampleSize) > reqHeight
&& (halfWidth / inSampleSize) > reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
int reqWidth, int reqHeight) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
}
And in onCreate:
glass = (ImageView) findViewById(R.id.imageViewGlass);
glass.setImageBitmap( decodeSampledBitmapFromResource(getResources(), R.drawable.empty_screen_game_setup, 500, 300) );
I have a list in my application that contains all the installed applications with their icons, I'm able to render the installed applications and the icons as well but it is consuming lot of memory as it loads the drawables(icons) of the installed applications into memory as well.
I want to scale down the icon and then load into memory just to reduce the memory usage of the application. Can anyone tell me how that can be achieved.
Note : if PNG format then it will not compress your image because PNG is a lossless format.
and applications icons are in PNG format
Any way to reduce the memory allocation for the icons??
Yeah it's all in the docs:
http://developer.android.com/training/displaying-bitmaps/index.html
Calculate your sample size, i.e. size of the bitmap you want:
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) {
// Calculate ratios of height and width to requested height and width
final int heightRatio = Math.round((float) height / (float) reqHeight);
final int widthRatio = Math.round((float) width / (float) reqWidth);
// Choose the smallest ratio as inSampleSize value, this will guarantee
// a final image with both dimensions larger than or equal to the
// requested height and width.
inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
}
return inSampleSize;
}
The decode your bitmap at this size:
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
int reqWidth, int reqHeight) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
}
This should all be done off the UI thread:
http://developer.android.com/training/displaying-bitmaps/process-bitmap.html
You can then cache the bitmaps so you don't have to do it more times than necessary:
http://developer.android.com/training/displaying-bitmaps/cache-bitmap.html
Use inSampleSize from BitmapFactory.Options
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 2; //Downsample 10x
I wonder if there is any difference between image extensions .jpg and .JPG. I encountered a problem in that .JPG images cannot be opened with the following code:
public static Bitmap decodeSampledBitmapFromResource(String path,
int reqWidth, int reqHeight) {
Log.e("PATH", path);
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
// Calculate inSampleSizeŠ°
options.inSampleSize = calculateInSampleSize(options, reqWidth,
reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
Bitmap b = BitmapFactory.decodeFile(path, options);
if(b!=null){
Log.d("bitmap","decoded sucsessfully");
}else{
Log.d("bitmap","decoding failed; options: "+options.toString());
}
return b;
}
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;
Log.d("bitmap", "original options: height "+ height + " widnth "+width+" inSampleSize "+inSampleSize);
if (height > reqHeight || width > reqWidth) {
// Calculate ratios of height and width to requested height and
// width
final int heightRatio = Math.round((float) height
/ (float) reqHeight);
final int widthRatio = Math.round((float) width / (float) reqWidth);
// Choose the smallest ratio as inSampleSize value, this will
// guarantee
// a final image with both dimensions larger than or equal to the
// requested height and width.
inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
Log.d("bitmap", "options: height "+ height + " widnth "+width+" inSampleSize "+inSampleSize);
}
return inSampleSize;
}
If I call
decodeSampledBitmapFromResource(path, 80, 60)
on *.JPG image, the returned bitmap is null.
I'm asking if they are generally implemented in different way or it is specific for Galaxy S3? (I was not able to repeat it on Galaxy Tab 7.7 or on HTC phones.)
Android runs on Linux operating system, which is case sensitive.
If the file has the extension in lowercase, like this: ".jpg", so you should refer in the source code. If the file has the extension ".JPG", also, the same should be written in code.
My requirement is what ever the size(width,height not file size) image stored in the database i need to retrieving that as simple passport size of image.
My code is :
byte[] imgByte=null;
in the oncreate method
imageview=new ImageView(this);
imageview.setLayoutParams(llp2);
imgByte=cursor.getBlob(cursor.getColumnIndex("imagestore"));
imageview.setScaleType(ScaleType.CENTER);
imageview.setImageBitmap(BitmapFactory.decodeByteArray(imgByte, 0, imgByte.length));
layout.addView(imageview)
when i displaying that displayed only what ever the size before enter. so i need to fit that image size in the android.
I tried these links
Reduce size of Bitmap to some specified pixel in Android
but the problem here is i got image in byte. But all codes image data type int only. can i get the correct solution to decrease the image size pragmatically?
You can use following code to create sample sized image. This will return Bitmap.
public static Bitmap decodeSampledBitmapFromResource(Resources res,
int resId, int reqWidth, int reqHeight) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth,
reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, 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) {
// Calculate ratios of height and width to requested height and
// width
final int heightRatio = Math.round((float) height
/ (float) reqHeight);
final int widthRatio = Math.round((float) width / (float) reqWidth);
// Choose the smallest ratio as inSampleSize value, this will
// guarantee
// a final image with both dimensions larger than or equal to the
// requested height and width.
inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
}
return inSampleSize;
}
Use this code,
decodeSampledBitmapFromResource(getResources(),R.drawable.xyz, 100, 100);
Here, 100 * 100 sample size you are providing. So Thumbnail of such size will be created.
Edit
To use bytes[] in code, use following short of code.
public static Bitmap decodeSampledBitmap(byte[] data, int reqWidth,
int reqHeight) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeByteArray(data, 0, data.length, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth,
reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeByteArray(data, 0, data.length, options);
}