I recently ran into some OutOfMemory error in an android application I am working on, mostly because I am loading the images in their original size (now I know this is a bad idea).
Im now working on implementing methods to load scaled down versions of the images depending on the actual ImageView size and caching them as suggested in googles developer guide.
The guide states out very well how I should process and handle images loaded at runtime in Java code, but it leaves out how to work with images defined via XML in my layout files.
For example if I have an ImageView with a predefined Image, the XML code would look something like this:
<ImageView
android:id="#+id/my_image_id"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/my_image"
/>
If I now want to replace that image at runtime, im checking the width and height of the ImageView, and load the new image according to these dimensions:
imageView.setImageBitmap(decodeSampledBitmapFromResource(Resources res, int resId,
int reqWidth, int reqHeight)
Obviously, this has not be done for the Image defined via XML.
Do I have to worry about these images, too, or does the system handle the downscaling for them? Or should I avoid declaring the src of an imageview via XML at all?
Thanks in advance,
danijoo
When you set in an image from your drawable folder android does take care for you. But in that case you have to help too :) . For example if you only provide an image for drawable-mdpi and you do run your application in hpdi devices android will scale up the image to fit it in the screen.But of course before it does it will try to find hdpi version of that image into drawable-hdpi folder. If you do provide hdpi version of that image into drawable-hdpi folder it will not scale.
Now as for your OutOfMemoryError i think you might've put your image in only drawable or any of the low dpi folder and you haven't put that image's larger version to other high dpi folders.(hdpi/xhdpi/xxhdpi).
As per documentation:
Provide different bitmap drawables for different screen densities:
By default, Android scales your bitmap drawables (.png, .jpg, and .gif files) and Nine-Patch drawables (.9.png files) so that they render at the appropriate physical size on each device. For example, if your application provides bitmap drawables only for the baseline, medium screen density (mdpi), then the system scales them up when on a high-density screen, and scales them down when on a low-density screen.
I think you will find more useful information in :screens_support
Use below code to scale down size of image when you are selecting it from Gallery or Camera.
Intent photoPickerIntent = new Intent(
Intent.ACTION_PICK,
android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
// photoPickerIntent.setType("image/*");
photoPickerIntent.putExtra("crop", "true");
photoPickerIntent.putExtra("outputX", 512);
photoPickerIntent.putExtra("outputY", 512);
photoPickerIntent.putExtra("aspectX", 1);
photoPickerIntent.putExtra("aspectY", 1);
photoPickerIntent.putExtra("scale", true);
This will load image of required size into your app and system will not run out of memory.
You can ask if you have any further queries.
Related
I'm using Android Studio, and have created an ImageView in activity_mail.xml. The layout width and height are set to "match_parent". The scaleType is set to "center" and the source image is 70x70 pixels. When I run the app in the emulator the Image appears on the screen larger than 70x70 pixels. I mean it takes up more than 70x70 pixels on the screen of the phone. I don't know why it's doing this. The app is exclusively in Landscape mode, that might be relevant. Forgive me if I have included extraneous details, I genuinely don't know what information would be relevant to include. I intend to make it so that the image takes up 70x70 pixels on the screen of the phone. What is causing the unintended result, and how could I fix it?
change ScaleType, maybe fit_center instead of just center + android:adjustViewBounds="true” will fit your purposes... another way is to create ImageView with wrap_content sizes placed in some container (e.g. RelativeLayout) with match_parent sizes
also check this visual guide for ScaleType
note that 70px image will be big on devices with HD resolution and significantly smaller on those with e.g. full HD. you should have few versions of your image in proper density buckets (mdpi, hdpi etc.) or just download proper size if your image comes frome some API, so then you can say that you have image with 70dp dimension, not 70px
I am developing an app that deals with images and applying filters, borders etc to the image..(like photo editing app).
I am facing issue when applying filters or borders to HD images (as images will be of different size and resolution).
My Question is how do I apply filters without changing the size or dimension of the Image.
Please help regarding this, Thanks in advance.
As I mentioned in the comment section of the Code, for smaller size images getDrawingCache is returning a value, but for the bigger size images it is returning null.
public Bitmap getBorderAppliedBitmap(PorterShapeImageView imgView , GPUImage gpuImage)
{
Bitmap bmp = gpuImage.getBitmapWithFilterApplied();
imgView.setImageBitmap(bmp);
imgView.setDrawingCacheEnabled(true);
imgView.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
imgView.layout(0, 0,
imgView.getMeasuredWidth(), imgView.getMeasuredHeight());
imgView.buildDrawingCache(true);
Bitmap bmap = Bitmap.createBitmap(imgView.getDrawingCache());
<!-- getting null from the method imgView.getDraingCache() only for the bigger size Images -->
imgView.setDrawingCacheEnabled(false);
return bmap;
}
What you must be facing is limited Android resources issue for Image Processing techniques. The best advice for you would be to shift towards NDK and write image filters in JNI using OpenCV .
It is the best option for faster results in HD images. Any how there must be a boundary to the resolution to which an image can be processed because all professional apps create their own boundary parameters.
I have basic Android application created with Android Studio with single image loaded in ImageView like this:
ImageView iv = (ImageView)findViewById(R.id.imageView);
iv.setScaleType(ImageView.ScaleType.CENTER_CROP);
iv.setImageResource(R.drawable.g01);
Image size is 1280x853 pix, nothing huge. When I store the image in drawable folder runtime memory consumed by app is 24.35 MB vs 11.85 MB when image is stored in drawable-nodpi folder. Tested on xhdpi device with resolution of 1280x720 pix.
In my real life app, difference is even greater 75 MB vs 25 MB.
Why the difference? I thought that drawable and drawable-nodpi folders are basically the same and serve the same purpose?
Why the difference? I thought that drawable and drawable-nodpi folders
are basically the same and serve the same purpose?
No they are not. drawable scales its content per the device's density, drawable-nodpi doesn't. In the first case you will get a bitmap density time width/height the original one
I want to have a splash screen, something like a full picture, which doesn't crop in heigth or width on different smartphone screens.
Now I achieved a splash screen with android:scaleType="fitXY", but now the image is cropped on top or bottom or if the devices screen size changes to another aspect ratio it is cropped on the left and right.
What do I have to do? I've already read the android developer article Supporting Multiple Screens, but I don't get it how to achieve this.
A simple picture in the middle of the screen is just simple to get, but a picture which fills the screen is hard to get. Can you help me pls?
you should use center_crop per this purpose. From the doc
Scale the image uniformly (maintain the image's aspect ratio) so that
both dimensions (width and height) of the image will be equal to or
larger than the corresponding dimension of the view (minus padding).
There is no way to create one single asset and expect it to do not be cropper and to do not create black areas when the application is deployed in different screen sizes.
The android platform is designed to work dynamically with multiple screen sizes that any manufacture can change at any time, including new resolutions that you haven't thought about it yet.
Android can specify minimums for screen hight/width categories in which your resources will fall, but those are generics.
In order to use them, you will have to specify qualifiers in your drawables and create a different splash screen for every qualifier, as for example if you use drawable-w420dp, all the resources there will be used when the screen has a minimum width of 420dp (notice that are not pixels)
So you have two options:
You can use one single splash image and design margins of that image flexible enough in order to cope with the image being cropped in certain cases. You can play with different scaleTypes in your ImageView and take as a reference this website http://etcodehome.blogspot.co.uk/2011/05/android-imageview-scaletype-samples.html even though as commented before, "center-crop" will be your best shot.
You can programatically use a specific image for a specific resolution.
2.1 Put in the assets directory, all the splash images that you want for all the specific resolutions or aspect ratios that you want to use
2.1 Get the screen size of the device with Get screen dimensions in pixels
2.2 Now you can load from the assets the image that you want dynamically
Use the below code
android:layout_width="match_parent"
android:layout_height="match_parent"
which will fill the entire screen.
Try Using Width and Height of image to "match_parent"
I create 14 Bitmap objects like this:
bmp[0]=BitmapFactory.decodeResource(getResources(), R.drawable.a0000);
bmp[1]=BitmapFactory.decodeResource(getResources(), R.drawable.a0001);
bmp[2]=BitmapFactory.decodeResource(getResources(), R.drawable.a0002);
bmp[3]=BitmapFactory.decodeResource(getResources(), R.drawable.a0003);
bmp[4]=BitmapFactory.decodeResource(getResources(), R.drawable.a0004);
bmp[5]=BitmapFactory.decodeResource(getResources(), R.drawable.a0005);
bmp[6]=BitmapFactory.decodeResource(getResources(), R.drawable.a0006);
bmp[7]=BitmapFactory.decodeResource(getResources(), R.drawable.a0007);
bmp[8]=BitmapFactory.decodeResource(getResources(), R.drawable.a0008);
bmp[9]=BitmapFactory.decodeResource(getResources(), R.drawable.a0009);
bmp[10]=BitmapFactory.decodeResource(getResources(), R.drawable.a0010);
bmp[11]=BitmapFactory.decodeResource(getResources(), R.drawable.a0011);
bmp[12]=BitmapFactory.decodeResource(getResources(), R.drawable.a0012);
bmp[13]=BitmapFactory.decodeResource(getResources(), R.drawable.a0013);
those Bitmaps are used to make an animation of the background, the resources are 14 jpj images of 320x480 pixels. so I created a custom view and then call the bitmaps at the onDraw method. Then I test the app on my phone that have the same resolution of my images, it run smoothly, then I try to use the app on a tablet with a larger resolution(like 1280x720), but my background don't fit the entire screen, so I investigate how fill the entire screen by make a new bitmap with different resolution based on the device resolution and the result was this:
newWidth= context.getResources().getDisplayMetrics().widthPixels;
newHeight= context.getResources().getDisplayMetrics().heightPixels;
int contador=0;
do{
bmp[contador]= Bitmap.createScaledBitmap(bmp[contador], newWidth, newHeight,true);
contador++;
}while(contador<13);
Then I simply put these line on my onDraw method.
canvas.drawBitmap(bmp[i], 0, 0, null);
i++;
If I test the app on my phone it runs fine but when I test it on my tablet throws me the "bitmap size exceeds VM budget" error... So I investigate a little bit more and I had the following solution: Create the original 14 bmp Bitmap objects and make a second array of Bitmaps called bmp2 and then only assign the bitmap when I need it and then clear it like this on my onDraw method:
bmp2[i]= Bitmap.createScaledBitmap(bmp[i], newWidth, newHeight,true);
canvas.drawBitmap(bmp2[i], 0, 0, null);
bmp2[i].recycle();
bmp2[i]=null;
It work on my tablet and my phone but with a really bad performance I assume is because I assign the image in real time (on the onDraw method) but if I use the recycle method and don't assign again the bmp2 there will be not a new bmp2...
So finally my real question is: How can I clear some memory after I show my image and then when I need it again I can use it. Thanks!
Other have mentioned how scaling up small bitmaps to fill a large screen will not give high-quality results. But anyway, to answer your specific question, this is how you can draw your low-res bitmaps scaled up to fill the screen without memory errors.
You need to use one of the alternative Canvas.drawBitmap() methods which can scale your bitmap to the correct size while drawing.
For example:
public void drawBitmap (Bitmap bitmap, Rect src, RectF dst, Paint paint)
From the Javadoc:
Draw the specified bitmap, scaling/translating automatically to fill
the destination rectangle.
Set the dst rectangle to be the screen size you calculated earlier.
Then, you do not need to create separate scaled bitmaps in memory, so your problem goes away.
I am afraid there is no safe way of doing a fullscreen animation using scaled bitmaps.
Why?
Let's do some counting. Each your initial bitmap is 320x480, yes? That's 320*480*4*14=8601600, which is roughly 8Mb of memory. When you scale to 1280x720, you add up 1280*720*4*14=51609600 bytes, which is 49 megabytes. 49 + 8 = 57 megabytes. Given that the newest tablets have maximum of 64 megabytes allocated for your app VM, no surprise it won't fit. And even if it fits with your tablet, what would happen if a Nexus 10 owner would buy your app? N10 has 2560×1600 resolution (not to mention that scaled bitmap would look pathetic).
Allocating single bitmap for single frame will hardly let you have a smooth animation, I'm afraid. You could try keeping a window of 5 bitmaps at once and recycling others, but still I don't thik it would be enough. Besides that would consume ton of CPU work for constant bitmap scaling and will destroy your battery.
If you really want an animated background, you should probably look at either OpenGL, or TextureView.
Most of the times the GC would do the job.
You should have different bitmap resolutions handled by Android by folder (xhdpi, hdpi, etc.).
You should also check BitmapFactory.Options in order to have lower quality version Bitmaps generated by your BitmapFactory signatures.
Edit
You might also want to consider using WeakReferences to your Bitmaps.
You should design new bitmaps for your Tablets and Other Higher Screens and put them in the xhdpi folder under the res directory. This is the most efficient option. Let The Android OS itself pull the best fitting Resource for you.
See this for more detailed help
http://developer.android.com/training/multiscreen/index.html
This is what the documentation says
Although the system performs scaling and resizing to make your
application work on different screens, you should make the effort to
optimize your application for different screen sizes and densities. In
doing so, you maximize the user experience for all devices and your
users believe that your application was actually designed for their
devices—rather than simply stretched to fit the screen on their
devices.