使用camera.getPicture拍照后,Cordova / phonegap崩溃了

时间:2013-06-13 09:09:52

标签: android cordova

我正在使用Cordova 2.6开发一个应用程序,我在旧款手机上使用camera.getPicture功能时遇到问题(没有内存演出)。

当应用程序打开相机时,它(应用程序)会移动到后台。然后Android的垃圾收集器启动并杀死应用程序。因此,当我拍摄照片并将其返回到我的应用程序时,它会因空指针异常而崩溃(强制关闭)。

这个问题众所周知,但没有记录为'怪癖'。

这是其他有同样问题的人: Camera example from the docs page fails on android 2.3.x

我遇到的最大问题是我无法察觉到这一点。如果可以,我可能会给用户一个警告,但现在它只是强行关闭并破坏了体验。

编辑:这是logcat的例外:

java.lang.RuntimeException: Failure delivering result ResultInfo{who=null, request=0, result=-1, data=null} to activity

帮助?

2 个答案:

答案 0 :(得分:0)

一种解决方案是创建自定义相机插件并将图像保存到设定的图像文件中。当应用程序重新加载时,由于内存检查是否存在该文件而导致崩溃或强制关闭,并继续。

这需要在调用相机之前保存一个状态,以便应用程序可以重新初始化以处理图像。

我认为检查之前拍摄的图像的地方可能是主类中的onCreate方法或相机插件中的onActivityResult

这是一个类似的帖子Taking a picture from the camera fails 20% of the time

Bellow代码就是我已经开始的,但处理所有不同的相机错误可能会有问题,即Android ACTION_IMAGE_CAPTURE Intent

要使此功能适用于此任务,您需要使用getExternalSaveFile方法保存文件,然后修改oncreate / onactivityresult以处理重新启动应用程序。

这是调用插件的Javascript

        CameraSW.takePhoto("onPhotoURISuccess");
    //Then, to get the location of the photo after you take it and load the page again
    var imgPath = CameraSW.getPhotoUri();
    console.log("capturePhotoEdit: imgPath " + imgPath);


function onPhotoURISuccess(imageURI) {

}

注册您的主应用程序,以便您可以通过javascript

访问这些功能
    static public Uri getCameraSaveFile(Context context, String directory,
        String basename, boolean fixed, String ext) {
    String fileName =   your_app.getImageFileName(basename, fixed,ext);
    //"" + System.currentTimeMillis() + ".jpg";

    ContentValues values = new ContentValues();

    values.put(MediaStore.Images.Media.TITLE, fileName);

    values.put(MediaStore.Images.Media.DESCRIPTION,
            "Image capture by camera");

    values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");

    Uri imageUri = context.getContentResolver().insert(
            MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);

    Log.i("MainActivity",
            "new image uri to string is " + imageUri.toString());

    Log.i("MainActivity", "new image path is " + imageUri.getPath());
    return imageUri;
}

static public Uri getExternalSaveFile(String directory, String basename,
        boolean fixed, String ext) {
    try {
        Uri newImageUri = null;
        if (ext == null)
            ext = "jpg";
        String dir_name = Environment.getExternalStorageDirectory()
                .getPath() + "/" + directory + "/";
        File path = new File(dir_name);
        Log.e(TAG, "getExternalSaveFile path = "
                +Environment.getExternalStorageDirectory().getPath() + "/"
                + directory + "/");
 // crashing here ? wtf
        if (!path.exists()) {
            try {
                Log.i(TAG, "getExternalSaveFile: mkdirs" + dir_name);
                path.mkdirs();
            } catch (Exception e) {
                Log.e(TAG,
                        "getExternalSaveFile: mkdir error" + e.getMessage());

            }
        }
        if (!path.isDirectory()) {
            Log.e(TAG, "getExternalSaveFile: file is not directory"
                    + dir_name);
            return null;
        }
        boolean setWritable = false;

        setWritable = path.setWritable(true, false);
        if (!setWritable) {
            Log.e(TAG,
                    "getExternalSaveFile Failed to set the file to writable");
        }
        File file = new File(path, your_app.getImageFileName(basename, fixed,
                ext));

        newImageUri = Uri.fromFile(file);

        Log.i(TAG, "getExternalSaveFile new image uri to string is "
                + newImageUri.toString());

        Log.i(TAG,
                "getExternalSaveFile new image path is "
                        + newImageUri.getPath());
        return newImageUri;
    } catch (Exception e) {
        Log.e(TAG, "getExternalSaveFile: main error" + e.getMessage());

    }
    return null;
}

