Thursday, April 19, 2012

Loading Large Bitmaps Efficiently in Android

Bitmaps can quickly consume application's available memory budget leading to an application crash due to the dreaded exception:
java.lang.OutofMemoryError: bitmap size exceeds VM budget
  So working with limited memory, ideally we only want to load a lower resolution version of image in memory.The lower resolution version should match the size of the UI component that displays it.For this we have to consider some factors.

  • Estimated memory usage of loading the full image in memory.
  • Amount of memory you are willing to commit to loading this image given any other memory requirements of your application.
  • Dimensions of the target ImageView or UI component that the image is to be loaded into.
  • Screen size and density of the current device.
Here is another simple example for sampling the images. 

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;
}

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);
}

// for loading an image with size 100 *100 

mImageView.setImageBitmap(
    decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100));


Reference : http://developer.android.com/training/displaying-bitmaps/index.html

Wednesday, April 11, 2012

Image resize in Android

Some times we need to  reduce the size of images retrieved from camera down to  a few smaller sizes or situations like  OutOfMemoryError due to  bitmap size  , images that showing in a list view or grid view  etc.  Then we resize the picture for reducing the memory usage .  Here is an simple example for doing this. 
/**
  * This function is used to read the original image file from a given path
  * and then scale it and save it to the application's private scope.
  * 
  * @param sourceImageFilePath Complete path name for the file to be opened.
  * @param desiredWidth The desired image width for this image.
  * @param context To identify the context's application package for writing a private image file.
  * @param destinationImageName The file name of the image to be saved within the applications private scope.
  * @return The scaled bitmap image after saved to the destination file.
  */
 
 public static Bitmap saveGalleryImageBitmap(String sourceImageFilePath, int desiredWidth, Context context, String destinationImageName, int rotate) {
  Bitmap scaledBitmap = null;
  Bitmap rotatedBitmap = null;
  try {
   // Get the source image's dimensions
   BitmapFactory.Options options = new BitmapFactory.Options();
   options.inJustDecodeBounds = true;
   BitmapFactory.decodeFile(sourceImageFilePath, options);
 
   int srcWidth = options.outWidth;
 
   // Only scale if the source is big enough. This code is just trying to fit a image into a certain width.
   if (desiredWidth > srcWidth)
    desiredWidth = srcWidth;
 
   // Calculate the correct inSampleSize/scale value. This helps reduce
   // memory use. It should be a power of 2
   // from:
   // http://stackoverflow.com/questions/477572/android-strange-out-of-memory-issue/823966#823966
   int inSampleSize = 1;
   while (srcWidth / 2 > desiredWidth) {
    srcWidth /= 2;
    inSampleSize *= 2;
   }
 
   float desiredScale = (float) desiredWidth / srcWidth;
 
   // Decode with inSampleSize
   options.inJustDecodeBounds = false;
   options.inDither = false;
   options.inSampleSize = inSampleSize;
   options.inScaled = false;
   options.inPreferredConfig = Bitmap.Config.ARGB_8888;  
   Bitmap sampledSrcBitmap = BitmapFactory.decodeFile(sourceImageFilePath, options);
 
   // Resize
   Matrix matrix = new Matrix();
   matrix.postScale(desiredScale, desiredScale);
   scaledBitmap = Bitmap.createBitmap(sampledSrcBitmap, 0, 0, sampledSrcBitmap.getWidth(), sampledSrcBitmap.getHeight(), matrix, true);
   sampledSrcBitmap = null;
 
   // Save
   FileOutputStream out = null;
   try {
    out = context.openFileOutput(destinationImageName, Context.MODE_PRIVATE);
   } catch (FileNotFoundException e) {
    e.printStackTrace();
   }
   
   Matrix rotateMatrix = new Matrix();
   rotateMatrix.postRotate(rotate); // For rotating the image.
   rotatedBitmap = Bitmap.createBitmap(scaledBitmap, 0, 0, scaledBitmap.getWidth(), scaledBitmap.getHeight(), rotateMatrix, true);
   scaledBitmap = null;
   
   rotatedBitmap.compress(Bitmap.CompressFormat.JPEG, 100, out);   
  } catch (OutOfMemoryError e) {
   scaledBitmap = null;
   e.printStackTrace();
  } catch (Exception e) {
   scaledBitmap = null;
   e.printStackTrace();
  }
  return rotatedBitmap;
 }

