I use these two functions, slightly modified from Google's code in the Android docs to use filepaths:
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 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);
}
With the idea being to use a scaled-down version of the Bitmap to be mapped onto an ImageView rather than the full-thing, which is wasteful.
mImageView.setImageBitmap(decodeSampledBitmapFromFilePath(pathToFile, 100, 100));
I implemented a thing where you press a button and it rotates to the next image, but there's still a significant lag (it takes a moment for the ImageView to populate) on my phone compared to my emulator. And then occasionally my phone app will crash and I can't replicate it on my emulator.
Is there a problem with this code I've posted above? Is there a problem with the way I am using the code?
Example:
public void reloadPic() {
new Thread(new Runnable() {
#Override
public void run() {
final Bitmap bm = decodeSampledBitmapFromFilePath(filepath, mImageViewWidth, mImageViewHeight);
getActivity().runOnUiThread(new Runnable() {
#Override
public void run() {
mImageView.setImageBitmap(bm);
}
});
}
}).start();
}
Your code is jumping between threads several times. First you launch a thread. Then you wait for it to be scheduled You decode on that thread. Then you post a command to the UI thread and wait for it to be scheduled. THen you post a draw command to the ui thread (that's part of what setImageBitmap does). Then you have to process any other commands that came in first. Then you actually draw the screen. There's really only 3 ways to speed this up:
1) Get rid of the thread. You shouldn't decode lots of images on the UI thread, but decoding 1 isn't too bad.
2)Store the images in the right size to begin with. This may mean creating thumbnails of the images ahead of time. Then you don't need to scale.
3)Preload your images. If there's only one button and you know what image it will load, load it before you need it, so when the button is pressed you have it ready. Wastes a bit of memory, but only 1 image worth. (This isn't a viable solution if you have a lot of possible next images).
Related
Using Xamarin and MvvmCross, I'm writing an Android application that is loading the images from an album into an MvxGridView with a custom binding:
<MvxGridView
android:id="#+id/grid_Photos"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:gravity="center"
android:numColumns="3"
android:verticalSpacing="4dp"
android:horizontalSpacing="4dp"
android:stretchMode="columnWidth"
android:fastScrollEnabled="true"
local:MvxBind="ItemsSource AllPhotos"
local:MvxItemTemplate="#layout/item_photo_thumbnail" />
Which uses item_photo_thumbnail.axml:
<ImageView
local:MvxBind="PicturePath PhotoPath"
style="#style/ImageView_Thumbnail" />
Here is the binding class:
public class PicturePathBinding : MvxTargetBinding
{
private readonly ImageView _imageView;
public PicturePathBinding(ImageView imageView)
: base(imageView)
{
_imageView = imageView;
}
public override MvxBindingMode DefaultMode
{
get { return MvxBindingMode.OneWay; }
}
public override Type TargetType
{
get { return typeof(string); }
}
public override void SetValue(object value)
{
if (value == null)
{
return;
}
string path = value as string;
if (!string.IsNullOrEmpty(path))
{
Java.IO.File imgFile = new Java.IO.File(path);
if (imgFile.Exists())
{
// First decode with inJustDecodeBounds=true to check dimensions
BitmapFactory.Options options = new BitmapFactory.Options();
options.InJustDecodeBounds = true;
BitmapFactory.DecodeFile(imgFile.AbsolutePath, options);
// Calculate inSampleSize
options.InSampleSize = CalculateInSampleSize(options, 100, 100);
// Decode bitmap with inSampleSize set
options.InJustDecodeBounds = false;
Bitmap myBitmap = BitmapFactory.DecodeFile(imgFile.AbsolutePath, options);
_imageView.SetImageBitmap(myBitmap);
}
}
}
protected override void Dispose(bool isDisposing)
{
if (isDisposing)
{
var target = Target as ImageView;
if (target != null)
{
target.Dispose();
target = null;
}
}
base.Dispose(isDisposing);
}
private int CalculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight)
{
// Raw height and width of image
int height = options.OutHeight;
int width = options.OutWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth)
{
int halfHeight = height / 2;
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;
}
}
The problem I'm having is that it is very sluggish and slow. I would love for the each image to load asynchronously. I don't know how to do that. In .net (XAML), their GridView control does everything automatically (with virtualization), but I'm realizing that in Android, it might have to be manually handled?
Can someone help me with this?
I am currently working with remote images, instead of local, so I have been using the Download Cache plugin that gives you the MvxImageView class. That in itself may give you some benefit.
So far my experience with Android is that everything runs if foreground by default, for the most part. Right now, with all of the calculation code inside of the binding class, that is almost certainly going to be run in the foreground.
What I would do to make this run faster is:
Use something like an ObservableCollection for your ItemsSource.
Kick off another thread in the start (or Start) of your View Model to add your items to the ObservableCollection. You can accomplish this easily with Task.Run()
Try to process as much as possible in that background thread with each item before adding it to the ObservableCollection
When updating ObservableCollection from the background thread, the actual update itself has to be done on the UI thread. This is easily done if you are using MvxViewModel as your base for your view model.
this.InvokeOnMainThread(() => myObservableCollection.Add(myItem) );
Following that pattern should actually help you Windows based clients as well.
I have 20 images in my app. All the images are in 640 * 360 resolution with not more than 60KB each.
I make use of Viewpager to slide the images. And use ViewFlipper inside ViewPager to flip the images.. When the user clicks on it, I show the corresponding text for the image.
The issue is that I get OutOfMemory exception when I swipe back and forth for 5 times. I read various Stackoverflow threads here!, here!, here! and Handling Large Bitmaps Efficiently! but not able to fix the issue,
Here is the code,
In Main_Fragment.java,
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) {
--- some code ---
--- some more code ---
View v = inflater.inflate(R.layout.main_fragment, container,false);
viewAnimator = ((ViewAnimator) v.findViewById(R.id.cardFlipper));
TextView caption_text = (TextView) v.findViewById(R.id.caption_text);
caption_text.setText(caption.toUpperCase());
ImageView main_img = (ImageView) v.findViewById(R.id.main_img);
int mainimg_resID = getResources().getIdentifier(mainimg,"drawable",context.getPackageName());
Bitmap icon = decodeSampledBitmapFromResource(getResources(),mainimg_resID,640,360);
main_img.setImageBitmap(icon);
viewAnimator.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
AnimationFactory.flipTransition((ViewAnimator) v,FlipDirection.LEFT_RIGHT);
}
});
return v;
}
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);
}
Can you please tell me what I'm doing wrong?? This is driving me nuts for the past few days :(
yes, I'm seen something very wrong that you are doing:
Bitmap icon = decodeSampledBitmapFromResource(getResources(),mainimg_resID,640,360);
you got it all wrong: the reqHeight and reqWidth parameters should be the image view width and height, and not the original image dimension!! that's all what calculateInSampleSize() is all about..
so, you should do the following:
Bitmap icon = decodeSampledBitmapFromResource(getResources(),mainimg_resID,main_img.getWidth(), main_img.getHeight());
by decoding bitmap in scale calculated based on the dimensions needed only for display - the result bitmap allocation would be minimal.
and by the way - the fact that the size of the resource you are using is only 60KB does not change anything. in fact - the image memory size (Kilobites/Megas) does not have any impact on the allocated bitmap. it's the resolution only that makes the difference!
EDIT
because the imageView dimentions are still zero at the onCreateView() method - you should prform the decoding only after it got it dimentions:
--- some code ---
--- some more code ---
View v = inflater.inflate(R.layout.main_fragment, container,false);
viewAnimator = ((ViewAnimator) v.findViewById(R.id.cardFlipper));
TextView caption_text = (TextView) v.findViewById(R.id.caption_text);
caption_text.setText(caption.toUpperCase());
final ImageView main_img = (ImageView) v.findViewById(R.id.main_img);
ViewTreeObserver vto = main_img.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
final ViewTreeObserver obs = main_img.getViewTreeObserver();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
obs.removeOnGlobalLayoutListener(this);
} else {
obs.removeGlobalOnLayoutListener(this);
}
// do here the image decoding + setting the image to the image view
Bitmap icon = decodeSampledBitmapFromResource(getResources(),mainimg_resID,main_img.getWidth(), main_img.getHeight());
// setImage....
}
});
viewAnimator.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
AnimationFactory.flipTransition((ViewAnimator) v,FlipDirection.LEFT_RIGHT);
}
});
return v;
EDIT 2
you should never use wrap_content for ImageView. that's because it forces the view to be in the size of the original image (potentially very large) instead of the opposite. when calculating required scale - the whole point is to compare between the ImageView dimentions to the original image(resource/file/stream) dimentions. that's why it does not make any sense use wrap_content.
Fellow developer, i seek your help.
I have a problem which is memory related. I don't really know how to tackle this so i will just present my code snippets. Bear in mind that, although it is on Xamarin.Android, it will also apply to normal Android.
I use a library which just starts a camera intent. The library (or component) i am using is : http://components.xamarin.com/view/xamarin.mobile. That is not really relevant, but maybe you can point me to other insights why i should or shouldn't be using this library.
Anyway, to start the camera and capture the input. I use the following code :
private void StartCamera() {
var picker = new MediaPicker (this);
if (! picker.IsCameraAvailable) {
Toast.MakeText (this, "No available camera found!", ToastLength.Short).Show ();
} else {
var intent = picker.GetTakePhotoUI (new StoreCameraMediaOptions{
Name = "photo.jpg",
Directory = "photos"
});
StartActivityForResult (intent, 1);
}
}
The onActivityForResult() method is called when i return from this camera intent. In this method, i do the following :
protected override async void OnActivityResult (int requestCode, Result resultCode, Intent data)
{
// User canceled
if (resultCode == Result.Canceled)
return;
System.GC.Collect ();
dialog = new ProgressDialog (this);
dialog.SetProgressStyle (ProgressDialogStyle.Spinner);
dialog.SetIconAttribute (Android.Resource.Attribute.DialogIcon);
dialog.SetTitle (Resources.GetString(Resource.String.dialog_picture_sending_title));
dialog.SetMessage (Resources.GetString(Resource.String.dialog_picture_sending_text));
dialog.SetCanceledOnTouchOutside (false);
dialog.SetCancelable (false);
dialog.Show ();
MediaFile file = await data.GetMediaFileExtraAsync (this);
await ConvertingAndSendingTask ( file );
dialog.Hide();
await SetupView ();
}
Then, in my ConvertingAndSendingTask() i convert the picture into the desired dimensions with a scaled bitmap. The code is as follows :
public async Task ConvertingAndSendingTask(MediaFile file) {
try{
System.GC.Collect();
int targetW = 1600;
int targetH = 1200;
BitmapFactory.Options options = new BitmapFactory.Options();
options.InJustDecodeBounds = true;
Bitmap b = BitmapFactory.DecodeFile (file.Path, options);
int photoW = options.OutWidth;
int photoH = options.OutHeight;
int scaleFactor = Math.Min(photoW/targetW, photoH/targetH);
options.InJustDecodeBounds = false;
options.InSampleSize = scaleFactor;
options.InPurgeable = true;
Bitmap bitmap = BitmapFactory.DecodeFile(file.Path, options);
float resizeFactor = CalculateInSampleSize (options, 1600, 1200);
Bitmap bit = Bitmap.CreateScaledBitmap(bitmap, (int)(bitmap.Width/resizeFactor),(int)(bitmap.Height/resizeFactor), false);
bitmap.Recycle();
System.GC.Collect();
byte[] data = BitmapToBytes(bit);
bit.Recycle();
System.GC.Collect();
await app.api.SendPhoto (data, app.ChosenAlbum.foreign_id);
bitmap.Recycle();
System.GC.Collect();
} catch(Exception e) {
System.Diagnostics.Debug.WriteLine (e.StackTrace);
}
}
Well, this method sends it good on newer devices with more memory but on lower end devices it ends up in a Out of memory error. Or anyway a nearly OOM. When Somethimes i goes well but when i want to take the second or third picture, it always ends up in OOM errors.
I realize that what i am doing is memory intensive. For example :
First i want the initial Width and Height of the original image.
Then it is sampled down (i don't really know if it is done well).
Then i load the sampled Bitmap into the memory.
When i loaded it into memory, my scaled bitmap has to be loaded in memory aswell prior to Recycle() the first Bitmap.
Ultimately i need a byte[] for sending the Bitmap over the web. But i need to convert it first prior to releasing my scaled Bitmap.
Then i release my scaled Bitmap and send the byte[].
Then as a final step, the byte[] needs to be released from memory aswell. I already do that on my BitmapToBytes() method as shown below, but i wanted to include it for maybe other insights.
static byte[] BitmapToBytes(Bitmap bitmap) {
byte[] data = new byte[0];
using (MemoryStream stream = new MemoryStream ())
{
bitmap.Compress (Bitmap.CompressFormat.Jpeg, 90, stream);
stream.Close ();
data = stream.ToArray ();
}
return data;
}
Does somebody sees any good parts where i can optimize this process? I know i am loading to much into memory but i can't think of another way.
It should be mentioned that i always want my images to be 1600x1200(Landscape) or 1200x1600(Portrait). I calculate that value in the following way :
public static float CalculateInSampleSize(BitmapFactory.Options options,
int reqWidth, int reqHeight) {
// Raw height and width of image
int height = options.OutHeight;
int width = options.OutWidth;
float inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
// Calculate ratios of height and width to requested height and
// width
float heightRatio = ((float) height / (float) reqHeight);
float widthRatio = ((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;
}
if(height < reqHeight || width < reqWidth) {
// Calculate ratios of height and width to requested height and
// width
float heightRatio = ((float) reqHeight / (float) height);
float widthRatio = ((float) reqWidth / (float) width);
// 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;
}
Does anybody has any recommendations or an alternative workflow?
I would be so much helped with this!
This might be a very delayed reply but may helpful to somebody who got the same issue.
Use the the calculated InSampleSize value (option)to decode file to
bitmap. this itself generates the scaled down bitmap instead of using in CreateScaledBitmap.
If you are expecting High resolution image as output then it is
difficult to handle OutOfMemory issue. Because An image with a higher
resolution does not provide any visible benefit, but still takes up
precious memory and incurs additional performance overhead due to
additional scaling performed by the view.[xamarin doc]
Calculate the bitmap target width and height relating to the imageview height and width. you can calculate this by MeasuredHeight and MeasuredWidth Property. [Note: This works only after complete image draw]
Consider using async method to decode file instead running on main thread [DecodeFileAsync]
For more detail follow this http://appliedcodelog.blogspot.in/2015/07/avoiding-imagebitmap.html
I created a clock widget that is updated from service (via broadcast receiver) every minute but after some hours it takes about 600mb of RAM.
The widget draws a bitmap every minute with some features and shows it by a simple ImageView.
At the beginning the widget occupies only a few kb of ram, but after a few minutes it takes hundreds of mb. There is a way to clear RAM before create the new bitmap?
This is a part of widget code:
public class Widget_01_Clock extends AppWidgetProvider {
#Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager,
int[] appWidgetIds) {
Bitmap clock = WidgetPaint.getClockBitmap();
RemoteViews updateViews = new RemoteViews(context.getPackageName(),R.layout.w_01_clock);
updateViews.setImageViewBitmap(R.id.w_01_clock, clock);
}
}
The reason for the is that every minute you load a new bitmap into your memory.
So your solution should be one of the following:
as already suggested recycle your used bitmap, for future use of this block of memory for next bitmaps.
work with really small images (thumbnails) that wont take so much space, and wont cause you an OutOfMemory exception.
Personally I think you should do both, here is a code to create a thumbnail version of your image file:
public static Bitmap decodeSampledBitmapFromFile(String path,
int reqWidth, int reqHeight) { // BEST QUALITY MATCH
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(path, options);
// Calculate inSampleSize
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
options.inPreferredConfig = Bitmap.Config.RGB_565;
int inSampleSize = 1;
if (height > reqHeight) {
inSampleSize = Math.round((float)height / (float)reqHeight);
}
int expectedWidth = width / inSampleSize;
if (expectedWidth > reqWidth) {
//if(Math.round((float)width / (float)reqWidth) > inSampleSize) // If bigger SampSize..
inSampleSize = Math.round((float)width / (float)reqWidth);
}
options.inSampleSize = inSampleSize;
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeFile(path, options);
}
I'm in the same boat that you are in, and I MAY have found a solution to keep it somewhat low. After lots of searching and failed results, I can get my clock widget (which has a background image up to 800x800, an overflow-menu image, time, and date -- all bitmaps) to normally go no higher than 45mb; if the user doesn't mess with the widget's settings and customization, it can sometimes stay below 20mb -- as I'm writing this, it's dropped to ~11MB. (then after messing with the customization, jumped to 41MB, and is now sitting at less than 10MB for about an hour now)
Your question says your clock has some features; I'm assuming this is an image of some sort? Well, I noticed with mine that, every minute, on every update, I redrew everything (background, menu, time, and date). I managed to break that in to three parts: menu (has to be separate for the pending intent), background + date (only gets updated when I have UPDATE intent or TIME CHANGED), and the time. I use a SparseArray in my Static class to hold the background + date bitmap, then when I'm only updating the time, I call that bitmap so I don't have to recreate it. Then when I set my remote views, I have something similar to this.
remoteViews.setImageViewBitmap(R.id.widget_consolidated, sparseArrayOfBitmaps.get(widgetNumber);
remoteViews.setImageViewBitmap(R.id.widget_time, functionToReturnTimeBitmap(arguments));
remoteViews.setImageViewBitmap(R.id.widget_overflow, functionToReturnMenuBitmap(arguments));
You might need to do something similar. It won't always keep the memory low (at least for me), but it does seem help.
I have an android App with plenty of animations.
When I programmatically create animations (using AnimationDrawable) the non-java object (as appears in DDMS Heap tab) grows with every new animation I load and never shrinks back even after my animations get released.
I have only one reference to each AnimationDrawable object from a wrapper object I wrote and I verified this object gets released by overriding the finalize method and making sure it gets called.
Eventually android stops loading images and prints "out of memory" errors to the log.
The interesting thing is that this happens only in some devices (Motorola Xoom, Sony Experia) and not in others (such as the Galaxy S).
This problem is not specific Honeycomb or pre-Honeycomb as you can see from the device examples I gave.
Some of the things I tried:
Calling recycle on each of the frames after I am done with the current animation but it doesn't seem to help.
Assigning null to the AnimationDrawble object
Making sure that there are no static variable related to the class holding the reference to the animation drawable
Make sure the problem disappears once I comment out myAnimation.addFrame(...)
This isn't an exact answer, but rather a helpful hint to find where the exact leak is occurring. Perform a heap-dump after you expect your memory to be reclaimed and see why the objects you think should be dead are still alive.
Make sure you get the memory analyzer tool for eclipse. (http://www.eclipse.org/mat/)
There could be two possible reason, first at the time of creating the bitmap and second when you are converting the bitmap into the BitmapDrawable. As i can see from your comment (new BitmapDrawable(currentFrameBitmap) now this method is depreciated better to use BitmapDrawable(getResources(),currentFrameBitmap) Without the Resources reference, the bitmap may not render properly, even when scaled correctly. To load bitmap efficiently you can scale it properly.
public class BitmapDecoderHelper {
private Context context;
public BitmapDecoderHelper(Context context){
this.context = context;
}
public 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("height reqheight width reqwidth", height+"//"+reqHeight+"//"+width+"///"+reqWidth);
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;
}
public Bitmap decodeSampledBitmapFromResource(String filePath,
int reqWidth, int reqHeight) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(filePath, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
Log.d("options sample size", options.inSampleSize+"///");
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
// out of memory occured easily need to catch and test the things.
return BitmapFactory.decodeFile(filePath, options);
}
public int getPixels(int dimensions){
Resources r = context.getResources();
int px = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dimensions, r.getDisplayMetrics());
return px;
}
public String getFilePath(Uri selectedImage){
String[] filePathColumn = {MediaStore.Images.Media.DATA};
Cursor cursor = context.getContentResolver().query(selectedImage, filePathColumn, null, null, null);
cursor.moveToFirst();
int columnIndex = cursor.getColumnIndex(filePathColumn[0]);
String filePath = cursor.getString(columnIndex);
cursor.close();
return filePath;
}
}