【问题标题】:Is there a way of making a flutter webview use android camera for file upload? How to open file picker in webview_flutter flutter?有没有办法让颤动的 webview 使用 android 相机进行文件上传?如何在 webview_flutter 中打开文件选择器?
【发布时间】:2020-06-03 00:11:01
【问题描述】:

我有一个带有 webview 的颤振 Web 应用程序,它从服务器加载我的 PHP 项目。在我的 PHP 项目中,我有一个注册表单,要求用户使用相机拍摄照片,然后将其上传到 MySQL DB。问题是当我单击上传文件按钮以使用相机时,它什么也不做。但是在浏览器中,文件选择器正在工作,但在我的webview android 上它什么也没做。

我试过了,还是不行。

<div class="col-sm-4">
Image 1 <span style="color:red">*</span><input type="file" name="img1" accept"image/*" capture="camera"  required>
</div>

这也是我的导入。

import 'package:car_renting_app/Animations/FadeAnimation.dart';
import 'package:car_renting_app/onboarding.dart';
import 'package:car_renting_app/popup.dart';
import 'package:car_renting_app/ui/adminwebview.dart';
import 'package:car_renting_app/widgets/animated_botton_bar.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:webview_flutter/webview_flutter.dart';
import 'package:connectivity/connectivity.dart';

这是我的 pubsec.yaml

cupertino_icons: ^0.1.2
webview_flutter:
simple_animations: ^1.1.3
page_transition: ^1.1.4
carousel_pro: ^0.0.13
url_launcher: ^5.1.1
sliding_up_panel: ^0.3.4
font_awesome_flutter: ^8.5.0
flutter_swiper: ^1.1.6
connectivity:
uni_links:

