在WebView中调用Android相机拍照录像

最近公司App需要以H5的方式接入七鱼客服,根据他们的开发文档接入起来还是很容易,成本很低。理论上如果以native的方式接入的话,用户体验会更好,但是接入后增加的包体积是无法接受的,遂改用H5的方式,整个接入过程还算顺利,有相对详尽的接入文档和demo,只是在最后接入完成后,因为沟通不畅,忽略了客服聊天界面在Android上无法发图片和发视频的问题。同一份html,iOS就是正常的,Android上就死活没反应。后来才恍然大悟,WebView是不支持JS去直接操作Android相机的,必须通过回调到native,由native完成照片的选择,拍摄和录像后将数据返回给JS才能完成一次照片和视频的发送。清楚了问题所在,就需要实现WebView的标准接口来实现这个回调到native的功能,在代码实现前,需要先厘清一些WebView的基本概念和原理。

像发送图片和视频这样的操作,涉及到了定制WebView的一些默认行为,理论上如果要做定制,就需要了解WebSettings、JavaScriptInterface、WebViewClient以及WebChromeClient,一般而言,通过配置WebSettings,使用JavasScriptInterface,重写WebViewClient和WebChromeClient对象的相关方法,就可以实现一些我们想要的行为。发图片和发视频就是通过重写WebChromeClient对象的几个方法来实现的。


// 重写WebChromeClient的特定方法来实现图片和视频的发送
mWebView.setWebChromeClient(new WebChromeClient() {
    // For Android >=3.0
    public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType) {
        if(acceptType.equals("image/*")) {
            if (mUploadMessage != null) {
                mUploadMessage.onReceiveValue(null);
                return;
            }
            mUploadMessage = uploadMsg;
            selectImage();
        } else {
            onReceiveValue();
        }
    }

    // For Android < 3.0
    public void openFileChooser(ValueCallback<Uri> uploadMsg) {
        openFileChooser(uploadMsg, "image/*");
    }

    // For Android  >= 4.1.1
    public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture) {
        openFileChooser(uploadMsg, acceptType);
    }

    // For Android  >= 5.0
    @Override
    @SuppressLint("NewApi")
    public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {
        if (fileChooserParams != null && fileChooserParams.getAcceptTypes() != null
                && fileChooserParams.getAcceptTypes().length > 0 && fileChooserParams.getAcceptTypes()[0].equals("image/*")) {
            if (mUploadMessageArray != null) {
                mUploadMessageArray.onReceiveValue(null);
            }
            mUploadMessageArray = filePathCallback;
            selectImage();
        } else if (fileChooserParams != null && fileChooserParams.getAcceptTypes() != null
                && fileChooserParams.getAcceptTypes().length > 0 && fileChooserParams.getAcceptTypes()[0].equals("video/*")){

            if (mUploadMessageArray != null) {
                mUploadMessageArray.onReceiveValue(null);
            }
            mUploadMessageArray = filePathCallback;
            PermissionUtil.requestPermission(QiyuWebViewActivity.this, PermissionUtil.PERMISSIONS_CAMERA_RECORD_AUDIO, PermissionUtil.REQUEST_CAMERA_RECORD_AUDIO);
        } else {
            onReceiveValue();
        }
        return true;
    }
});

需要注意的是,这几个回调方法,需要针对不同Android版本分别做处理,在onShowFileChooser方法中需要区分是图片还是视频,并且无论是在相册中选取还是拍摄照片,录像都需要申请相应的权限,这个一定不能少,网上很多的demo都是没有权限申请环节的,代码根本不可用。

在正确的回调到onShowFileChooser后,就要区分图片和视频的情况下走正常的选取,拍照,录像的流程,并且把获取到的图片,视频数据回传给JS调用,这样就完成了桥接调用。虽然现在看起来挺简单的,但是还是有些点容易成为坑点,让人走很多弯路:

  • 需要的配置一定要设定
mWebView.getSettings().setJavaScriptEnabled(true);
mWebView.getSettings().setDomStorageEnabled(true);

  • 动态权限申请一定不能少

  • 正确区分图片和视频

// "video/* 为视频"
fileChooserParams.getAcceptTypes()[0].equals("image/*")

总结一下,在WebView的JS调用系统相机的关键是重写WebChromeClient的特定方法来实现的。