来自Android WebView的onShowFileChooser()只能使用一次。

42
我需要从设备中选择图片并将其上传到服务器。第一次选择图片时,onShowFileChooser()被调用并且一切工作正常。但是,当我再次尝试点击上传时,onShowFileChooser()就不会被调用了。但是对于非棒棒糖设备来说,它是有效的。每次我点击上传时,都会调用openFileChoser()。我是否遗漏了什么。以下是我的代码:

        //Needed for file upload feature
        vWebView.setWebChromeClient(new WebChromeClient() {

            // file upload callback (Android 2.2 (API level 8) -- Android 2.3 (API level 10)) (hidden method)
            public void openFileChooser(ValueCallback<Uri> filePathCallback) {
                showAttachmentDialog(filePathCallback);
            }

            // file upload callback (Android 3.0 (API level 11) -- Android 4.0 (API level 15)) (hidden method)
            public void openFileChooser(ValueCallback filePathCallback, String acceptType) {
                showAttachmentDialog(filePathCallback);
            }

            // file upload callback (Android 4.1 (API level 16) -- Android 4.3 (API level 18)) (hidden method)
            public void openFileChooser(ValueCallback<Uri> filePathCallback, String acceptType, String capture) {
                showAttachmentDialog(filePathCallback);

            }

            // file upload callback (Android 5.0 (API level 21) -- current) (public method)

            // for Lollipop, all in one
            @Override

            public boolean onShowFileChooser(
                    WebView webView, ValueCallback<Uri[]> filePathCallback,
                    WebChromeClient.FileChooserParams fileChooserParams) {
                // Double check that we don't have any existing callbacks
                if (mFilePathCallbackArray != null) {
                    mFilePathCallbackArray.onReceiveValue(null);
                }
                mFilePathCallbackArray = filePathCallback;
                // Set up the take picture intent

                if (mTypeCap == IMAGE) {
                    Intent takePictureIntent = pictureIntentSetup();
                    return showChooserDialog(takePictureIntent);
                }
                //set up video capture intent
                else {
                    Intent takeVideoIntent = videoIntentSetUp();
                    return showChooserDialog(takeVideoIntent);
                }

            }

        });

  //For lollypop
    private Intent pictureIntentSetup() {
        Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        if (takePictureIntent.resolveActivity(getActivity().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("Failed", "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;
            }
        }
        return takePictureIntent;

    }

    //For lollypop
    private Intent videoIntentSetUp() {
        Intent takeVideoIntent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
        if (takeVideoIntent.resolveActivity(getActivity().getPackageManager()) != null) {

            // create the file where the video should go
            File videoFile = null;
            try {
                videoFile = createVideoFile();
                takeVideoIntent.putExtra("PhotoPath", mCameraPhotoPath);
            } catch (IOException ex) {
                // Error occurred while creating the File
                Log.e("Failed", "Unable to create Video File", ex);
            }

            // continue only if the file was successfully created
            if (videoFile != null) {
                mCameraPhotoPath = "file:" + videoFile.getAbsolutePath();
                takeVideoIntent.putExtra(MediaStore.EXTRA_OUTPUT,
                        Uri.fromFile(videoFile));
            } else {
                takeVideoIntent = null;
            }
        }
        return takeVideoIntent;
    }

//For lollypop
    private boolean showChooserDialog(Intent intent) {
        Intent contentSelectionIntent = new Intent(Intent.ACTION_GET_CONTENT);
        contentSelectionIntent.addCategory(Intent.CATEGORY_OPENABLE);
        if (mTypeCap.equalsIgnoreCase(IMAGE))
            contentSelectionIntent.setType(IMAGE);
        else
            contentSelectionIntent.setType(VIDEO);

        Intent[] intentArray;
        if (intent != null) {
            intentArray = new Intent[]{intent};
        } else {
            intentArray = new Intent[0];
        }

        Intent chooserIntent = new Intent(Intent.ACTION_CHOOSER);
        chooserIntent.putExtra(Intent.EXTRA_INTENT, contentSelectionIntent);
        if (mTypeCap.equalsIgnoreCase(IMAGE))
            chooserIntent.putExtra(Intent.EXTRA_TITLE, "Image Chooser");
        else
            chooserIntent.putExtra(Intent.EXTRA_TITLE, "Video Chooser");
        chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, intentArray);

        getActivity().startActivityForResult(chooserIntent, FILE_CHOOSER_RESULT_CODE);

        return true;
    }

