Android关于第三方h5在webview调用摄像头及相机的处理
参考资料:
深坑之Webview,解决H5调用android相机拍照和录像
除了没有适配6.0以上的动态申请以及拦截h5的方法不同,其余均参考以上文章
因为app和银行合作,在banner页投放了银行调用摄像进行人脸识别的功能,发现点击拉起摄像的功能无效,但是在内置浏览器却可以,由此怀疑需要对webview进行相关适配处理。后来查阅多方文档,发现上述文档比较详尽,同时总结下我自己的理解:
- 在h5页面调用原生的相机,自己的产品开发过程平时一般都是和前端沟通好,会在本地进行url的拦截,根据url判断。但是如果前端页面链接不含有特定字段同时都是js调用怎么办呢?所以如果业务需要,是要在webview里进行适配操作,就是复写下面提到的几个方法,说白了就是前端调用完相机,我们要复写下面的方法,并将原生相机的回调,图片的回调通过ValueCallback回调给webview,然后前端才可以通过方法接收到回调的参数进行校验。这样我们要做的事情就很简单了。根据版本不同复写方法,并且传入获取图片以后的回调即可。
这种问题解决办法在于复写openFileChoosers和onShowFileChoosers的方法:
可以先看下介绍
[Android开发深入理解WebChromeClient之onShowFileChooser或openFileChooser使用说明]
(http://teachcourse.cn/2224.html)
在所有发布的SDK版本中,openFileChooser是一个隐藏的方法,使用onShowFileChooser代替,但是最好同时复写WebChromeClient类里的showFileChooser和openFileChooser方法,Android 4.4.X以上的系统回调onShowFileChooser方法,低于或等于Android 4.4.X的系统回调openFileChooser方法,只重写onShowFileChooser或openFileChooser造成在有的系统可以正常回调,在有的系统点击没有反应。
1、前端调用摄像头的代码
前端调用摄像头拍照前端调用摄像头录像
一般第三方的h5调用原生的摄像都是这两个方法,我们可以通过拦截accept字段来进行判断(openFileChooser的复写方法里提供了),当然,如果链接里有其他字段,也可以通过onShouldOverrideUrlLoading进行链接的字段拦截。
2、复写方法
@Override protected void openFileChoosers(ValueCallback valueCallback) { LogUtil.e("czz", "运行方法 onShowFileChooser"); mUploadMessage = valueCallback; take(); } @Override protected void openFileChoosers(ValueCallback valueCallback, String acceptType) { LogUtil.e("czz", "运行方法 onShowFileChooser2"); mUploadMessage = valueCallback; mAcceptType = acceptType; take(); } @Override protected void openFileChoosers(ValueCallback valueCallback, String acceptType, String capture) { LogUtil.e("czz", "运行方法 onShowFileChooser3"); mUploadMessage = valueCallback; mAcceptType = acceptType; take(); } @Override protected void onShowFileChoosers(WebView webView, ValueCallback filePathCallback, WebChromeClient.FileChooserParams fileChooserParams) { LogUtil.e("czz", "运行方法 onShowFileChooser4"); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { if (fileChooserParams.getAcceptTypes().length > 0) { mAcceptType = fileChooserParams.getAcceptTypes()[0]; } } mUploadCallbackAboveL = filePathCallback; take(); }
/** * 拍照 */ private void take() { /*打开摄像头拍照通水进行动态权限申请,权限判定是自己封装的方法,仅供参考。*/ RxPermissions rxPermissions = new RxPermissions(this); rxPermissions.request(Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE) .subscribe(aBoolean -> { if (aBoolean) { try { if ("image/*".equals(mAcceptType)) { takePhoto(); } /*打开摄像头录像*/ if ("video/*".equals(mAcceptType)) { recordVideo(); } } catch (Exception e) { ApToast.showLong(mContext, mContext.getString(R.string.run_camera_error)); } } }); } /** * 摄像 */ private void takePhoto() { File fileUri = new File(Environment.getExternalStorageDirectory().getPath() + "/" + SystemClock.currentThreadTimeMillis() + ".jpg"); imageUri = Uri.fromFile(fileUri); if (Build.VERSION.SDK_INT >= 24) { imageUri = FileProvider.getUriForFile(this, getPackageName() + ".fileprovider", fileUri);//通过FileProvider创建一个content类型的Uri } takePicture(this, imageUri, PHOTO_REQUEST); } /** * 录像 */ private void recordVideo() { Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE); intent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 1); //限制时长 intent.putExtra(MediaStore.EXTRA_DURATION_LIMIT, 10); //开启摄像机 startActivityForResult(intent, VIDEO_REQUEST); } public static void takePicture(Activity activity, Uri imageUri, int requestCode) { //调用系统相机 Intent intentCamera = new Intent(); if (Build.VERSION.SDK_INT >= 24) { intentCamera.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); //添加这一句表示对目标应用临时授权该Uri所代表的文件 } intentCamera.setAction(MediaStore.ACTION_IMAGE_CAPTURE); //将拍照结果保存至photo_file的Uri中,不保留在相册中 intentCamera.putExtra(MediaStore.EXTRA_OUTPUT, imageUri); if (activity != null) { activity.startActivityForResult(intentCamera, requestCode); } }
回调处理
@Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); LogUtil.e("requestCode = " + requestCode + "\n" + "resultCode = " + resultCode); if (requestCode == PHOTO_REQUEST) { LogUtil.e("PHOTO_REQUEST1"); if (null == mUploadMessage && null == mUploadCallbackAboveL) return; Uri result = data == null || resultCode != RESULT_OK ? null : data.getData(); if (mUploadCallbackAboveL != null) { LogUtil.e("PHOTO_REQUEST2"); onActivityResultAboveL(requestCode, resultCode, data); } else if (mUploadMessage != null) { LogUtil.e("PHOTO_REQUEST3"); mUploadMessage.onReceiveValue(result); mUploadMessage = null; } } else if (requestCode == VIDEO_REQUEST) { if (null == mUploadMessage && null == mUploadCallbackAboveL) return; Uri result = data == null || resultCode != RESULT_OK ? null : data.getData(); if (mUploadCallbackAboveL != null) { if (resultCode == RESULT_OK) { mUploadCallbackAboveL.onReceiveValue(new Uri[]{result}); mUploadCallbackAboveL = null; } else { mUploadCallbackAboveL.onReceiveValue(new Uri[]{}); mUploadCallbackAboveL = null; } } else if (mUploadMessage != null) { if (resultCode == RESULT_OK) { mUploadMessage.onReceiveValue(result); mUploadMessage = null; } else { mUploadMessage.onReceiveValue(Uri.EMPTY); mUploadMessage = null; } } } }//这个回调主要是处理5.0以上的回调上传 private void onActivityResultAboveL(int requestCode, int resultCode, Intent data) { if (requestCode != PHOTO_REQUEST || mUploadCallbackAboveL == null) { LogUtil.e("onActivityResultAboveL1"); return; } Uri[] results = null; if (resultCode == Activity.RESULT_OK) { if (data == null) { LogUtil.e("onActivityResultAboveL2"); results = new Uri[]{imageUri}; } else { LogUtil.e("onActivityResultAboveL3"); 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)}; } } LogUtil.e("onActivityResultAboveL1" + results.toString()); mUploadCallbackAboveL.onReceiveValue(results); mUploadCallbackAboveL = null; }
更多相关文章
- Android年终盘点:阿里程序员必备技能知识点,程序员必须收藏
- Android开发之模板模式初探
- Java中输入流的read()为阻塞式方法的相关实例
- Android(安卓)控制线程的暂停和恢复
- android studio 适配android7.0 android 6.0拍照调用系统裁剪工
- Android(安卓)反射调用方法并利用此方法展开android通知栏(兼容4.
- 这可能是最好的 Android/Kotlin日志输出方法
- Android(安卓)Trick 11: 对Android中的AsyncTask进行函数化的封
- 【SMS】android 短信接收流程分析——-拦截短信示例代码