mirror of
https://github.com/apache/cordova-android.git
synced 2025-02-22 00:32:55 +08:00
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:
parent
8969eed506
commit
a691e9f744
@ -20,6 +20,7 @@ package org.apache.cordova;
|
|||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -43,6 +44,7 @@ import android.graphics.Matrix;
|
|||||||
import android.graphics.Bitmap.CompressFormat;
|
import android.graphics.Bitmap.CompressFormat;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.provider.MediaStore;
|
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,
|
* 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 retval = Bitmap.createScaledBitmap(bitmap, newWidth, newHeight, true);
|
||||||
bitmap.recycle();
|
bitmap.recycle();
|
||||||
|
System.gc();
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -310,20 +313,12 @@ public class CameraLauncher extends Plugin {
|
|||||||
// If image available
|
// If image available
|
||||||
if (resultCode == Activity.RESULT_OK) {
|
if (resultCode == Activity.RESULT_OK) {
|
||||||
try {
|
try {
|
||||||
// Read in bitmap of captured image
|
Bitmap bitmap = null;
|
||||||
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);
|
|
||||||
|
|
||||||
// If sending base64 image back
|
// If sending base64 image back
|
||||||
if (destType == DATA_URL) {
|
if (destType == DATA_URL) {
|
||||||
|
bitmap = scaleBitmap(getBitmapFromResult(intent));
|
||||||
|
|
||||||
this.processPicture(bitmap);
|
this.processPicture(bitmap);
|
||||||
checkForDuplicateImage(DATA_URL);
|
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
|
// Add compressed version of captured image to returned media store Uri
|
||||||
OutputStream os = this.cordova.getActivity().getContentResolver().openOutputStream(uri);
|
OutputStream os = this.cordova.getActivity().getContentResolver().openOutputStream(uri);
|
||||||
bitmap.compress(Bitmap.CompressFormat.JPEG, this.mQuality, os);
|
bitmap.compress(Bitmap.CompressFormat.JPEG, this.mQuality, os);
|
||||||
@ -390,7 +406,7 @@ public class CameraLauncher extends Plugin {
|
|||||||
Uri uri = intent.getData();
|
Uri uri = intent.getData();
|
||||||
android.content.ContentResolver resolver = this.cordova.getActivity().getContentResolver();
|
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
|
// and there will be no attempt to resize any returned data
|
||||||
if (this.mediaType != PICTURE) {
|
if (this.mediaType != PICTURE) {
|
||||||
this.success(new PluginResult(PluginResult.Status.OK, uri.toString()), this.callbackId);
|
this.success(new PluginResult(PluginResult.Status.OK, uri.toString()), this.callbackId);
|
||||||
@ -447,7 +463,7 @@ public class CameraLauncher extends Plugin {
|
|||||||
bitmap.recycle();
|
bitmap.recycle();
|
||||||
bitmap = null;
|
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.
|
// 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);
|
this.success(new PluginResult(PluginResult.Status.OK, ("file://" + fileName + "?" + System.currentTimeMillis())), this.callbackId);
|
||||||
System.gc();
|
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.
|
* Creates a cursor that can be used to determine how many images we have.
|
||||||
*
|
*
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
package org.apache.cordova;
|
package org.apache.cordova;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
|
||||||
@ -32,10 +33,11 @@ import org.json.JSONObject;
|
|||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.ContentValues;
|
import android.content.ContentValues;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.graphics.Bitmap;
|
import android.database.Cursor;
|
||||||
import android.graphics.BitmapFactory;
|
import android.graphics.BitmapFactory;
|
||||||
import android.media.MediaPlayer;
|
import android.media.MediaPlayer;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import android.provider.MediaStore;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
public class Capture extends Plugin {
|
public class Capture extends Plugin {
|
||||||
@ -61,6 +63,7 @@ public class Capture extends Plugin {
|
|||||||
private double duration; // optional duration parameter for video recording
|
private double duration; // optional duration parameter for video recording
|
||||||
private JSONArray results; // The array of results to be returned to the user
|
private JSONArray results; // The array of results to be returned to the user
|
||||||
private Uri imageUri; // Uri of captured image
|
private Uri imageUri; // Uri of captured image
|
||||||
|
private int numPics; // Number of pictures before capture activity
|
||||||
|
|
||||||
//private CordovaInterface cordova;
|
//private CordovaInterface cordova;
|
||||||
|
|
||||||
@ -202,6 +205,9 @@ public class Capture extends Plugin {
|
|||||||
* Sets up an intent to capture images. Result handled by onActivityResult()
|
* Sets up an intent to capture images. Result handled by onActivityResult()
|
||||||
*/
|
*/
|
||||||
private void captureImage() {
|
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);
|
Intent intent = new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE);
|
||||||
|
|
||||||
// Specify file so that large image is captured and returned
|
// 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
|
// 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
|
// To work around it I had to grab the code from CameraLauncher.java
|
||||||
try {
|
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
|
// Create entry in media store for image
|
||||||
// (Don't use insertImage() because it uses default compression setting of 50 - no way to change it)
|
// (Don't use insertImage() because it uses default compression setting of 50 - no way to change it)
|
||||||
ContentValues values = new ContentValues();
|
ContentValues values = new ContentValues();
|
||||||
@ -281,23 +279,22 @@ public class Capture extends Plugin {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
FileInputStream fis = new FileInputStream(DirectoryManager.getTempDirectoryPath(this.cordova.getActivity()) + "/Capture.jpg");
|
||||||
// Add compressed version of captured image to returned media store Uri
|
|
||||||
OutputStream os = this.cordova.getActivity().getContentResolver().openOutputStream(uri);
|
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();
|
os.close();
|
||||||
|
fis.close();
|
||||||
bitmap.recycle();
|
|
||||||
bitmap = null;
|
|
||||||
System.gc();
|
|
||||||
|
|
||||||
// Restore exif data to file
|
|
||||||
exif.createOutFile(FileUtils.getRealPathFromURI(uri, this.cordova));
|
|
||||||
exif.writeExifData();
|
|
||||||
|
|
||||||
// Add image to results
|
// Add image to results
|
||||||
results.put(createMediaFile(uri));
|
results.put(createMediaFile(uri));
|
||||||
|
|
||||||
|
checkForDuplicateImage();
|
||||||
|
|
||||||
if (results.length() >= limit) {
|
if (results.length() >= limit) {
|
||||||
// Send Uri back to JavaScript for viewing image
|
// Send Uri back to JavaScript for viewing image
|
||||||
this.success(new PluginResult(PluginResult.Status.OK, results), this.callbackId);
|
this.success(new PluginResult(PluginResult.Status.OK, results), this.callbackId);
|
||||||
@ -405,4 +402,36 @@ public class Capture extends Plugin {
|
|||||||
public void fail(JSONObject err) {
|
public void fail(JSONObject err) {
|
||||||
this.error(new PluginResult(PluginResult.Status.ERROR, err), this.callbackId);
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user