【问题标题】:Cordova/phonegap crashes after taking a picture with camera.getPicture使用 camera.getPicture 拍照后 Cordova/phonegap 崩溃
【发布时间】:2013-06-13 09:09:51
【问题描述】:

我正在使用 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

帮助?

【问题讨论】:

    标签: android cordova


    【解决方案1】:

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

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

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

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

    下面是我已经开始的代码,但处理所有不同的相机错误可能会有问题,即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" />
    

    【讨论】:

    • 忘了提到我正在使用 2.8.0 ...所以不确定这是否会在 2.6 中运行
    【解决方案2】:

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

    场景:

    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 现在为空。此 imageFilePath 是传入 ExifInterface 的内容。如果您注意到 ABOVE...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);
            }
    
    

    【讨论】:

      猜你喜欢
      • 2013-11-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-02-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-06-27
      相关资源
      最近更新 更多