在activity的OnActivityResult方法中:

  @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
  //File upload related
            if (requestCode == NewsDetailFragment.FILE_CHOOSER_RESULT_CODE && (resultCode == RESULT_OK || resultCode == RESULT_CANCELED)) {
                Fragment fragment = getSupportFragmentManager()
                        .findFragmentById(R.id.container);
                if (fragment != null && fragment instanceof DetailFragment) {
                    Fragment currentFragment = ((DetailFragment) fragment).getCurrentFragment();
                    if (currentFragment instanceof WebDetailFragment)
                        currentFragment.onActivityResult(requestCode, resultCode, data);
                }
            }

        }
}

fragment的onActivityResult方法:

 @Override
    public void onActivityResult(int requestCode, int resultCode, Intent intentData) {
        super.onActivityResult(requestCode, resultCode, intentData);
        // code for all versions except of Lollipop
        if (!Utility.isLollypopAndAbove()) {
            Uri result = null;
            // check that the response is a good one
            if (resultCode == Activity.RESULT_OK) {
                if (requestCode == FILE_CHOOSER_RESULT_CODE) {
                    if (null == this.mFilePathCallback) {
                        return;
                    }
                    if (null == mFilePathCallback) return;


                    if (intentData == null) {
                        // if there is not data, then we may have taken a photo
                        if (mCameraPhotoPath != null) {
                            result = Uri.parse(mCameraPhotoPath);
                        }
                    } else {
                        String dataString = intentData.getDataString();
                        if (dataString != null) {
                            result = Uri.parse(dataString);
                        }
                    }
//                Uri result = intentData == null || resultCode != Activity.RESULT_OK ? null
//                        : intentData.getData();

                }

                //  for Lollipop only
            }
            mFilePathCallback.onReceiveValue(result);
            mFilePathCallback = null;
        }
        else  {
            Uri[] results = null;

            // check that the response is a good one
            if(resultCode==Activity.RESULT_OK) {
                if (requestCode == FILE_CHOOSER_RESULT_CODE) {
                    if (null == mFilePathCallbackArray) {
                        return;
                    }
                    if (intentData == null) {
                        // if there is not data, then we may have taken a photo
                        if (mCameraPhotoPath != null) {
                            results = new Uri[]{Uri.parse(mCameraPhotoPath)};
                        }
                    } else {
                        String dataString = intentData.getDataString();
                        if (dataString != null) {
                            results = new Uri[]{Uri.parse(dataString)};
                        }
                    }
                }
            }
            mFilePathCallbackArray.onReceiveValue(results);
            mFilePathCallbackArray = null;
            return;

        }
    }

7
你的代码中的“items”是什么?能否分享一下,我想测试一下。 - atrebbi
9个回答

38

首先,抱歉我的英文。你需要返回空的Uri[]{}以接收文件。

mUploadMessageForAndroid5.onReceiveValue(new Uri[]{});

我的代码可以选择拍照或选择本地图片:

private static final int REQUEST_GET_THE_THUMBNAIL = 4000;
private static final long ANIMATION_DURATION = 200;
public final static int FILECHOOSER_RESULTCODE = 1;
public final static int FILECHOOSER_RESULTCODE_FOR_ANDROID_5 = 2;

//JS
webView.getSettings().setJavaScriptEnabled(true);

//set ChromeClient
webView.setWebChromeClient(getChromeClient());

//ChromeClinet配置
private WebChromeClient getChromeClient() {
    return new WebChromeClient() {

        //3.0++
        public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType) {
            openFileChooserImpl(uploadMsg);
        }

        //3.0--
        public void openFileChooser(ValueCallback<Uri> uploadMsg) {
            openFileChooserImpl(uploadMsg);
        }

        public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture) {
            openFileChooserImpl(uploadMsg);
        }

        // For Android > 5.0
        public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> uploadMsg, WebChromeClient.FileChooserParams fileChooserParams) {
            openFileChooserImplForAndroid5(uploadMsg);
            return true;
        }

    };
}

