当前位置: 首页 > 知识库问答 >
问题:

Android WebView:无法将相机拍摄的图像保存或上传到HTML5画布,但可以从存储中上传现有图像

周睿范
2023-03-14

背景:我正在尝试构建一个非常基本的WebView应用程序,用于访问我最近构建的响应性web应用程序/网站。该网站的一个功能包括一个“选择照片”按钮,用户可以将图像上传到HTML5画布上进行临时图像查看和数据提取。此按钮基于简单的HTML文件输入:

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

该功能在“常规”移动浏览器(各种Android和iOS设备上的Chrome、Safari、Firefox等)中都能完美运行。当用户在这些浏览器中点击“选择照片”按钮时,他们可以(1)使用设备的摄像头拍摄/保存/上传全新图像到HTML5画布,或者(2)上传存储在手机上的现有图像。

问题是:在我的WebView应用程序中,我可以将现有照片从本地存储上传到HTML5画布上,但当从WebView应用程序打开相机时,我无法上传相机拍摄的照片。

当我在WebView应用程序中单击网站的“选择照片”按钮时,系统会提示我在相机和本地存储之间进行选择(这很好)。从本地存储浏览/上传到HTML5画布效果良好。但是,当我点击相机选项时,我可以像往常一样使用相机(前置或后置相机)拍照,但照片不会保存或上传到HTML5画布(这很糟糕!)。在拍摄/接受照片后,我只得到了一张空的HTML5画布。照片也不会出现在本地存储中。

我在日志中找不到任何相关错误(在多个模拟器/各种Android版本上测试,我的Pixel 3a运行Android 10)。该应用程序会提示您输入相机和存储/写入权限,并且该应用程序似乎会在被授予时保留这些权限。我的猜测是,应用程序无法保存/获取新的相机照片,因为我为新照片文件指定了错误的目录或文件名。不知道该如何解决这个问题。

我的尝试:过去几天我一直在尝试让这个应用程序从相机上传照片。我得到的最接近的代码是本文中的一些代码(反映在我下面的代码中)。

我已经尝试用Android开发文档(尤其是相机)中的一些信息来增强该帖子中的代码

这是我目前的主要活动。java代码。如果你需要查看其他文件,请告诉我。(注意:url替换为example.com)

package com.example.placeholderexamplename;

