【问题标题】:Choose camera in file upload in cordova application on android without using cordova camera在不使用cordova相机的情况下在android上的cordova应用程序中选择文件上传中的相机
【发布时间】:2014-12-10 10:36:03
【问题描述】:

所以我制作了一个cordova应用程序,我添加了android平台并制作了一个带有输入字段的简单html

<input type="file" capture="camera" accept="image/*" id="takePictureField">

我已经添加了

<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" android:required="false" />
<uses-feature android:name="android.hardware.camera.autofocus" />
<feature name="http://api.phonegap.com/1.0/camera" />

到清单文件。

但是当我按下按钮时,我无法选择用相机拍摄新照片。有没有我想念的权限,或者其他什么??

我不能使用cordova拍照功能,它必须在纯html中完成。

【问题讨论】:

  • 这里有同样的问题。从那以后你找到解决这个问题的方法了吗?谢谢。
  • 我改用了cordova插件。并让 if cordovaEnabled do this else 做其他事情。

标签: android html cordova camera


【解决方案1】:

经过一番谷歌搜索,我可以得出以下结论:

移动浏览器中的媒体捕获似乎仍然存在一些问题。看看这个link。摘录说:

实际上,当前的实现似乎根本不依赖capture 属性,而仅依赖typeaccept 属性:浏览器显示一个对话框,用户可以在其中选择必须取文件,不考虑捕获属性。例如,iOS Safari 依赖于图像和视频(不是音频)的 accept 属性(不是捕获)。即使您不使用accept 属性,浏览器也会让您在“拍照或录像”和“选择现有”之间进行选择

所以看起来捕获属性没有任何影响。

另外,建议您查看此SO post 以获取有关使其工作的更多信息。希望能帮助到你。干杯。

更新:在投票后,我进一步深入研究了这个问题。大多数搜索都没有成功,因为此问题的最佳解决方案是使用 Cordova 相机插件。最后偶然发现了这个SO post,它与这个问题完全相同。用户能够解决该问题(不过,使用人行横道 Web 视图)。 @Fabio 已经在此处提到了该帖子中的答案。但是,您可以使用cordova-custom-plugin 添加所需的权限,而不是仅仅为了包含权限而添加插件。

此外,根据@jcesarmobile 的评论(他是 Cordova 专家)在没有人行横道网络视图插件的帖子中,输入类型仅适用于 iOS 而不是 Android。所以使用相机插件是不使用人行横道插件使其工作的唯一方法。希望它有所帮助。

更新 2:在更新问题之后,我更深入地研究了这个问题的解决方案。但现在我可以保证这个问题对于 Android Webview 仍然没有解决。看起来这不是 Cordova 问题,而是 Chromium Web 视图的问题。

有关详细信息,请您在 Apache Cordova 问题跟踪器中查看这些问题:

到目前为止,这两个问题都没有得到解决。所以我敢肯定,除非你使用 Cordova 相机插件,否则你不能让它在 Android 上运行。希望您同意我的观点并接受该决议。干杯

【讨论】:

  • 这个问题是针对科尔多瓦的
  • @aWebDeveloper 当然是用于科尔多瓦的。 Cordova 应用程序还使用 webview,而 webview 又使用浏览器功能。这个问题还谈到了太旧的cordova 3.6。如果您有其他问题,请更新问题。也是一种善意的要求,投反对票永远不会激励任何人。如果答案没有帮助,请发表评论。
  • @aWebDeveloper 用更多信息更新了我的答案。
  • 感谢您抽出宝贵的时间。我已经更新了删除cordova 3.6的问题,并强调我希望它没有cordova相机。 +1 自定义配置插件
  • @aWebDeveloper 首先感谢您更新问题和投票。我已经更新了我的答案,这应该会让你更清楚。希望你能接受。
【解决方案2】:

我的项目使用的是 cordova-plugin-inappbrowser。

我在webview open camera from input field without filechooser 中使用插件源中的 InAppBrowser 类中的 onActivityResult 和 onShowFileChooser 方法来解决它。

查看插件版本 3.0.0 对 InAppBrowser 类所做的更改

1 - 包括进口:

import android.app.Activity;
import android.Manifest;
import android.os.Environment;
import android.provider.MediaStore;
import android.util.Log;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;

