Android开发深入理解WebChromeClient之onShowFileChooser或openFileChooser使用说明 - TeachCourse

Android开发使用WebView控件加载包含表单的H5网页,点击上传文件按钮,弹出对话框,选择从相册获取照片、拍照或打开手机文件管理器,从Android手机选取一张图片或一个文件,然后通过ValueCallback接口传递,在WebView加载的H5网页显示。这里有一个问题,点击“取消”或返回按钮,无法重复回调onShowFileChooser或openFileChooser方法

来源: Android开发深入理解WebChromeClient之onShowFileChooser或openFileChooser使用说明 – TeachCourse

Android开发使用WebView控件加载包含表单的H5网页,点击上传文件按钮,弹出对话框,选择从相册获取照片、拍照或打开手机文件管理器,从Android手机选取一张图片或一个文件,然后通过ValueCallback接口传递,在WebView加载的H5网页显示。这里有一个问题,点击“取消”或返回按钮,无法重复回调onShowFileChooser或openFileChooser方法,控制台打印:Attempted to finish an input event but the input event receiver has already been disposed

一、深入理解onShowFileChooser或openFileChooser

上一篇文章TeachCourse详细分析WebChromeClient各个方法的使用,特殊说明关于WebChromeClient,它既不是接口也不是抽象类,但声明的方法很多方法体都是空的,这是让钊林感到疑惑之一,查看WebView源码,setWebChromeClient()传入WebChromeClient对象,然后使用传入的对象,调用WebChromeClient声明的方法,再将一些参数传递返回WebChromeClient空方法体重。在WebView源码里面代码也很简单,详细的处理处理逻辑看不到,这是让钊林感到疑惑之二,感觉像一个黑箱子。

然后就一直想,那么重写WebChromeClient的方法有什么作用呢?先看一下onShowFileChooser,如下:

  1. /**
  2.  * Tell the client to show a file chooser.
  3.  *
  4.  * This is called to handle HTML forms with ‘file’ input type, in response to the
  5.  * user pressing the “Select File” button.
  6.  * To cancel the request, call <code>filePathCallback.onReceiveValue(null)</code> and
  7.  * return true.
  8.  *
  9.  * @param webView The WebView instance that is initiating the request.
  10.  * @param filePathCallback Invoke this callback to supply the list of paths to files to upload,
  11.  *                         or NULL to cancel. Must only be called if the
  12.  *                         <code>showFileChooser</code> implementations returns true.
  13.  * @param fileChooserParams Describes the mode of file chooser to be opened, and options to be
  14.  *                          used with it.
  15.  * @return true if filePathCallback will be invoked, false to use default handling.
  16.  *
  17.  * @see FileChooserParams
  18.  */
  19. public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback,
  20.         FileChooserParams fileChooserParams) {
  21.     return false;
  22. }

该方法的作用,告诉当前APP,打开一个文件选择器,比如:打开相册、启动拍照或打开本地文件管理器,实际上更好的理解,WebView加载包含上传文件的表单按钮,HTML定义了input标签,同时input的type类型为file,手指点击该按钮,回调onShowFileChooser这个方法,在这个重写的方法里面打开相册、启动照片或打开本地文件管理器,甚至做其他任何的逻辑处理,点击一次回调一次的前提是请求被取消,而取消该请求回调的方法:给ValueCallback接口的onReceiveValue抽象方法传入null,同时onShowFileChooser方法返回true;

ValueCallback的抽象方法被回调onShowFileChooser方法返回true;反之返回false;再来看一下openFileChooser的源码,如下:

  1. /**
  2.  * Tell the client to open a file chooser.
  3.  * @param uploadFile A ValueCallback to set the URI of the file to upload.
  4.  *      onReceiveValue must be called to wake up the thread.a
  5.  * @param acceptType The value of the ‘accept’ attribute of the input tag
  6.  *         associated with this file picker.
  7.  * @param capture The value of the ‘capture’ attribute of the input tag
  8.  *         associated with this file picker.
  9.  *
  10.  * @deprecated Use {@link #showFileChooser} instead.
  11.  * @hide This method was not published in any SDK version.
  12.  */
  13. @SystemApi
  14. @Deprecated
  15. public void openFileChooser(ValueCallback<Uri> uploadFile, String acceptType, String capture) {
  16.     uploadFile.onReceiveValue(null);
  17. }

在所有发布的SDK版本中,openFileChooser是一个隐藏的方法,使用onShowFileChooser代替,但是最好同时重写showFileChooser和openFileChooser方法,Android 4.4.X以上的系统回调onShowFileChooser方法,低于或等于Android 4.4.X的系统回调openFileChooser方法,只重写onShowFileChooser或openFileChooser造成在有的系统可以正常回调,在有的系统点击没有反应。

仔细分析onShowFileChooser和openFileChooser回调方法,这两个方法之间的区别,

第一个区别:前者ValueCallback接口回传一个Uri数组,后者回传一个Uri对象,在onActivityResult回调方法中调用ValueCallback接口方法onReceiveValue传入参数特别注意;

  1. /**
  2.  *回调onShowFileChooser方法,onReceiveValue传入Uri对象数组
  3.  */
  4. mFilePathCallback.onReceiveValue(new Uri[]{uri});
  1. /**
  2.  *回调openFileChooser方法,onReceiveValue传入一个Uri对象
  3.  */
  4. mFilePathCallback4.onReceiveValue(uri);