import android.Manifest;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.pm.PackageManager;
import androidx.core.app.ActivityCompat;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.provider.MediaStore;
import android.view.KeyEvent;
import android.webkit.ValueCallback;
import android.webkit.WebChromeClient;
import android.widget.Toast;
import android.util.Log;
import android.view.View;
import android.view.Window;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.ContextCompat;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class MainActivity extends AppCompatActivity {

    private static final int INPUT_FILE_REQUEST_CODE = 2;
    private static final int FILECHOOSER_RESULTCODE = 1;
    private static final String TAG = MainActivity.class.getSimpleName();
    Context context;
    WebView webView;
    private WebSettings webSettings;
    private ValueCallback<Uri> mUploadMessage;
    private Uri mCapturedImageURI = null;
    private ValueCallback<Uri[]> mFilePathCallback;
    private String mCameraPhotoPath;
    private static final int CAMERA_PERMISSION_CODE = 100;
    private static final int STORAGE_PERMISSION_CODE = 101;

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {

        if (requestCode != INPUT_FILE_REQUEST_CODE || mFilePathCallback == null) {
            super.onActivityResult(requestCode, resultCode, data);
            return;
        }
        Uri[] results = null;
        // Check response
        if (resultCode == Activity.RESULT_OK) {
            if (data == null) {
                // If no data, we may have taken a photo
                if (mCameraPhotoPath != null) {
                    results = new Uri[]{Uri.parse(mCameraPhotoPath)};
                }
            } else {
                String dataString = data.getDataString();
                if (dataString != null) {
                    results = new Uri[]{Uri.parse(dataString)};
                }
            }
        }
        mFilePathCallback.onReceiveValue(results);
        mFilePathCallback = null;

        return;
    }



    //start check permissions

    private final static int REQUEST_CODE_ASK_PERMISSIONS = 1;
    private static final String[] REQUIRED_SDK_PERMISSIONS = new String[] {
            Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE };


    protected void checkPermissions() {

        final List<String> missingPermissions = new ArrayList<String>();

        // check all required dynamic permissions
        for (final String permission : REQUIRED_SDK_PERMISSIONS) {
            final int result = ContextCompat.checkSelfPermission(this, permission);
            if (result != PackageManager.PERMISSION_GRANTED) {
                missingPermissions.add(permission);

            }



        }
        if (!missingPermissions.isEmpty()) {

            // request all missing permissions
            final String[] permissions = missingPermissions
                    .toArray(new String[missingPermissions.size()]);
            ActivityCompat.requestPermissions(this, permissions, REQUEST_CODE_ASK_PERMISSIONS);
        } else {
            final int[] grantResults = new int[REQUIRED_SDK_PERMISSIONS.length];
            Arrays.fill(grantResults, PackageManager.PERMISSION_GRANTED);
            onRequestPermissionsResult(REQUEST_CODE_ASK_PERMISSIONS, REQUIRED_SDK_PERMISSIONS,
                    grantResults);
        }
    }


    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[],
                                           @NonNull int[] grantResults) {
        switch (requestCode) {
            case REQUEST_CODE_ASK_PERMISSIONS:
                for (int index = permissions.length - 1; index >= 0; --index) {
                    if (grantResults[index] != PackageManager.PERMISSION_GRANTED) {
                        Toast.makeText(this, "To search by photo, please grant camera and storage access via device settings.", Toast.LENGTH_LONG).show();
                    }
                }
        }
    }
    //end check permissions


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        View decorView = getWindow().getDecorView();
        int uiOptions = View.SYSTEM_UI_FLAG_FULLSCREEN;
        decorView.setSystemUiVisibility(uiOptions);
        requestWindowFeature(Window.FEATURE_NO_TITLE);

        setContentView(R.layout.activity_main);
        context = this;
        webView = (WebView) findViewById(R.id.activity_main_webview);
        WebSettings settings = webView.getSettings();
        settings.setJavaScriptEnabled(true);
        webView.loadUrl("https://www.example.com");
        webView.setWebChromeClient(new ChromeClient());
        checkPermissions();

        webView.setWebViewClient(new WebViewClient() {

            public boolean shouldOverrideUrlLoading(WebView view, String url) {
                boolean isLocalUrl = false;
                try {
                    URL givenUrl = new URL(url);
                    String host = givenUrl.getHost();
                    if(host.contains("example.com"))
                        isLocalUrl = true;

                } catch (MalformedURLException e) {

                }

                if (isLocalUrl)
                    return super.shouldOverrideUrlLoading(view, url);
                else
                {
                    Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
                    startActivity(intent);
                    return true;
                }
            }

            @Override
            public void onPageFinished(WebView view, String url) {
                super.onPageFinished(view, url);

                Log.w("----------", "page finish : " + url);
            }

            public void onReceivedError(WebView webView, int errorCode, String description, String failingUrl) {
                try {
                    webView.stopLoading();
                } catch (Exception e) {
                }
                if (webView.canGoBack()) {
                    webView.goBack();
                }
                webView.loadUrl("about:blank");
                AlertDialog alertDialog = new AlertDialog.Builder(context).create();
                alertDialog.setTitle("Error");
                alertDialog.setCancelable(false);
                alertDialog.setMessage("Check your internet connection and try again.");
                alertDialog.setButton(DialogInterface.BUTTON_POSITIVE, "Try Again", new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int which) {
                        startActivity(getIntent());
                        finish();
                    }
                });
                alertDialog.show();
                super.onReceivedError(webView, errorCode, description, failingUrl);
            }
        });
    }

    private File createImageFile() throws IOException {

        // Create an image file name

        String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
        String imageFileName = "JPEG_" + timeStamp + "_";

        //File storageDir = Environment.getExternalStoragePublicDirectory(Environment.getExternalStorageState());
        File storageDir = context.getExternalFilesDir(Environment.DIRECTORY_PICTURES);

        File imageFile = File.createTempFile(
                imageFileName,  // prefix
                ".jpg",         // suffix
                storageDir      // directory
        );

        return imageFile;

    }

    public boolean onKeyDown(int keyCode, KeyEvent event) {
        // Check if the key event was the Back button and if there's history
        if ((keyCode == KeyEvent.KEYCODE_BACK) && webView.canGoBack()) {
            webView.goBack();
            return true;
        }

        return super.onKeyDown(keyCode, event);
    }




    @Override
    public void onBackPressed() {
        super.onBackPressed();
    }

    public class ChromeClient extends WebChromeClient {
        // For Android 5.0

        public boolean onShowFileChooser(WebView view, ValueCallback<Uri[]> filePath, WebChromeClient.FileChooserParams fileChooserParams) {

            // Double check that we don't have any existing callbacks
            if (mFilePathCallback != null) {
                mFilePathCallback.onReceiveValue(null);
            }
            mFilePathCallback = filePath;
            Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);


            //Intent intent = new Intent("android.media.action.IMAGE_CAPTURE");
            // startActivity(intent);

            if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
                // Create the File where the photo should go
                File photoFile = null;
                try {
                    photoFile = createImageFile();
                    takePictureIntent.putExtra("PhotoPath", mCameraPhotoPath);
                } catch (IOException ex) {
                    // Error occurred while creating the File
                    Log.e(TAG, "Unable to create Image File", ex);
                }
                // Continue only if the File was successfully created
                if (photoFile != null) {
                    mCameraPhotoPath = "file:" + photoFile.getAbsolutePath();
                    takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT,
                            Uri.fromFile(photoFile));
                } else {
                    takePictureIntent = null;
                }
            }



            Intent contentSelectionIntent = new Intent(Intent.ACTION_GET_CONTENT);
            contentSelectionIntent.addCategory(Intent.CATEGORY_OPENABLE);

            contentSelectionIntent.setType("image/*");
            //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, "Choose from...");
            chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, intentArray);
            startActivityForResult(chooserIntent, INPUT_FILE_REQUEST_CODE);
            return true;
        }

    }
    public void onWindowFocusChanged(boolean hasFocus) {
        super.onWindowFocusChanged(hasFocus);
        if (hasFocus) {
            View decorView = getWindow().getDecorView();
            int uiOptions = View.SYSTEM_UI_FLAG_FULLSCREEN;
            decorView.setSystemUiVisibility(uiOptions);
        }
    }
}

