在安卓上的科尔多瓦应用程序中的文件上传中选择相机,而无需使用科尔多瓦相机



所以我做了一个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" />

到清单文件。

但是当我按下按钮时,我无法选择用相机拍摄新照片。 我错过了什么权限,还是其他什么?

我无法使用科尔多瓦拍照功能,它必须在纯 html 中完成。

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

移动浏览器中的媒体捕获似乎仍然存在一些问题。查看此链接。摘录说:

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

所以看起来捕获属性不会产生任何影响。

另外,建议您查看此SO帖子以获取有关完成此工作的更多信息。希望对您有所帮助。干杯。

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

此外,根据@jcesarmobile的评论(他是科尔多瓦专家(在没有人行横道网络视图插件的帖子中,输入类型仅在 iOS 上工作正常,而在 Android 上则不然。因此,使用相机插件是在不使用人行横道插件的情况下使其工作的唯一方法。希望它有所帮助。

更新

2:在更新的问题之后,我更深入地挖掘了这个问题的解决方案。但是现在我可以保证,对于Android Webview来说,这个问题仍然没有得到解决。看起来这不是科尔多瓦的问题,而是铬网络视图的问题。

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

  • 文件输入元素未打开
  • 文件输入元素应支持接受/源属性

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

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

我解决了从输入字段的webview打开相机中的合并解决方案,而无需文件选择器,方法为活动结果和插件源中的类InAppBrowser的显示文件选择器。

查看插件版本 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 - 替换显示文件选择器代码:


                    // 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 - 替换活动结果


    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;
        }
    }

将这些权限添加到 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" />

将这些权限请求添加到主活动中.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"

在此区域的 Android 清单.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;
}

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

一个老问题,但我刚刚面对这个问题。事实证明,实现您想要的最简单方法是将这些 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. 始终打开相机
  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 可以在这里找到。

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

(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 方法处理的 File 对象。参见科尔多瓦文档中的示例

请注意,此实现特定于我的项目,因此您可能不需要调用 resolveLocalFileSystemURL,具体取决于您打算如何使用在 camera.getPicture 的 onSuccess 回调中传递的图像 URL。

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

最新更新