2 - 声明变量:

    private String mCM;

3 - 替换 onShowFileChooser 代码:


                    // For Android 5.0+
                    public boolean onShowFileChooser (WebView webView, ValueCallback<Uri[]> filePathCallback, WebChromeClient.FileChooserParams fileChooserParams)
                    {
                        if(Build.VERSION.SDK_INT >=23 && (cordova.getActivity().checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED || cordova.getActivity().checkSelfPermission(Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED)) {
                            cordova.getActivity().requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.CAMERA}, 1);
                        }

                        LOG.d(LOG_TAG, "File Chooser 5.0+");

                        // If callback exists, finish it.
                        if(mUploadCallbackLollipop != null) {
                            mUploadCallbackLollipop.onReceiveValue(null);
                        }
                        mUploadCallbackLollipop = filePathCallback;

                        Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);

                        if(takePictureIntent.resolveActivity(cordova.getActivity().getPackageManager()) != null) {

                            File photoFile = null;
                            try{
                                photoFile = createImageFile();
                                takePictureIntent.putExtra("PhotoPath", mCM);
                            }catch(IOException ex){
                                Log.e(LOG_TAG, "Image file creation failed", ex);
                            }
                            if(photoFile != null){
                                mCM = "file:" + photoFile.getAbsolutePath();
                                takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(photoFile));
                            }else{
                                takePictureIntent = null;
                            }
                        }
                        // Create File Chooser Intent
                        Intent contentSelectionIntent = new Intent(Intent.ACTION_GET_CONTENT);
                        contentSelectionIntent.addCategory(Intent.CATEGORY_OPENABLE);
                        contentSelectionIntent.setType("*/*");
                        Intent[] intentArray;
                        if(takePictureIntent != null){
                            intentArray = new Intent[]{takePictureIntent};
                        }else{
                            intentArray = new Intent[0];
                        }

                        Intent chooserIntent = new Intent(Intent.ACTION_CHOOSER);
                        chooserIntent.putExtra(Intent.EXTRA_INTENT, contentSelectionIntent);
                        chooserIntent.putExtra(Intent.EXTRA_TITLE, "Selecione a imagem");
                        chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, intentArray);

                        // Run cordova startActivityForResult
                        cordova.startActivityForResult(InAppBrowser.this, chooserIntent, FILECHOOSER_REQUESTCODE);

                        return true;
                    }

4 - 创建方法


    private File createImageFile() throws IOException{
        @SuppressLint("SimpleDateFormat") String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
        String imageFileName = "img_"+timeStamp+"_";
        File storageDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
        return File.createTempFile(imageFileName,".jpg",storageDir);
    }

5 - 替换 onActivityResult


    public void onActivityResult(int requestCode, int resultCode, Intent intent) {
        // For Android >= 5.0
        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {

            LOG.d(LOG_TAG, "onActivityResult (For Android >= 5.0)");

            Uri[] results = null;
            //Check if response is positive
            if(resultCode== Activity.RESULT_OK){
                if(requestCode == FILECHOOSER_REQUESTCODE){
                    if(null == mUploadCallbackLollipop){
                        return;
                    }
                    if(intent == null || intent.getData() == null){
                        //Capture Photo if no image available
                        if(mCM != null){
                            results = new Uri[]{Uri.parse(mCM)};
                        }
                    }else{
                        String dataString = intent.getDataString();
                        if(dataString != null){
                            results = new Uri[]{Uri.parse(dataString)};
                        }
                    }
                }
            }
            mUploadCallbackLollipop .onReceiveValue(results);
            mUploadCallbackLollipop = null;
        }
        // For Android < 5.0
        else {
            LOG.d(LOG_TAG, "onActivityResult (For Android < 5.0)");
            // If RequestCode or Callback is Invalid
            if(requestCode != FILECHOOSER_REQUESTCODE || mUploadCallback == null) {
                super.onActivityResult(requestCode, resultCode, intent);
                return;
            }

            if (null == mUploadCallback) return;
            Uri result = intent == null || resultCode != cordova.getActivity().RESULT_OK ? null : intent.getData();

            mUploadCallback.onReceiveValue(result);
            mUploadCallback = null;
        }
    }