private void openFileChooserImpl(ValueCallback<Uri> uploadMsg) {
    mUploadMessage = uploadMsg;
    new AlertDialog.Builder(mActivity)
            .setItems(items, new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    if (items[which].equals(items[0])) {
                        Intent i = new Intent(Intent.ACTION_GET_CONTENT);
                        i.addCategory(Intent.CATEGORY_OPENABLE);
                        i.setType("image/*");
                        startActivityForResult(Intent.createChooser(i, "File Chooser"), FILECHOOSER_RESULTCODE);
                    } else if (items[which].equals(items[1])) {
                        dispatchTakePictureIntent();
                    }
                    dialog.dismiss();
                }
            })
            .setOnCancelListener(new DialogInterface.OnCancelListener() {
                @Override
                public void onCancel(DialogInterface dialog) {
                    Log.v(TAG, TAG + " # onCancel");
                    mUploadMessage = null;
                    dialog.dismiss();
            }})
            .show();
}

private void openFileChooserImplForAndroid5(ValueCallback<Uri[]> uploadMsg) {
    mUploadMessageForAndroid5 = uploadMsg;

    new AlertDialog.Builder(mActivity)
            .setItems(items, new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    if (items[which].equals(items[0])) {
                        Intent contentSelectionIntent = new Intent(Intent.ACTION_GET_CONTENT);
                        contentSelectionIntent.addCategory(Intent.CATEGORY_OPENABLE);
                        contentSelectionIntent.setType("image/*");

                        Intent chooserIntent = new Intent(Intent.ACTION_CHOOSER);
                        chooserIntent.putExtra(Intent.EXTRA_INTENT, contentSelectionIntent);
                        chooserIntent.putExtra(Intent.EXTRA_TITLE, "Image Chooser");

                        startActivityForResult(chooserIntent, FILECHOOSER_RESULTCODE_FOR_ANDROID_5);
                    } else if (items[which].equals(items[1])) {
                        dispatchTakePictureIntent();
                    }
                    dialog.dismiss();
                }
            })
            .setOnCancelListener(new DialogInterface.OnCancelListener() {
                @Override
                public void onCancel(DialogInterface dialog) {
                    Log.v(TAG, TAG + " # onCancel");
                    //important to return new Uri[]{}, when nothing to do. This can slove input file wrok for once.
                    //InputEventReceiver: Attempted to finish an input event but the input event receiver has already been disposed.
                    mUploadMessageForAndroid5.onReceiveValue(new Uri[]{});
                    mUploadMessageForAndroid5 = null;
                    dialog.dismiss();
            }}).show();
}

private void dispatchTakePictureIntent() {
    Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
    if (takePictureIntent.resolveActivity(mActivity.getPackageManager()) != null) {
//            File file = new File(createImageFile());
        Uri imageUri = null;
        try {
            imageUri = Uri.fromFile(createImageFile());
        } catch (IOException e) {
            e.printStackTrace();
        }
        //temp sd card file
        takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
        startActivityForResult(takePictureIntent, REQUEST_GET_THE_THUMBNAIL);
    }
}

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 = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/don_test/");
    if (!storageDir.exists()) {
        storageDir.mkdirs();
    }
    File image = File.createTempFile(
            imageFileName,  /* prefix */
            ".jpg",         /* suffix */
            storageDir      /* directory */
    );

    // Save a file: path for use with ACTION_VIEW intents
    mCurrentPhotoPath = image.getAbsolutePath();
    return image;
}

@Override
public void onActivityResult(int requestCode, int resultCode, Intent intent) {
    Log.v(TAG, TAG + " # onActivityResult # requestCode=" + requestCode + " # resultCode=" + resultCode);
    if (requestCode == FILECHOOSER_RESULTCODE) {
        if (null == mUploadMessage)
            return;
        Uri result = intent == null || resultCode != Activity.RESULT_OK ? null : intent.getData();
        mUploadMessage.onReceiveValue(result);
        mUploadMessage = null;

    } else if (requestCode == FILECHOOSER_RESULTCODE_FOR_ANDROID_5) {
        if (null == mUploadMessageForAndroid5)
            return;
        Uri result;

        if (intent == null || resultCode != Activity.RESULT_OK) {
            result = null;
        } else {
            result = intent.getData();
        }

        if (result != null) {
            Log.v(TAG, TAG + " # result.getPath()=" + result.getPath());
            mUploadMessageForAndroid5.onReceiveValue(new Uri[]{result});
        } else {
            mUploadMessageForAndroid5.onReceiveValue(new Uri[]{});
        }
        mUploadMessageForAndroid5 = null;
    } else if (requestCode == REQUEST_GET_THE_THUMBNAIL) {
        if (resultCode == Activity.RESULT_OK) {
            File file = new File(mCurrentPhotoPath);
            Uri localUri = Uri.fromFile(file);
            Intent localIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, localUri);
            mActivity.sendBroadcast(localIntent);

            Uri result = Uri.fromFile(file);
            mUploadMessageForAndroid5.onReceiveValue(new Uri[]{result});
            mUploadMessageForAndroid5 = null;
        } else {

            File file = new File(mCurrentPhotoPath);
            Log.v(TAG, TAG + " # file=" + file.exists());
            if (file.exists()) {
                file.delete();
            }
        }
    }
}