static public String getImageFileName(String basename, boolean fixed,
        String ext) {
    String file = null;
    if (fixed) {
        file = basename + "." + ext;
    } else {
        file = basename + System.currentTimeMillis() + "." + ext;
    }
    return file;
}

public void takePhoto(final String callback) {
    Log.v(TAG, "takePhoto: Starting takePhoto: " + callback);

    Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
    Log.v(TAG, "takePhoto: new intent");
    //Uri path = getExternalSaveFile("your_app", "question_", true, "jpg");
    Uri path = getCameraSaveFile(this,"your_app", "question_", true, "jpg");
    if (path == null) {
        Log.e(TAG, "takePhoto: path is not writable or can not be found");
        // should pass error on ward to phonegap
        return;
    }
    image_uri  = path.toString();
    intent.putExtra(MediaStore.EXTRA_OUTPUT, path);
    Log.v(TAG, "takePhoto: getExternalSaveFile");

    intent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 1);

    // Intent intent = new
    // Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE);
    // intent.putExtra(android.provider.MediaStore.EXTRA_OUTPUT,
    // Uri.fromFile(new File(folderPath, filePath)));
    if (your_app.webView != null && your_app.webView.pluginManager != null) {

        CameraSWPlugin plugin = (CameraSWPlugin)your_app.webView.pluginManager.getPlugin("CameraSW");
        if (plugin == null) {
            Log.e(TAG, "takePhoto: startActivityForResult: plugin CameraSWPlugin null make sure plugin is listed in config.xml <plugin name=\"CameraSW\" value=\"com.your_app.your_app.CameraSWPlugin\" />");
            throw new NullPointerException("takePhoto: startActivityForResult: plugin CameraSWPlugin null make sure plugin is listed in config.xml <plugin name=\"CameraSW\" value=\"com.your_app.your_app.CameraSWPlugin\" />");
        } else {
            plugin.setPath(path);
            plugin.setSuccess_callback(callback);
        startActivityForResult(
                plugin,
                intent, TAKE_PICTURE);
        Log.v(TAG, "takePhoto: startActivityForResult");
        }
    } else {
        // should not happen
        Log.v(TAG, "webView or Pluginmanager not read");
    }
}

public String getPhotoUri() {
    return image_uri;
//      return Uri.fromFile(new File(folderPath, filePath)).toString();
}

插件文件CameraSWPlugin.java

package com.your_app.your_app;

import static com.your_app.your_app.CommonUtilities.TAKE_PICTURE;

import java.io.File;

import org.apache.cordova.api.CallbackContext;
import org.apache.cordova.api.CordovaPlugin;
import org.apache.cordova.api.PluginResult;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Environment;
import android.util.Log;
import static com.your_app.your_app.CommonUtilities.TAG;
/**
 *   I did not include the    
 * This class echoes a string called from JavaScript.
 */
public class CameraSWPlugin extends CordovaPlugin {
    @Override
    public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
        if (action.equals("echo")) {
            String message = args.getString(0); 
            your_app.invalidate();
            this.echo(message, callbackContext);
            return true;
        }
        return false;
    }

    private void echo(String message, CallbackContext callbackContext) {
        if (message != null && message.length() > 0) { 
            callbackContext.success(message);
        } else {
            callbackContext.error("Expected one non-empty string argument.");
        }
    }