【讨论】:

    【解决方案3】:

    将这些权限添加到 AndroidManifest.xml 文件中

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.CAMERA" />
    <uses-feature android:name="android.hardware.camera" />
    <uses-feature android:name="android.hardware.camera.autofocus" />
    

    将这些权限请求添加到 MainActivity.java 中

    @Override
    public void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
    
      ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA, Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
    

    将 android:requestLegacyExternalStorage="true" 添加到此标签上的 AndroidManifest.xml 文件中

    <application
        android:requestLegacyExternalStorage="true"
    

    在此区域的 AndroidManifest.xml 文件中添加提供程序

    <application>
    ...
        <provider
        android:name="android.support.v4.content.FileProvider"
        android:authorities="${applicationId}.provider"
        android:exported="false"
        android:grantUriPermissions="true">
            <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/file_paths">
            </meta-data>
        </provider>
    ....
    </application>
    
    

    res/xml/file_paths.xml 中创建新的 xml 文件

    内容:

    <?xml version="1.0" encoding="utf-8"?>
    <paths xmlns:android="http://schemas.android.com/apk/res/android">
        <external-path name="external_files" path="." />
    </paths>
    

    将这些行添加到 CordovaLib/build.gradle 文件的末尾

    dependencies {
        implementation 'com.android.support:support-v4:28.0.0'
    }
    

    像这样更改 SystemWebChromeClient.java 文件

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    @Override
    public boolean onShowFileChooser(WebView webView, final ValueCallback<Uri[]> filePathsCallback, final WebChromeClient.FileChooserParams fileChooserParams) {
        Intent intent = createChooserIntentWithImageSelection();
        try {
            parentEngine.cordova.startActivityForResult(new CordovaPlugin() {
                @Override
                public void onActivityResult(int requestCode, int resultCode, Intent intent) {
                    Uri[] result = WebChromeClient.FileChooserParams.parseResult(resultCode, intent);
                    if(result==null){
                        if(mCameraPhotoPath!=null && Uri.parse(mCameraPhotoPath)!=null) {
                            File returnFile = new File(Uri.parse(mCameraPhotoPath).getPath());
                            if (returnFile.length() > 0) {
                                result = new Uri[1];
                                result[0] = Uri.parse(mCameraPhotoPath);
                            }
                        }
                    }
                    LOG.d(LOG_TAG, "Receive file chooser URL: " + result);
                    filePathsCallback.onReceiveValue(result);
                }
            }, intent, INPUT_FILE_REQUEST_CODE);
        } catch (ActivityNotFoundException e) {
            LOG.w("No activity found to handle file chooser intent.", e);
            filePathsCallback.onReceiveValue(null);
        }
        return true;
    }
    
    private static final String PATH_PREFIX = "file:";
    
    public Intent createChooserIntentWithImageSelection() {
        Intent contentSelectionIntent = new Intent(Intent.ACTION_GET_CONTENT);
        contentSelectionIntent.addCategory(Intent.CATEGORY_OPENABLE);
        contentSelectionIntent.setType("image/*");
        ArrayList<Intent> extraIntents = new ArrayList<>();
        Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        takePictureIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
        takePictureIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
        File photoFile = createImageFile();
        if (photoFile != null) {
            mCameraPhotoPath = PATH_PREFIX + photoFile.getAbsolutePath();
            takePictureIntent.putExtra("PhotoPath", mCameraPhotoPath);
            takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT,
                    FileProvider.getUriForFile(appContext,
                            appContext.getPackageName() + ".provider",
                            photoFile));
        }
    
        Intent chooserIntent = new Intent(Intent.ACTION_CHOOSER);
        chooserIntent.putExtra(Intent.EXTRA_INTENT, contentSelectionIntent);
    
        if (takePictureIntent != null) {
            extraIntents.add(takePictureIntent);
        }
        if (!extraIntents.isEmpty()) {
            chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS,
                    extraIntents.toArray(new Intent[]{}));
        }
        return chooserIntent;
    }
    
    //creating temp picture file
    private File createImageFile() {
        String state = Environment.getExternalStorageState();
        if (!state.equals(Environment.MEDIA_MOUNTED)) {
            Log.e(TAG, "External storage is not mounted.");
            return null;
        }
    
        String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
        String imageFileName = "JPEG_" + timeStamp + "_";
        File storageDir = getExternalSdCardPath();
        storageDir.mkdirs();
    
        try {
            File file = File.createTempFile(imageFileName, ".jpg", storageDir);
            Log.d(TAG, "Created image file: " + file.getAbsolutePath());
            return file;
        } catch (IOException e) {
            Log.e(TAG, "Unable to create Image File, " +
                    "please make sure permission 'WRITE_EXTERNAL_STORAGE' was added.");
            return null;
        }
    }
    
    //for external sd card check
    public static File getExternalSdCardPath() {
        String path = null;
    
        File sdCardFile = null;
        List<String> sdCardPossiblePath = Arrays.asList("external_sd", "ext_sd", "external", "extSdCard");
    
        for (String sdPath : sdCardPossiblePath) {
            File file = new File("/mnt/", sdPath);
    
            if (file.isDirectory() && file.canWrite()) {
                path = file.getAbsolutePath();
    
                String timeStamp = new SimpleDateFormat("ddMMyyyy_HHmmss").format(new Date());
                File testWritable = new File(path, "test_" + timeStamp);
    
                if (testWritable.mkdirs()) {
                    testWritable.delete();
                } else {
                    path = null;
                }
            }
        }
    
        if (path != null) {
            sdCardFile = new File(path);
        } else {
            sdCardFile = new File(Environment.getExternalStorageDirectory().getAbsolutePath());
        }
    
        return sdCardFile;
    }
    

    【讨论】:

      【解决方案4】:

      免责声明:最近关于此答案的报告表明它不是 工作了。这个问题和答案与旧科尔多瓦和 插件版本,因此它们可能不适用于您当前的 情况。

      一个老问题,但我刚刚遇到了这个问题。 事实证明,实现你想要的最简单的方法是在你的应用程序中包含这些 Cordova 插件,即使你不需要使用它们

      cordova-plugin-camera
      cordova-plugin-media-capture
      cordova-plugin-device
      cordova-plugin-file
      cordova-plugin-media
      

      加载这些后,我发现了这种行为

      点击

      <input type="file" />
      

      让您从相机、摄像机、麦克风或文档中进行选择

      点击

      <input type="file" accept="image/*" />
      

      请您从相机或文档中进行选择

      点击

      <input type="file" accept="image/*" capture="capture" />
      

      立即启动相机。

      【讨论】:

      • 不工作。为什么有 1 票,这让我至少尝试了一次。
      • 嗨,Sam 在撰写本文时该解决方案正在发挥作用,因此赞成票,现在科尔多瓦、插件和 webviews 版本发生了变化,也许它不再工作了,我现在不想再试一次了。我更新了答案以反映它。干杯,法比奥
      • 在 Cordova 中使用相机是一种糟糕的体验,即使它适用于 Android x,它也不适用于 Android y,当它最终适用于所有 Android 时,它不适用于 iPhone
      【解决方案5】:

      灵感来自Gilberto answer,这是我的改编:

      1. 始终打开相机
      2. 在打开实际相机之前请求权限,当获得权限时,打开相机,否则,显示 Toast。
      3. 如果权限被拒绝,点击输入填写要求,无需刷新或更改屏幕等。

      新增4种方式:一种是获取权限,一种是查看权限,一种是拍照前创建图片文件,一种是打开相机。

      这里是那些方法

      
          /**
           * Called by the system when the user grants permissions
           *
           * @param requestCode
           * @param permissions
           * @param grantResults
           */
          public void onRequestPermissionResult(int requestCode, String[] permissions, int[] grantResults)
                  throws JSONException {
      
              for (int r : grantResults) {
                  if (r == PackageManager.PERMISSION_DENIED) {
                      Toast.makeText(cordova.getActivity(), "Autorisation pour ouvrir la caméra refusé", Toast.LENGTH_LONG)
                              .show();
                      mUploadCallbackLollipop.onReceiveValue(null);
                      mUploadCallbackLollipop = null;
                      return;
                  }
              }
      
              if (requestCode == PERM_REQUEST_CAMERA_FOR_FILE) {
                  startCameraActivityForAndroidFivePlus();
              }
          }
      
          private File createImageFile() throws IOException {
              @SuppressLint("SimpleDateFormat")
              String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
              String imageFileName = "img_" + timeStamp + "_";
              File storageDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
              return File.createTempFile(imageFileName, ".jpg", storageDir);
          }
      
          private void startCameraActivityForAndroidFivePlus() {
              Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
              File photoFile = null;
              try {
                  photoFile = createImageFile();
                  takePictureIntent.putExtra("PhotoPath", mCapturedPhoto);
              } catch (IOException ex) {
                  LOG.e(LOG_TAG, "Image file creation failed", ex);
              }
              if (photoFile != null) {
                  mCapturedPhoto = "file:" + photoFile.getAbsolutePath();
                  takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(photoFile));
              } else {
                  takePictureIntent = null;
              }
      
              // Fix FileUriExposedException exposed beyond app through ClipData.Item.getUri()
              StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();
              StrictMode.setVmPolicy(builder.build());
      
              // Run cordova startActivityForResult
              cordova.startActivityForResult(InAppBrowser.this, takePictureIntent, FILECHOOSER_REQUESTCODE_LOLLIPOP);
          }
      
          private boolean checkPermissionForCamera() {
              if (Build.VERSION.SDK_INT >= 23) {
                  List<String> permToAsk = new ArrayList<String>();
                  if (cordova.getActivity().checkSelfPermission(
                          Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
                      permToAsk.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
                  }
                  if (cordova.getActivity()
                          .checkSelfPermission(Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
                      permToAsk.add(Manifest.permission.CAMERA);
                  }
                  if (permToAsk.size() > 0) {
                      cordova.requestPermissions(InAppBrowser.this, PERM_REQUEST_CAMERA_FOR_FILE,
                              permToAsk.toArray(new String[permToAsk.size()]));
                      return true;
                  }
              }
              return false;
          }
      

      InAppChromeClient 实现已更新为:

      inAppWebView.setWebChromeClient(new InAppChromeClient(thatWebView) {
                          // For Android 5.0+
                          public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback,
                                  WebChromeClient.FileChooserParams fileChooserParams) {
      
                              LOG.d(LOG_TAG, "File Chooser 5.0+");
                              // If callback exists, finish it.
                              if (mUploadCallbackLollipop != null) {
                                  mUploadCallbackLollipop.onReceiveValue(null);
                              }
                              mUploadCallbackLollipop = filePathCallback;
      
                              // #Update to always open camera app
                              if (checkPermissionForCamera()) {
                                  return true;
                              }
      
                              startCameraActivityForAndroidFivePlus();
                              return true;
                          }
                      });
      

      下面是这些常量:

          private String mCapturedPhoto;
          private final static int PERM_REQUEST_CAMERA_FOR_FILE = 3;
      

      完整的InAppBrowser.java can be found here.

      【讨论】:

        【解决方案6】:

        如果您不想修改插件源代码,这是另一种解决方法。为拍照创建一个单独的控件。将控件的单击事件设置为以下处理程序:

        (event) => {
            event.stopPropagation();
            Camera.sourceType = Camera.PictureSourceType.CAMERA;
        
            const onCameraSuccess = (imgURL) => {
                window.resolveLocalFileSystemURL(imgURL, (entry) => {
                    const onFileSuccess = (file) => this._onSelectMultipleFiles(event, file);
                    const onFileFail = (error) => console.log(error);
                    entry.file(onFileSuccess, onFileFail);
                });
        
                console.log("picture retrieved successfully");
            };
        
            const onCameraFail = () => {
                console.log("picture retrieval failed");
            };
        
            navigator.camera.getPicture(onCameraSuccess, onCameraFail, {
                quality: 100, 
                destinationType: Camera.DestinationType.FILE_URI,
            });
        }
        

        这使用cordova-plugin-camera在点击控件时启动相机应用程序,并从cordova-plugin-file调用resolveLocalFileSystemURL将相机返回的图像URL转换为文件对象,由我的_onSelectMultipleFiles方法处理. see example from cordova docs

        注意这个实现是针对我的项目的,所以你可能不需要调用resolveLocalFileSystemURL,这取决于你打算如何使用在camera.getPicture的onSuccess回调中传递的图像URL。

        显然,这样做的缺点是需要使用两个控件:一个用于从文件中检索图像,另一个用于从相机中检索图像。

        【讨论】:

          猜你喜欢
          • 2015-04-02
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-12-17
          • 2016-12-18
          • 2017-03-10
          相关资源
          最近更新 更多