9
或者只返回null,使用.onReceiveValue(null) - FrozenFire
谢谢!但是这会导致一个奇怪的行为,即如果用户取消了文件选择(即 resultCode == Activity.RESULT_CANCELLED),则会向 WebView 提供 null 并清除原先选择的图像。如何解决这个问题? - Sira Lam
1
我该如何从一个片段中处理这个问题? - Mahmoud Omara
有人在片段上遇到这个问题吗? - Aadam

14

我遇到了类似的问题,在onShowFileChooser方法中返回了布尔值,当我调用超类方法时问题得到解决。

        @Override
        public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {
            //Logic to implement 
            return super.onShowFileChooser(webView, filePathCallback, fileChooserParams);
        }

2
我不明白。为什么你要覆盖一个方法,然后再从覆盖中调用它呢?这不是和没有该方法一样吗? - Nateowami
//实现逻辑 - 在调用超级方法之前实现逻辑 - ked
@ked,你救了我的一天,伙计。非常感谢 :) 干杯! - Rakesh
5
如жһњдҢ жџӨзњ‹WebChromeClient.javaдё­super.onShowFileChooser()зљ„жғђд»Әз ЃпәЊе®ѓеЏҒдәљз®ЂеҚ•ењ°иү”е›һfalseгЂ‚ - Udo Klimaschewski
那对我来说做到了! - Oleksandr Nos
显示剩余3条评论

7
在onShowFileChooser()方法中,只有当您使用filePathCallback时,才应返回true,这是最好的方式。
private ValueCallback<Uri[]> filePathCallback;

@Override
public boolean onShowFileChooser(
        WebView webView, ValueCallback<Uri[]> filePathCallback,
        WebChromeClient.FileChooserParams fileChooserParams) {
    // do whatever you need to do, to show a file chooser/camera intent
    this.filePathCallback = filePathCallback;
    return true;
}

@Override
protected void onActivityResult(int requestCode, int resultCode,
                                Intent intent) {
    // Check for your correct requestCode from your intent here
    if (resultCode == RESULT_CANCELED) {
        // this is important, call the callback with null parameter
        this.filePathCallback.onReceiveValue(null);
    } else if (resultCode == RESULT_OK) {
        // extract the uri(s) you need here
        this.filePathCallback.onReceiveValue(new Uri[]{result});
    }
}

result 变量未知。 - CoolMind
什么是变量result?你没有声明它。 - Azlan Jamal
1
变量result的类型为Uri,您必须从传递的Intent数据中提取所选文件的Uri。例如:Uri result = Uri.parse(intent.getDataString())。 - Udo Klimaschewski
谢谢您提供这段代码,非常有帮助。您可以像这样传递参数: filePathCallback.onReceiveValue(WebChromeClient.FileChooserParams.parseResult(resultCode, intent)); - Filoména Petržlénová
非常感谢!这是正确的解决方案!! - Sjd

6

我遇到了与onShowFileChooser相似的问题,它只能正常工作一次。经过几个小时的试错之后,我通过一个简单的实验解决了这个问题:

  • Not invoking ValueCallback will result onShowFileChooser only works once. Below code only works once...

    override fun onShowFileChooser(
      view: WebView?,
      filePath: ValueCallback<Array<Uri>>?,
      fileChooserParams: FileChooserParams?
    ): Boolean {
      Log.i("blah", "<== onShowFileChooser ==>")
      // filePath?.onReceiveValue(null)
      return true
    }
    
  • Properly invoking ValueCallback will make onShowFileChooser work every time:

    override fun onShowFileChooser(
      view: WebView?,
      filePath: ValueCallback<Array<Uri>>?,
      fileChooserParams: FileChooserParams?
    ): Boolean {
      Log.i("blah", "<== onShowFileChooser ==>")
      filePath?.onReceiveValue(null)
      return true
    }
    

所以我只需检查所有分支,确保所有正确调用ValueCallback,那么WebView就可以按预期工作。


这是唯一有效的解决方案,非常感谢。 - Hasan A Yousef

