CB-910: Camera out of memory error

Whenever possible do not load the image into a Bitmap as it takes too much memory and blows up the Java heap.
This commit is contained in:
macdonst 2012-06-19 10:51:19 -04:00
parent 8969eed506
commit a691e9f744
2 changed files with 91 additions and 33 deletions

View File

@ -20,6 +20,7 @@ package org.apache.cordova;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
@ -43,6 +44,7 @@ import android.graphics.Matrix;
import android.graphics.Bitmap.CompressFormat;
import android.net.Uri;
import android.provider.MediaStore;
import android.util.Log;
/**
* This class launches the camera view, allows the user to take a picture, closes the camera view,
@ -277,6 +279,7 @@ public class CameraLauncher extends Plugin {
Bitmap retval = Bitmap.createScaledBitmap(bitmap, newWidth, newHeight, true);
bitmap.recycle();
System.gc();
return retval;
}
@ -310,20 +313,12 @@ public class CameraLauncher extends Plugin {
// If image available
if (resultCode == Activity.RESULT_OK) {
try {
// Read in bitmap of captured image
Bitmap bitmap;
try {
bitmap = android.provider.MediaStore.Images.Media.getBitmap(this.cordova.getActivity().getContentResolver(), imageUri);
} catch (FileNotFoundException e) {
Uri uri = intent.getData();
android.content.ContentResolver resolver = this.cordova.getActivity().getContentResolver();
bitmap = android.graphics.BitmapFactory.decodeStream(resolver.openInputStream(uri));
}
bitmap = scaleBitmap(bitmap);
Bitmap bitmap = null;
// If sending base64 image back
if (destType == DATA_URL) {
bitmap = scaleBitmap(getBitmapFromResult(intent));
this.processPicture(bitmap);
checkForDuplicateImage(DATA_URL);
}
@ -348,6 +343,27 @@ public class CameraLauncher extends Plugin {
}
}
// If all this is true we shouldn't compress the image.
if (this.targetHeight == -1 && this.targetWidth == -1 && this.mQuality == 100) {
FileInputStream fis = new FileInputStream(FileUtils.stripFileProtocol(imageUri.toString()));
OutputStream os = this.cordova.getActivity().getContentResolver().openOutputStream(uri);
byte[] buffer = new byte[4096];
int len;
while ((len = fis.read(buffer)) != -1) {
os.write(buffer, 0, len);
}
os.flush();
os.close();
fis.close();
checkForDuplicateImage(FILE_URI);
this.success(new PluginResult(PluginResult.Status.OK, uri.toString()), this.callbackId);
return;
}
bitmap = scaleBitmap(getBitmapFromResult(intent));
// Add compressed version of captured image to returned media store Uri
OutputStream os = this.cordova.getActivity().getContentResolver().openOutputStream(uri);
bitmap.compress(Bitmap.CompressFormat.JPEG, this.mQuality, os);
@ -390,7 +406,7 @@ public class CameraLauncher extends Plugin {
Uri uri = intent.getData();
android.content.ContentResolver resolver = this.cordova.getActivity().getContentResolver();
// If you ask for video or all media type you will automatically get back a file URI
// If you ask for video or all media type you will automatically get back a file URI
// and there will be no attempt to resize any returned data
if (this.mediaType != PICTURE) {
this.success(new PluginResult(PluginResult.Status.OK, uri.toString()), this.callbackId);
@ -447,7 +463,7 @@ public class CameraLauncher extends Plugin {
bitmap.recycle();
bitmap = null;
// The resized image is cached by the app in order to get around this and not have to delete you
// The resized image is cached by the app in order to get around this and not have to delete you
// application cache I'm adding the current system time to the end of the file url.
this.success(new PluginResult(PluginResult.Status.OK, ("file://" + fileName + "?" + System.currentTimeMillis())), this.callbackId);
System.gc();
@ -471,6 +487,19 @@ public class CameraLauncher extends Plugin {
}
}
private Bitmap getBitmapFromResult(Intent intent)
throws IOException, FileNotFoundException {
Bitmap bitmap = null;
try {
bitmap = android.provider.MediaStore.Images.Media.getBitmap(this.ctx.getActivity().getContentResolver(), imageUri);
} catch (FileNotFoundException e) {
Uri uri = intent.getData();
android.content.ContentResolver resolver = this.ctx.getActivity().getContentResolver();
bitmap = android.graphics.BitmapFactory.decodeStream(resolver.openInputStream(uri));
}
return bitmap;
}
/**
* Creates a cursor that can be used to determine how many images we have.
*

View File

@ -19,6 +19,7 @@
package org.apache.cordova;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
@ -32,10 +33,11 @@ import org.json.JSONObject;
import android.app.Activity;
import android.content.ContentValues;
import android.content.Intent;
import android.graphics.Bitmap;
import android.database.Cursor;
import android.graphics.BitmapFactory;
import android.media.MediaPlayer;
import android.net.Uri;
import android.provider.MediaStore;
import android.util.Log;
public class Capture extends Plugin {
@ -61,6 +63,7 @@ public class Capture extends Plugin {
private double duration; // optional duration parameter for video recording
private JSONArray results; // The array of results to be returned to the user
private Uri imageUri; // Uri of captured image
private int numPics; // Number of pictures before capture activity
//private CordovaInterface cordova;
@ -202,6 +205,9 @@ public class Capture extends Plugin {
* Sets up an intent to capture images. Result handled by onActivityResult()
*/
private void captureImage() {
// Save the number of images currently on disk for later
this.numPics = queryImgDB().getCount();
Intent intent = new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE);
// Specify file so that large image is captured and returned
@ -256,14 +262,6 @@ public class Capture extends Plugin {
// It crashes in the emulator and on my phone with a null pointer exception
// To work around it I had to grab the code from CameraLauncher.java
try {
// Create an ExifHelper to save the exif data that is lost during compression
ExifHelper exif = new ExifHelper();
exif.createInFile(DirectoryManager.getTempDirectoryPath(this.cordova.getActivity()) + "/Capture.jpg");
exif.readExifData();
// Read in bitmap of captured image
Bitmap bitmap = android.provider.MediaStore.Images.Media.getBitmap(this.cordova.getActivity().getContentResolver(), imageUri);
// Create entry in media store for image
// (Don't use insertImage() because it uses default compression setting of 50 - no way to change it)
ContentValues values = new ContentValues();
@ -281,23 +279,22 @@ public class Capture extends Plugin {
return;
}
}
// Add compressed version of captured image to returned media store Uri
FileInputStream fis = new FileInputStream(DirectoryManager.getTempDirectoryPath(this.cordova.getActivity()) + "/Capture.jpg");
OutputStream os = this.cordova.getActivity().getContentResolver().openOutputStream(uri);
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, os);
byte[] buffer = new byte[4096];
int len;
while ((len = fis.read(buffer)) != -1) {
os.write(buffer, 0, len);
}
os.flush();
os.close();
bitmap.recycle();
bitmap = null;
System.gc();
// Restore exif data to file
exif.createOutFile(FileUtils.getRealPathFromURI(uri, this.cordova));
exif.writeExifData();
fis.close();
// Add image to results
results.put(createMediaFile(uri));
checkForDuplicateImage();
if (results.length() >= limit) {
// Send Uri back to JavaScript for viewing image
this.success(new PluginResult(PluginResult.Status.OK, results), this.callbackId);
@ -405,4 +402,36 @@ public class Capture extends Plugin {
public void fail(JSONObject err) {
this.error(new PluginResult(PluginResult.Status.ERROR, err), this.callbackId);
}
/**
* Creates a cursor that can be used to determine how many images we have.
*
* @return a cursor
*/
private Cursor queryImgDB() {
return this.cordova.getActivity().getContentResolver().query(
android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
new String[] { MediaStore.Images.Media._ID },
null,
null,
null);
}
/**
* Used to find out if we are in a situation where the Camera Intent adds to images
* to the content store.
*/
private void checkForDuplicateImage() {
Cursor cursor = queryImgDB();
int currentNumOfImages = cursor.getCount();
// delete the duplicate file if the difference is 2
if ((currentNumOfImages - numPics) == 2) {
cursor.moveToLast();
int id = Integer.valueOf(cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media._ID))) - 1;
Uri uri = Uri.parse(MediaStore.Images.Media.EXTERNAL_CONTENT_URI + "/" + id);
this.cordova.getActivity().getContentResolver().delete(uri, null, null);
}
}
}