【问题讨论】:

    标签: php android flutter file-upload webview


    【解决方案1】:

    我认为这是使用“webview_flutter”的替代解决方案:https://github.com/flutter/flutter/issues/27924#issuecomment-647197754

    必须编辑“FlutterWebView.java”和“AndroidManifest”。

    【讨论】:

      【解决方案2】:

      @Bruno 的链接答案对我不起作用。 所以我找到了许多解决方案并将它们一起使用,最后它对我有用。 webview_flutter: ^2.0.8(此解决方案也适用于较低版本)

      先决条件:- 在实施此解决方案相机和存储权限之前应实施权限处理

      1. 转到你的 Flutter 的外部库

      2. 导航到 webview_flutter 插件项目文件夹

      3. 导航到安卓文件夹

      4. 转到 src>main

      5. 打开清单文件,它应该是空的,里面只有包名,然后复制粘贴这个

         <provider
             android:name="io.flutter.plugins.webviewflutter.GenericFileProvider"
             android:authorities="${applicationId}.generic.provider"
             android:exported="false"
             android:grantUriPermissions="true"
             >
             <meta-data
                 android:name="android.support.FILE_PROVIDER_PATHS"
                 android:resource="@xml/provider_paths"
                 />
         </provider>
        
         </application>
         <queries>
             <package android:name="com.google.android.apps.photos" />
             <package android:name="com.google.android.apps.docs" />
             <package android:name="com.google.android.documentsui" />
         </queries>
        
      6. 如果主文件夹下没有res目录,则创建res目录

      7. 创建 values 目录,然后在其中创建 strings.xml:复制并粘贴此目录

      <resources>
          <string name="webview_file_chooser_title">Choose a file</string>
          <string name="webview_image_chooser_title">Choose an image</string>
          <string name="webview_video_chooser_title">Choose a video</string>
      </resources>
      
      1. 创建xml文件夹,然后在其中创建provider_paths.xml,复制粘贴

         <paths xmlns:android="http://schemas.android.com/apk/res/android">
             <external-files-path name="safetyapp_images" path="images" />
         </paths>
        
      2. 转到 main>java>io>flutter>plugins>webviewflutter

      3. 创建名为 Constants.java 的 java 类并复制它

      包io.flutter.plugins.webviewflutter;

          public class Constants {
              static final String ACTION_REQUEST_CAMERA_PERMISSION_FINISHED =
                      "action_request_camera_permission_denied";
              static final String ACTION_FILE_CHOOSER_FINISHED = "action_file_chooser_completed";
          
              static final String EXTRA_TITLE = "extra_title";
              static final String EXTRA_ACCEPT_TYPES = "extra_types";
              static final String EXTRA_SHOW_VIDEO_OPTION = "extra_show_video_option";
              static final String EXTRA_SHOW_IMAGE_OPTION = "extra_show_image_option";
              static final String EXTRA_FILE_URIS = "extra_file_uris";
              static final String EXTRA_ALLOW_MULTIPLE_FILES = "extra_allow_multiple_files";
          
          
              static final String WEBVIEW_STORAGE_DIRECTORY = "images";
          }
      
      1. 创建 FileChooserActivity.java 并复制它

        包io.flutter.plugins.webviewflutter;

        import static io.flutter.plugins.webviewflutter.Constants.ACTION_FILE_CHOOSER_FINISHED;
        import static io.flutter.plugins.webviewflutter.Constants.EXTRA_ACCEPT_TYPES;
        import static io.flutter.plugins.webviewflutter.Constants.EXTRA_ALLOW_MULTIPLE_FILES;
        import static io.flutter.plugins.webviewflutter.Constants.EXTRA_FILE_URIS;
        import static io.flutter.plugins.webviewflutter.Constants.EXTRA_SHOW_IMAGE_OPTION;
        import static io.flutter.plugins.webviewflutter.Constants.EXTRA_SHOW_VIDEO_OPTION;
        import static io.flutter.plugins.webviewflutter.Constants.EXTRA_TITLE;
        import static io.flutter.plugins.webviewflutter.Constants.WEBVIEW_STORAGE_DIRECTORY;
        
        import android.app.Activity;
        import android.content.Intent;
        import android.database.Cursor;
        import android.net.Uri;
        import android.os.Bundle;
        import android.provider.MediaStore;
        import android.provider.OpenableColumns;
        import android.util.Log;
        import androidx.annotation.Nullable;
        import androidx.core.content.FileProvider;
        import java.io.File;
        import java.io.FileOutputStream;
        import java.io.IOException;
        import java.io.InputStream;
        import java.io.OutputStream;
        import java.text.SimpleDateFormat;
        import java.util.ArrayList;
        import java.util.Date;
        
        public class FileChooserActivity extends Activity {
        
        private static final int FILE_CHOOSER_REQUEST_CODE = 12322;
        private static final SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMdd_HHmmss");
        
        // List of Uris that point to files where there MIGHT be the output of the capture. At most one of these can be valid
        private final ArrayList<Uri> potentialCaptureOutputUris = new ArrayList<>();
        
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            showFileChooser(
                    getIntent().getBooleanExtra(EXTRA_SHOW_IMAGE_OPTION, false),
                    getIntent().getBooleanExtra(EXTRA_SHOW_VIDEO_OPTION, false));
        }
        
        private void showFileChooser(boolean showImageIntent, boolean showVideoIntent) {
            Intent getContentIntent = createGetContentIntent();
            Intent captureImageIntent =
                    showImageIntent ? createCaptureIntent(MediaStore.ACTION_IMAGE_CAPTURE, "jpg") : null;
            Intent captureVideoIntent =
                    showVideoIntent ? createCaptureIntent(MediaStore.ACTION_VIDEO_CAPTURE, "mp4") : null;
        
            if (getContentIntent == null && captureImageIntent == null && captureVideoIntent == null) {
                // cannot open anything: cancel file chooser
                sendBroadcast(new Intent(ACTION_FILE_CHOOSER_FINISHED));
                finish();
            } else {
                ArrayList<Intent> intentList = new ArrayList<>();
        
                if (getContentIntent != null) {
                    intentList.add(getContentIntent);
                }
        
                if (captureImageIntent != null) {
                    intentList.add(captureImageIntent);
                }
                if (captureVideoIntent != null) {
                    intentList.add(captureVideoIntent);
                }
        
                Intent chooserIntent = new Intent(Intent.ACTION_CHOOSER);
                chooserIntent.putExtra(Intent.EXTRA_TITLE, getIntent().getStringExtra(EXTRA_TITLE));
        
                chooserIntent.putExtra(Intent.EXTRA_INTENT, intentList.get(0));
                intentList.remove(0);
                if (intentList.size() > 0) {
                    chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, intentList.toArray(new Intent[0]));
                }
        
                startActivityForResult(chooserIntent, FILE_CHOOSER_REQUEST_CODE);
            }
        }
        
        private Intent createGetContentIntent() {
            Intent filesIntent = new Intent(Intent.ACTION_GET_CONTENT);
        
            if (getIntent().getBooleanExtra(EXTRA_ALLOW_MULTIPLE_FILES, false)) {
                filesIntent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
            }
        
            String[] acceptTypes = getIntent().getStringArrayExtra(EXTRA_ACCEPT_TYPES);
        
            if (acceptTypes.length == 0 || (acceptTypes.length == 1 && acceptTypes[0].length() == 0)) {
                // empty array or only 1 empty string? -> accept all types
                filesIntent.setType("*/*");
            } else if (acceptTypes.length == 1) {
                filesIntent.setType(acceptTypes[0]);
            } else {
                // acceptTypes.length > 1
                filesIntent.setType("*/*");
                filesIntent.putExtra(Intent.EXTRA_MIME_TYPES, acceptTypes);
            }
        
            return (filesIntent.resolveActivity(getPackageManager()) != null) ? filesIntent : null;
        }
        
        private Intent createCaptureIntent(String type, String fileFormat) {
            Intent captureIntent = new Intent(type);
            if (captureIntent.resolveActivity(getPackageManager()) == null) {
                return null;
            }
        
            // Create the File where the output should go
            Uri captureOutputUri = getTempUri(fileFormat);
            potentialCaptureOutputUris.add(captureOutputUri);
        
            captureIntent.putExtra(MediaStore.EXTRA_OUTPUT, captureOutputUri);
        
            return captureIntent;
        }
        private File getStorageDirectory() {
            File imageDirectory = new File(this.getExternalFilesDir(null), WEBVIEW_STORAGE_DIRECTORY);
            if (!imageDirectory.isDirectory()) {
                imageDirectory.mkdir();
            }
            return imageDirectory;
        }
        
        
        private Uri getTempUri(String format) {
            String fileName = "CAPTURE-" + simpleDateFormat.format(new Date()) + "." + format;
            File file = new File(getStorageDirectory(), fileName);
            return FileProvider.getUriForFile(
                    this, getApplicationContext().getPackageName() + ".generic.provider", file);
        }
        
        private String getFileNameFromUri(Uri uri) {
            Cursor returnCursor = getContentResolver().query(uri, null, null, null, null);
            assert returnCursor != null;
            int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
            returnCursor.moveToFirst();
            String name = returnCursor.getString(nameIndex);
            returnCursor.close();
            return name;
        }
        
        private Uri copyToLocalUri(Uri uri) {
            File destination = new File(getStorageDirectory(), getFileNameFromUri(uri));
        
            try (InputStream in = getContentResolver().openInputStream(uri);
                 OutputStream out = new FileOutputStream(destination)) {
                byte[] buffer = new byte[1024];
                int len;
                while ((len = in.read(buffer)) != -1) {
                    out.write(buffer, 0, len);
                }
                return FileProvider.getUriForFile(
                        this, getApplicationContext().getPackageName() + ".generic.provider", destination);
            } catch (IOException e) {
                Log.e("WEBVIEW", "Unable to copy selected image", e);
                e.printStackTrace();
                return null;
            }
        }
        
        @Override
        protected void onActivityResult(int requestCode, int resultCode, Intent data) {
            if (requestCode == FILE_CHOOSER_REQUEST_CODE) {
                Intent fileChooserFinishedIntent = new Intent(ACTION_FILE_CHOOSER_FINISHED);
                if (resultCode == Activity.RESULT_OK) {
                    if (data != null && (data.getDataString() != null || data.getClipData() != null)) {
                        if (data.getDataString() != null) {
                            // single result from file browser OR video from camera
                            Uri localUri = copyToLocalUri(data.getData());
                            if (localUri != null) {
                                fileChooserFinishedIntent.putExtra(
                                        EXTRA_FILE_URIS, new String[] {localUri.toString()});
                            }
                        } else if (data.getClipData() != null) {
                            // multiple results from file browser
                            int uriCount = data.getClipData().getItemCount();
                            String[] uriStrings = new String[uriCount];
        
                            for (int i = 0; i < uriCount; i++) {
                                Uri localUri = copyToLocalUri(data.getClipData().getItemAt(i).getUri());
                                if (localUri != null) {
                                    uriStrings[i] = localUri.toString();
                                }
                            }
                            fileChooserFinishedIntent.putExtra(EXTRA_FILE_URIS, uriStrings);
                        }
                    } else {
                        // image result from camera (videos from the camera are handled above, but this if-branch could handle them too if this varies from device to device)
                        for (Uri captureOutputUri : potentialCaptureOutputUris) {
                            try {
                                // just opening an input stream (and closing immediately) to test if the Uri points to a valid file
                                // if it's not a real file, the below catch-clause gets executed and we continue with the next Uri in the loop.
                                getContentResolver().openInputStream(captureOutputUri).close();
                                fileChooserFinishedIntent.putExtra(
                                        EXTRA_FILE_URIS, new String[] {captureOutputUri.toString()});
                                // leave the loop, as only one of the potentialCaptureOutputUris is valid and we just found it
                                break;
                            } catch (IOException ignored) {
                            }
                        }
                    }
                }
                sendBroadcast(fileChooserFinishedIntent);
                finish();
            } else {
                super.onActivityResult(requestCode, resultCode, data);
            }
           }
         }
        
      2. 创建这个类 FileChooserLauncher.java 并复制它

        包io.flutter.plugins.webviewflutter;

        import static io.flutter.plugins.webviewflutter.Constants.ACTION_FILE_CHOOSER_FINISHED;
        import static io.flutter.plugins.webviewflutter.Constants.ACTION_REQUEST_CAMERA_PERMISSION_FINISHED;
        import static io.flutter.plugins.webviewflutter.Constants.EXTRA_ACCEPT_TYPES;
        import static io.flutter.plugins.webviewflutter.Constants.EXTRA_ALLOW_MULTIPLE_FILES;
        import static io.flutter.plugins.webviewflutter.Constants.EXTRA_FILE_URIS;
        import static io.flutter.plugins.webviewflutter.Constants.EXTRA_SHOW_IMAGE_OPTION;
        import static io.flutter.plugins.webviewflutter.Constants.EXTRA_SHOW_VIDEO_OPTION;
        import static io.flutter.plugins.webviewflutter.Constants.EXTRA_TITLE;
        
        import android.Manifest;
        import android.content.BroadcastReceiver;
        import android.content.Context;
        import android.content.Intent;
        import android.content.IntentFilter;
        import android.content.pm.PackageManager;
        import android.net.Uri;
        import android.webkit.ValueCallback;
        import androidx.core.content.ContextCompat;
        
        public class FileChooserLauncher extends BroadcastReceiver {
        
        private Context context;
        private String title;
        private boolean allowMultipleFiles;
        private boolean videoAcceptable;
        private boolean imageAcceptable;
        private ValueCallback<Uri[]> filePathCallback;
        private String[] acceptTypes;
        
        public FileChooserLauncher(
                Context context,
                boolean allowMultipleFiles,
                ValueCallback<Uri[]> filePathCallback,
                String[] acceptTypes) {
            this.context = context;
            this.allowMultipleFiles = allowMultipleFiles;
            this.filePathCallback = filePathCallback;
            this.acceptTypes = acceptTypes;
        
            if (acceptTypes.length == 0 || (acceptTypes.length == 1 && acceptTypes[0].length() == 0)) {
                // acceptTypes empty -> accept anything
                imageAcceptable = true;
                videoAcceptable = true;
            } else {
                for (String acceptType : acceptTypes) {
                    if (acceptType.startsWith("image/")) {
                        imageAcceptable = true;
                    } else if (acceptType.startsWith("video/")) {
                        videoAcceptable = true;
                    }
                }
            }
        
            if (imageAcceptable && !videoAcceptable) {
                title = context.getResources().getString(R.string.webview_image_chooser_title);
            } else if (videoAcceptable && !imageAcceptable) {
                title = context.getResources().getString(R.string.webview_video_chooser_title);
            } else {
                title = context.getResources().getString(R.string.webview_file_chooser_title);
            }
        }
        
        private boolean canCameraProduceAcceptableType() {
            return imageAcceptable || videoAcceptable;
        }
        
        private boolean hasCameraPermission() {
            return ContextCompat.checkSelfPermission(context, Manifest.permission.CAMERA)
                    == PackageManager.PERMISSION_GRANTED;
        }
        
        public void start() {
            if (!canCameraProduceAcceptableType() || hasCameraPermission()) {
                showFileChooser();
            } else {
                IntentFilter intentFilter = new IntentFilter();
                intentFilter.addAction(ACTION_REQUEST_CAMERA_PERMISSION_FINISHED);
                context.registerReceiver(this, intentFilter);
        
                Intent intent = new Intent(context, RequestCameraPermissionActivity.class);
                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                context.startActivity(intent);
            }
        }
        
        private void showFileChooser() {
            IntentFilter intentFilter = new IntentFilter(ACTION_FILE_CHOOSER_FINISHED);
            context.registerReceiver(this, intentFilter);
        
            Intent intent = new Intent(context, FileChooserActivity.class);
            intent.putExtra(EXTRA_TITLE, title);
            intent.putExtra(EXTRA_ACCEPT_TYPES, acceptTypes);
            intent.putExtra(EXTRA_SHOW_IMAGE_OPTION, imageAcceptable && hasCameraPermission());
            intent.putExtra(EXTRA_SHOW_VIDEO_OPTION, videoAcceptable && hasCameraPermission());
            intent.putExtra(EXTRA_ALLOW_MULTIPLE_FILES, allowMultipleFiles);
            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            context.startActivity(intent);
        }
        
        @Override
        public void onReceive(Context context, Intent intent) {
            if (intent.getAction().equals(ACTION_REQUEST_CAMERA_PERMISSION_FINISHED)) {
                context.unregisterReceiver(this);
                showFileChooser();
            } else if (intent.getAction().equals(ACTION_FILE_CHOOSER_FINISHED)) {
                String[] uriStrings = intent.getStringArrayExtra(EXTRA_FILE_URIS);
                Uri[] result = null;
        
                if (uriStrings != null) {
                    int uriStringCount = uriStrings.length;
                    result = new Uri[uriStringCount];
        
                    for (int i = 0; i < uriStringCount; i++) {
                        result[i] = Uri.parse(uriStrings[i]);
                    }
                }
        
                filePathCallback.onReceiveValue(result);
                context.unregisterReceiver(this);
                filePathCallback = null;
            }
          }
         }
        
      3. 应该有一个类叫FlutterWebView.java 只需复制此覆盖的方法并粘贴到其中即可。

        @Override
        public boolean onShowFileChooser(
                WebView webView,
                ValueCallback<Uri[]> filePathCallback,
                FileChooserParams fileChooserParams) {
          // info as of 2021-03-08:
          // don't use fileChooserParams.getTitle() as it is (always? on                Mi 9T Pro Android 10 at least) null
          // don't use fileChooserParams.isCaptureEnabled() as it is (always? on Mi 9T Pro Android 10 at least) false, even when the file upload allows images or any file
          final Context context = webView.getContext();
          final boolean allowMultipleFiles = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP
                  && fileChooserParams.getMode() == FileChooserParams.MODE_OPEN_MULTIPLE;
          final String[] acceptTypes = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP
                  ? fileChooserParams.getAcceptTypes() : new String[0];
          new FileChooserLauncher(context, allowMultipleFiles, filePathCallback, acceptTypes)
                  .start();
          return true;
        } }
        
      4. 创建一个类 RequestCameraPermissionActivity.java 并将此代码粘贴到那里

      导入 android.Manifest; 导入android.app.Activity; 导入android.content.Intent; 导入android.os.Bundle; 导入androidx.annotation.NonNull; 导入androidx.annotation.Nullable; 导入androidx.core.app.ActivityCompat;

          public class RequestCameraPermissionActivity extends Activity {
      
          private static final int CAMERA_PERMISSION_REQUEST_CODE = 12321;
      
          @Override
          protected void onCreate(@Nullable Bundle savedInstanceState) {
              super.onCreate(savedInstanceState);
      
              ActivityCompat.requestPermissions(
                      this, new String[] {Manifest.permission.CAMERA}, CAMERA_PERMISSION_REQUEST_CODE);
          }
      
          @Override
          public void onRequestPermissionsResult(
                  int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
              if (requestCode == CAMERA_PERMISSION_REQUEST_CODE) {
                  sendBroadcast(new Intent(ACTION_REQUEST_CAMERA_PERMISSION_FINISHED));
                  finish();
              } else {
                  super.onRequestPermissionsResult(requestCode, permissions, grantResults);
              }
          }
      }
      

      【讨论】:

        【解决方案3】:

        这是一个添加onShowFileChooser的PR

        https://github.com/flutter/plugins/pull/3225

        也许下一个版本会在 Android 上支持此功能。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2021-09-23
          • 1970-01-01
          • 2016-07-22
          • 2021-06-06
          • 2023-03-10
          • 1970-01-01
          • 2021-01-07
          • 2014-06-27
          相关资源
          最近更新 更多