@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    Log.v(TAG, "Photo onActivityResult requestCode = " + requestCode + " resultCode = " + resultCode + " data = " + data);
    switch (requestCode) {
        case TAKE_PICTURE:
            if (resultCode == Activity.RESULT_OK) {
                //Do whatever you need to do when the camera returns
                //This is after the picture is already saved, we return to the page
                if (this.path != null) {
                    // 

                    try {
                        this.sendJavascript(path.toString());
                    } catch (Exception e) {
                        Log.v(TAG, "onActivityResult: path json error " + e.getMessage());                  
                        e.printStackTrace();
                    }
                } else {
                    Log.v(TAG, "onActivityResult: path was not set " + requestCode);                    
                }
            }
            break;
        default:
            Log.v(TAG, "Something strange happened... " + requestCode);
            break;
    }
}
private String success_callback = null;
private Uri path = null;
public String getSuccess_callback() {
    return success_callback;
}

public void setSuccess_callback(String success_callback) {
    this.success_callback = success_callback;
}

public  void sendJavascript( JSONObject _json )
{

String _d =  "javascript:"+this.success_callback+"(" + _json.toString() + ")";
      Log.v(TAG + ":sendJavascript", _d);

      if (this.success_callback != null ) {
          this.webView.sendJavascript( _d );
      }
}


public  void sendJavascript( String _json )
{

String _d =  "javascript:"+this.success_callback+"(" + JSONObject.quote(_json) + ")";
      Log.v(TAG + ":sendJavascript", _d);

      if (this.success_callback != null ) {
          this.webView.sendJavascript( _d );
      }
}

public void setPath(Uri path) {
    this.path = path;   
}

}

请务必添加到config.xml

 <plugin name="CameraSW" value="com.your_app.your_app.CameraSWPlugin" />

答案 1 :(得分:0)

对于我来说,相机插件中有一个错误。我根据我的情况修复了它。这可能适用于您的,也可能不适用。

场景:

  1. 使用平板电脑,其中 NATIVE CAMERA APP 在启动时旋转为横向。
  2. 我的应用正在纵向运行。因此,它需要保存实例变量并重新加载它们。

我发现了什么以及我修复了什么:

首先看 ProcessResultFromCamera 函数 -> 这些行:

        String sourcePath = (this.allowEdit && this.croppedUri != null) ?
                this.croppedFilePath :
                this.imageFilePath;

它正在使用 imageUri 但保存了 imageFilePath?也许这是有意的,也许不是。

我添加了一个快速调试助手类来推出 Toast。我在整个 CameraLauncher.java 中都添加了 Toast。

我查看了 onSaveInstanceState 并注意到了一些事情:

  1. imageFilePath 未存储在 onSaveInstanceState 中的任何位置,因此当 Activity 重新创建自身时...最初创建的 imageFilePath 现在为 null。这个 imageFilePath 是传入 ExifInterface 的内容。如果您注意到以上...sourcePath 已分配给 imageFilePath。

这将解释为什么我们得到“文件名不能为空”,因为我相信在 ExifInterface.java 中抛出了特定的异常。

                **_exif.createInFile(sourcePath);_**  <<---- sourcePath is null
                exif.readExifData();
                rotate = exif.getOrientation();

        if (this.**imageUri** != null) {
            state.putString(IMAGE_URI_KEY, this.**imageFilePath**);
        }

修复??

更新 onSaveInstanceState 中的 imageUri。将 imageFileName 添加到 onSaveInstanceState。

将 imageFileName 添加到 onRestore

这是在 onSaveInstanceState 中更新/添加的实际代码:

        if (this.imageUri != null) {
            state.putString(IMAGE_URI_KEY, String.valueOf(this.imageUri));
        }

        if (this.imageFilePath != null) {
            state.putString(IMAGE_FILE_PATH_KEY, this.imageFilePath);
        }

这是在 onRestoreStateForActivityResult 中添加的代码:

        if (state.containsKey(IMAGE_FILE_PATH_KEY)) {
            this.imageFilePath = state.getString(IMAGE_FILE_PATH_KEY);
        }