Tuesday, April 3, 2012

GPS Location in Android Devices

In order to get started with the location capabilities of the Android API, the first step is to take reference of the LocationManager class, which provides access to the system location services. This is done via the getSystemService of our activity (actually it inherits it from the Context parent class). Then, we request updates of the device's location using the methodrequestLocationUpdates. In that method, we provide the name of the preferred location provider (in our case GPS), the minimum time interval for notifications (in milliseconds), the minimum distance interval for notifications (in meters) and finally a class implementing the LocationListener interface. That interface declares methods for handling changes in the user's location as well as changes in the location provider's status. All the above can be translated into code as below:



package com.example.android.lbs;

import android.app.Activity;
import android.content.Context;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.Toast;

public class GPSActivity extends Activity {
    
    private static final long MINIMUM_DISTANCE_CHANGE_FOR_UPDATES = 1; // in Meters
    private static final long MINIMUM_TIME_BETWEEN_UPDATES = 1000; // in Milliseconds
    
    protected LocationManager locationManager;
    
    protected Button retrieveLocationButton;
    
    @Override
    public void onCreate(Bundle savedInstanceState) {
        
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        retrieveLocationButton = new Button(this);
 retrieveLocationButton.setText("GPS");
 addContentView(retrieveLocationButton, new LayoutParams(android.view.ViewGroup.LayoutParams.WRAP_CONTENT ,                             android.view.ViewGroup.LayoutParams.FILL_PARENT));

        
        locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
        
        locationManager.requestLocationUpdates(
                LocationManager.GPS_PROVIDER, 
                MINIMUM_TIME_BETWEEN_UPDATES, 
                MINIMUM_DISTANCE_CHANGE_FOR_UPDATES,
                new MyLocationListener()
        );
        
    retrieveLocationButton.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                showCurrentLocation();
            }
    });        
        
    }    

    protected void showCurrentLocation() {

        Location location = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);

        if (location != null) {
            String message = String.format(
                    "Current Location \n Longitude: %1$s \n Latitude: %2$s",
                    location.getLongitude(), location.getLatitude()
            );
            Toast.makeText(LbsGeocodingActivity.this, message,
                    Toast.LENGTH_LONG).show();
        }

    }   

    private class MyLocationListener implements LocationListener {

        public void onLocationChanged(Location location) {
            String message = String.format(
                    "New Location \n Longitude: %1$s \n Latitude: %2$s",
                    location.getLongitude(), location.getLatitude()
            );
            Toast.makeText(LbsGeocodingActivity.this, message, Toast.LENGTH_LONG).show();
        }

        public void onStatusChanged(String s, int i, Bundle b) {
            Toast.makeText(LbsGeocodingActivity.this, "Provider status changed",
                    Toast.LENGTH_LONG).show();
        }

        public void onProviderDisabled(String s) {
            Toast.makeText(LbsGeocodingActivity.this,
                    "Provider disabled by the user. GPS turned off",
                    Toast.LENGTH_LONG).show();
        }

        public void onProviderEnabled(String s) {
            Toast.makeText(LbsGeocodingActivity.this,
                    "Provider enabled by the user. GPS turned on",
                    Toast.LENGTH_LONG).show();
        }

    }
    
}
In order to be able to run the above code, the necessary permissions have to be granted. These are:
1. ACCESS_FINE_LOCATION
2. ACCESS_MOCK_LOCATION
3. ACCESS_COARSE_LOCATION
Include those in the AndroidManifest.xml file, which will be as follows:

 <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
 <uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION" />
 <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />



Read more: http://www.javacodegeeks.com/2010/09/android-location-based-services.html