(第一次编辑)这是我的Androidanifest.xml代码(占位符值用于url等)。这包括以前尝试留下的一些未使用的代码(如本节);删除未使用的代码并没有影响问题。目前我只提示并记录“CAMERA”和“WRITE_EXTERNAL_STORAGE”权限(这些是正确的权限吗?):

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.placeholderexamplename">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/example_icon"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/example_icon_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity
            android:name="com.example.placeholderexamplename.MainActivity"
            android:configChanges="orientation|screenSize">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>

            <intent-filter>
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.BROWSABLE" />
                <data android:scheme="https" android:host="www.example.com" />
                <data android:scheme="https" />
            </intent-filter>
        </activity>
        <provider
            android:name="androidx.core.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>

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



</manifest>

还有我的主要活动。xml代码:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.example.placeholderexamplename.MainActivity">

    <WebView
        android:id="@+id/activity_main_webview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:fitsSystemWindows="true" />



    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />


</androidx.constraintlayout.widget.ConstraintLayout>

我想支持KitKat及以后的版本,但目前仅限于Android 10的解决方案是可以的。WebView应用程序需要能够从(1)设备的相机或(2)本地存储中获取图像到HTML5画布。从应用程序内拍摄的新照片可以保存到公共目录或绑定到应用程序的目录中——没有偏好,我只需要能够从应用程序内拍摄照片并显示在HTML5画布中,以执行快速操作。一旦解除封锁,我将从那里开始工作。

