You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cordova.apache.org by ma...@apache.org on 2012/06/20 17:03:38 UTC

android commit: CB-910: Camera out of memory error

Updated Branches:
  refs/heads/master 8969eed50 -> a691e9f74


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.


Project: http://git-wip-us.apache.org/repos/asf/incubator-cordova-android/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-cordova-android/commit/a691e9f7
Tree: http://git-wip-us.apache.org/repos/asf/incubator-cordova-android/tree/a691e9f7
Diff: http://git-wip-us.apache.org/repos/asf/incubator-cordova-android/diff/a691e9f7

Branch: refs/heads/master
Commit: a691e9f744a878d3f457d7c229a4c74889fa1cac
Parents: 8969eed
Author: macdonst <si...@gmail.com>
Authored: Tue Jun 19 10:51:19 2012 -0400
Committer: macdonst <si...@gmail.com>
Committed: Wed Jun 20 11:00:13 2012 -0400

----------------------------------------------------------------------
 .../src/org/apache/cordova/CameraLauncher.java     |   55 +++++++++---
 framework/src/org/apache/cordova/Capture.java      |   69 ++++++++++----
 2 files changed, 91 insertions(+), 33 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-cordova-android/blob/a691e9f7/framework/src/org/apache/cordova/CameraLauncher.java
----------------------------------------------------------------------
diff --git a/framework/src/org/apache/cordova/CameraLauncher.java b/framework/src/org/apache/cordova/CameraLauncher.java
index 1afaf8d..3520247 100755
--- a/framework/src/org/apache/cordova/CameraLauncher.java
+++ b/framework/src/org/apache/cordova/CameraLauncher.java
@@ -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.
      *

http://git-wip-us.apache.org/repos/asf/incubator-cordova-android/blob/a691e9f7/framework/src/org/apache/cordova/Capture.java
----------------------------------------------------------------------
diff --git a/framework/src/org/apache/cordova/Capture.java b/framework/src/org/apache/cordova/Capture.java
index a2dfafd..cd115d4 100644
--- a/framework/src/org/apache/cordova/Capture.java
+++ b/framework/src/org/apache/cordova/Capture.java
@@ -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);
+        }
+    }
 }