第二个区别:前者将后者的acceptType、capture封装成FileChooserParams抽象类

二、实例展示onShowFileChooser或openFileChooser处理过程

1223-0941-popupdialog-2
这是实例运行的效果图,H5表单写入两个上传文件的按钮,点击其中一个从底部弹出对话框,选择相册文件或拍照,点击“取消”按钮,再次点击“上传文件”按钮能够再次回调onShowFileChooser或openFileChooser方法。

在之前的理解中,误解onShowFileChooser或openFileChooser只能打开相册或启动相机拍照,其实不仅仅是这样,onShowFileChooser或openFileChooser既然是一个回调的方法,可以重复执行各种逻辑代码,比如:启动另一个Activity、弹窗对话框、录制视频或录音等

在上面的例子中,执行弹窗操作,将弹窗的处理代码放置onShowFileChooser或openFileChooser方法体,如下:

  1. @Override
  2. public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {
  3.     super.onShowFileChooser(webView, filePathCallback, fileChooserParams);
  4.          popupDialog();
  5.     PickPhotoUtil.mFilePathCallback = filePathCallback;
  6.     /**
  7.      * 返回true,如果filePathCallback被调用;返回false,如果忽略处理
  8.      */
  9.     return true;
  10. }

  1. public void openFileChooser(ValueCallback<Uri> filePathCallback, String acceptType, String capture) {
  2.              popupDialog();
  3.         String title = acceptType;
  4.         PickPhotoUtil.mFilePathCallback4 = filePathCallback;
  5.     }

点击弹窗取消按钮、点击打开相册取消操作或取消拍照,可能无法再次回调onShowFileChooser或openFileChooser方法,如果你没有在点击弹窗取消方法中或onActivityResult回调方法resultCode==RESULT_CANCELED处理,再次点击上传按钮,打印出log:

Attempted to finish an input event but the input event receiver has already been disposed

同时,点击没有效果

  1. /**
  2.      * 弹窗,启动拍照或打开相册
  3.      */
  4.     public void popupDialog() {
  5.         ActionSheetDialog actionSheetDialog= new ActionSheetDialog(activity).builder()
  6.                 .setCancelable(false)
  7.                 .setCanceledOnTouchOutside(false)
  8.                 .addSheetItem(“手机拍照”, ActionSheetDialog.SheetItemColor.Blue,
  9.                         new ActionSheetDialog.OnSheetItemClickListener() {
  10.                             @Override
  11.                             public void onClick(int which) {
  12.                                 goToTakePhoto();
  13.                             }
  14.                         })
  15.                 .addSheetItem(“手机相册”, ActionSheetDialog.SheetItemColor.Blue,
  16.                         new ActionSheetDialog.OnSheetItemClickListener() {
  17.                             @Override
  18.                             public void onClick(int which) {
  19.                                 goForPicFile();
  20.                             }
  21.                         });
  22.         actionSheetDialog.show();
  23.         /**
  24.          * 设置点击“取消”按钮监听,目的取消mFilePathCallback回调,可以重复调起弹窗
  25.          */
  26.         actionSheetDialog.setOnClickListener(new View.OnClickListener() {
  27.             @Override
  28.             public void onClick(View v) {
  29.                 cancelFilePathCallback();
  30.             }
  31.         });
  32.     }
  1. /**
  2.      * onActivityResult回调方法,当resultCode==RESULT_CANCELED,取消mFilePathCallback回调,可以* 重复调起弹窗
  3.      */
  4.         @Override
  5.     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
  6.         super.onActivityResult(requestCode, resultCode, data);
  7.         switch (requestCode) {
  8.         /**
  9.          *打开系统自带文件管理器回调
  10.          */
  11.             case PickPhotoUtil.REQUEST_FILE_PICKER:
  12.                 pickPhotoResult(resultCode, data);
  13.                 break;
  14.         /**
  15.          *打开相册回调
  16.          */
  17.             case PickPhotoUtil.REQUEST_CODE_PICK_PHOTO:
  18.                 pickPhotoResult(resultCode, data);
  19.                 break;
  20.         /**
  21.          *拍照后回调
  22.          */
  23.             case PickPhotoUtil.REQUEST_CODE_TAKE_PHOTO:
  24.                 takePhotoResult(resultCode);
  25.                 break;
  26.             default:
  27.                 break;
  28.         }
  29.     }
  1. /**
  2.      *取消mFilePathCallback回调
  3.      */
  4.     private void cancelFilePathCallback() {
  5.         if (PickPhotoUtil.mFilePathCallback4 != null) {
  6.             PickPhotoUtil.mFilePathCallback4.onReceiveValue(null);
  7.             PickPhotoUtil.mFilePathCallback4 = null;
  8.         } else if (PickPhotoUtil.mFilePathCallback != null) {
  9.             PickPhotoUtil.mFilePathCallback.onReceiveValue(null);
  10.             PickPhotoUtil.mFilePathCallback = null;
  11.         }
  12.     }

在不期待回调mFilePathCallback的onReceiveValue方法时,调用cancelFilePathCallback(),解决点击上传按钮无法重复回调的问题。

ps:

Demo已上传GitHub,路径view\webview\UploadImgForH5Activity.java

赞(0) 打赏
分享到: 更多 (0)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