这是我第一次尝试Android development/Android Studio。我是个笨蛋,我被困住了。我将非常感谢任何帮助!WebView应用程序中的其他一切都正常工作,这是我的最后一个拦截器。

提前感谢您的投入。

共有1个答案

隆睿
2023-03-14

我在Github上使用此示例https://github.com/mgks/Os-FileUp作为我的基本示例代码,但使用FileProvider构建文件uri

在舱单上:

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

在res文件夹中创建一个名为xml的文件夹,然后添加一个xml文件provider_paths.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>

因为我只用于上传照片,所以我从以下位置更新基本示例代码:

if (photoFile != null) {
                            cam_file_data = "file:" + photoFile.getAbsolutePath();
                            takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(photoFile));
                        } else {
                            cam_file_data = null;
                            takePictureIntent = null;
                        }

到此代码:

if (photoFile != null) {
                            cam_file_data = "file:" + photoFile.getAbsolutePath();
                            Uri imgUrl;
                            if (getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.M) {
                                String authority = "com.your.packagename";
                                imgUrl = FileProvider.getUriForFile(MainActivity.this, authority, photoFile);
                            } else {
                                imgUrl = Uri.fromFile(photoFile);
                            }
                            takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, imgUrl);
                        } else {
                            cam_file_data = null;
                            takePictureIntent = null;
                        }
 类似资料:
  • 我想将相机拍摄的图像上传到firebase存储。我知道如何存储图像,一旦我得到图像的Uri格式,但我从相机活动得到位图。所以我需要知道怎么做? 我使用函数uploadFile()将图像的Uri存储到firebase存储,然后获取图像的url以将其存储在firebase实时数据库中。但是我需要将位图转换为Uri吗?如果是,如何进行?我发现的代码不起作用,如果有其他方法,请告诉我 谢谢您的时间:)

  • 我有一个问题与我的代码,我必须上传图像到Firebase存储,我需要的图像来自画廊和相机,从画廊的图像是好的,但来自相机的图像给问题,图像加载在ImageView和被发送到数据库是黑色的。有人知道如何解决这个问题吗,或者你知道任何其他加载图像的方法吗? 来自画廊 从相机

  • 问题内容: 我目前正在使用 http://paperjs.org 创建HTML5画布绘图应用程序。我想让用户将图像上传到画布中。我知道我需要进行登录和注册,但是有更简单的方法吗?我已经看到HTML5拖放式上传。 问题答案: 我假设您的意思是将图像加载到画布中,而不是从画布上载图像。 在这里阅读他们所有的画布文章可能是一个好主意 但基本上,您要做的是在javascript中创建图片,然后将image

  • 我有一个按钮,它向用户提供选择,他是否想要从画廊或相机的图像。当用户从图库拍摄图像时,我能够成功地将图像上传到Firebase存储,但是当用户选择相机时,我无法将图像上传到存储。这是用户选择相机时的onActivityResult代码 它将图像上传到我的imageview,但给我错误提示 当我尝试上载到存储时 我在stack overflow上遇到了一个类似的问题。 我认为公认的答案是错误的,因为

  • 嘿,谢谢你抽出时间。在我的页面中,我的应用程序的webview中加载的是照片上传: 它上传一张照片,如果你通常图片你的画廊等... 如果我点击这个输入,我可以选择一张图片,然后网站检测到更改(js:onchange)。我已经尝试了一些东西,但在我选择它之后它不会上传图片。下面是我对imgupload的编码: 我希望你能帮忙,祝你今天过得愉快

  • 此应用程序中的WebView会打开一个带有上载按钮的页面。 下面是一个代码块,可以打开一个对话框从gallery或camera上传图像。 在我的活动中,我有: 在onCreate中,我有以下内容: 文件浏览器和图库正在按预期工作。问题是,当我用相机拍照时,它没有上载到“选择文件”选项中,该选项显示状态“未选择文件”。 选择相机时: 使用相机拍摄快照:出现返回和检查选项。 关于选择检查标记: 文件未