5

只需设置空值:

if (resultCode == Activity.RESULT_OK) {
    handleImageRequest(data)
} else {
    mValueCallbackArray?.onReceiveValue(null)
    mValueCallbackArray = null
}

resultCode是什么,它在哪里被调用?在onActivityResult中吗? - CoolMind

3

我曾经遇到过同样的问题,问题出在我期望得到一个OnActivityResult回调,但当你按下返回键时,它并没有被触发。

解决方法是在onResume中实现以下代码,告诉回调函数答案是空的,并能够重复使用它:

@Override
protected void onResume() {
    super.onResume();
    if (mFilePathCallbackArray == null)
        return;

    mFilePathCallbackArray.onReceiveValue(new Uri[]{});
    mFilePathCallbackArray = null;
}

1

我遇到了完全相同的问题,但我的使用情况有些不同,但我的webview与您的代码一样加载在片段上。第一次文件选择器在Lollipop和早期版本的设备上都能正常显示,但是从下一次开始,它就无法显示文件选择器本身了。原因是我第一次没有正确处理onActivityResult。我的适用于Lollipop的工作代码片段如下:

 @TargetApi(Build.VERSION_CODES.LOLLIPOP)

 private boolean onActivityResultLolliPop(
      int requestCode, int resultCode, Intent data) {

    if (requestCode != SurveyWebChromeClient.FILE_CHOOSER_REQUEST_CODE 
            || mFilePathCallbackL == null) {
        super.onActivityResult(requestCode, resultCode, data);
        return true;
    }

    Uri[] results = null;
    if (resultCode == Activity.RESULT_OK) {
        if (data == null) {
            if (mCameraPhotoVideoPath != null) {//if there is not data here, then we may have taken a photo/video
                results = new Uri[]{Uri.parse(mCameraPhotoVideoPath)};
            }
        } else {
            String dataString = data.getDataString();
            ClipData clipData = data.getClipData();

            if (clipData != null) {
                results = new Uri[clipData.getItemCount()];
                for (int i = 0; i < clipData.getItemCount(); i++) {
                    ClipData.Item item = clipData.getItemAt(i);
                    results[i] = item.getUri();
                }
            }

            if (dataString != null)
                results = new Uri[]{Uri.parse(dataString)};
        }
    }
    mFilePathCallbackL.onReceiveValue(results);
    mFilePathCallbackL = null;
    return false;
}

我的问题已经解决了。原因是在onPause()中暂停了webview,因此它没有响应按钮点击。 - Sangeetha Pinto
@AmandaFernandez,请发布您的代码以解决此问题,谢谢。 - Carl
@Carl,我已经发布了我的代码。在我的情况下,在片段的onPause()中,webview被暂停并且从未恢复...因此,onShowFileChoser()没有被调用。只需检查一下您在onActivityResult()中获取的结果代码。结果必须提供给您的活动,然后传递给FilePathCallback。也许结果没有传递给您的活动。请检查我的onActivityResultCode()。 - Sangeetha Pinto
@AmandaFernandez,感谢您的回答。我已经解决了这个问题。在我的情况下,问题出在onActivityResult()方法中,我没有处理结果。 - Carl

1

这对我有效(Kotlin,Jetpack Compose) ->

override fun onShowFileChooser(
        webView: WebView?,
        filePathCb: ValueCallback<Array<Uri>>?,
        fileChooserParams: FileChooserParams?,
    ): Boolean {

        if (filePathCallback != null) {
            filePathCallback!!.onReceiveValue(null)
        }
        filePathCallback = filePathCb

        return true
    }

将来我可以上传或拒绝文件上传 ->

        if(onCloseCameraClick){

            filePathCallback?.onReceiveValue(null)
            filePathCallback = null
            showMediaPicker = false
        }
        else{

            filePathCallback?.onReceiveValue(arrayOf(uri))
            filePathCallback = null
            showMediaPicker = false
        }

你能分享完整的代码吗?谢谢! - chkml
filePathCallback!!.onReceiveValue(null)这一行是关键,顺便说一下可以改进filePathCallback?.onReceiveValue(null) - Narek Hayrapetyan

1

只需让onShowFIleChooser()返回false,下次就会生效。

public boolean onShowFileChooser(){
... 
return **false**;
}

如果你设置为 false,那么当尝试调用 mFilePathCallback.onReceiveValue(result); 时,在 onActivityResult 中会抛出一个异常。 - CoolMind

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接