webview之加载H5界面无法调用手机本地图库

2024-09-06 16:18

本文主要是介绍webview之加载H5界面无法调用手机本地图库,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

webview加载H5页面,如果H5界面需要调用手机的本地图库

首先在此祝各位大佬远离BUG

  • 比如我们在开发中会遇到这样的场景,需要加载一个H5界面,这个界面里面可能有用户上传头像这个功能,但是当你怎么点击上传图片的时候它都无响应。但是你把这个H5用手机浏览器打开,会发现他可以正常调用手机本地的图库,对于此类问题,我分两种情况讲
  • Acvtivity里面用webview去加载 H5界面。
  • fragment里面用webview去加载 H5界面。

Acvtivity里面用webview去加载 H5界面。首先要重新设置myChromeViewClient和myWebViewClinet,会根据不用的系统调用不用的回调,2.0 3.0 4.0 5.0+,需要注意的是回调在onActivityResult方法里面。

解决方案上代码

首先需要定义成员变量:

private UploadHandler mUploadHandler;
private ValueCallback<Uri[]> mUploadMessageForAndroid5;
public final static int FILECHOOSER_RESULTCODE_FOR_ANDROID_5 = 2;
private MyChromeViewClient myChromeViewClient =new MyChromeViewClient();

然后拿到webview控件对其设置:

webview.setWebChromeClient(myChromeViewClient);
webview.setWebViewClient(myWebViewClinet);

重写onActivityResult方法,因为设置myChromeViewClient和myWebViewClinet,会根据不用的系统调用不用的回调,2.0
3.0 4.0 5.0+,而各种回调的执行都在onActivityResult里面:

@Overrideprotected void onActivityResult(int requestCode, int resultCode,Intent intent) {if (requestCode == Controller.FILE_SELECTED) {// Chose a file from the file picker.if (mUploadHandler != null) {mUploadHandler.onResult(resultCode, intent);}} else if (requestCode == FILECHOOSER_RESULTCODE_FOR_ANDROID_5) {if (null == mUploadMessageForAndroid5)return;Uri result = (intent == null || resultCode != Activity.RESULT_OK) ? null: intent.getData();System.out.println("-----------界面执行了回调"+(result == null));if (result != null) {mUploadMessageForAndroid5.onReceiveValue(new Uri[] { result });} else {mUploadMessageForAndroid5.onReceiveValue(new Uri[] {});}mUploadMessageForAndroid5 = null;}super.onActivityResult(requestCode, resultCode, intent);}

然后将一下代码复制到你的activity里面

class MyDownloadListener implements DownloadListener {@Overridepublic void onDownloadStart(String url, String userAgent,String contentDisposition, String mimetype, long contentLength) {// TODO Auto-generated method stub}}/*** 有几个类要说明下:* * MyChromeViewClient* 继承WebChromeClient重写了几个关键方法。其中有三个重载方法openFileChooser,用来兼容不同的Andorid版本* ,以防出现NoSuchMethodError异常。* 另外一个类UploadHandler,起到一个解耦合作用,它相当于WebChromeClient和Web网页端的一个搬运工兼职翻译* ,解析网页端传递给WebChromeClient的动作* ,然后将onActivityResult接收用户选择的文件传递给司机ValueCallback* 。WebChromeClient提供了一个Web网页端和客户端交互的通道,而UploadHandler就是用来搬砖的~。* UploadHandler有个很重要的成员变量:ValueCallback<Uri>* mUploadMessage。ValueCallback是WebView留下来的一个回调* ,就像是WebView的司机一样,当WebChromeClient和UploadHandler合作将文件选择后* ,ValueCallback开始将文件给WebView,告诉WebView开始干活了,砖头已经运回来了,你可以盖房子了。*/class MyChromeViewClient extends WebChromeClient {@Overridepublic void onCloseWindow(WebView window) {WebViewCustomer.this.finish();super.onCloseWindow(window);}public void onProgressChanged(WebView view, final int progress) {}@Overridepublic boolean onJsAlert(WebView view, String url, String message,final JsResult result) {new AlertDialog.Builder(WebViewCustomer.this).setTitle("提示信息").setMessage(message).setPositiveButton(android.R.string.ok,new DialogInterface.OnClickListener() {@Overridepublic void onClick(DialogInterface dialog,int which) {result.confirm();}}).setCancelable(false).create().show();return true;}@Overridepublic boolean onJsConfirm(WebView view, String url, String message,final JsResult result) {new AlertDialog.Builder(WebViewCustomer.this).setTitle("提示信息").setMessage(message).setPositiveButton(android.R.string.ok,new DialogInterface.OnClickListener() {@Overridepublic void onClick(DialogInterface dialog,int which) {result.confirm();}}).setNegativeButton(android.R.string.cancel,new DialogInterface.OnClickListener() {public void onClick(DialogInterface dialog,int which) {result.cancel();}}).setCancelable(false).create().show();return true;}// Android 2.xpublic void openFileChooser(ValueCallback<Uri> uploadMsg) {openFileChooser(uploadMsg, "");}// Android 3.0public void openFileChooser(ValueCallback<Uri> uploadMsg,String acceptType) {openFileChooser(uploadMsg, "", "filesystem");}// Android 4.1public void openFileChooser(ValueCallback<Uri> uploadMsg,String acceptType, String capture) {mUploadHandler = new UploadHandler(new Controller());mUploadHandler.openFileChooser(uploadMsg, acceptType, capture);}// For Android 5.0+public boolean onShowFileChooser(WebView webView,ValueCallback<Uri[]> filePathCallback,WebChromeClient.FileChooserParams fileChooserParams) {openFileChooserImplForAndroid5(filePathCallback);return true;}}private void openFileChooserImplForAndroid5(ValueCallback<Uri[]> uploadMsg) {mUploadMessageForAndroid5 = uploadMsg;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, "选择图片");System.out.println("-----------调用");startActivityForResult(chooserIntent,FILECHOOSER_RESULTCODE_FOR_ANDROID_5);}class MyWebViewClinet extends WebViewClient {@Overridepublic boolean shouldOverrideUrlLoading(WebView view, String url) {return true;}}// copied from android-4.4.3_r1/src/com/android/browser/UploadHandler.javaclass UploadHandler {/** The Object used to inform the WebView of the file to upload.*/private ValueCallback<Uri> mUploadMessage;private String mCameraFilePath;private boolean mHandled;private boolean mCaughtActivityNotFoundException;private Controller mController;public UploadHandler(Controller controller) {mController = controller;}public String getFilePath() {return mCameraFilePath;}boolean handled() {return mHandled;}public void onResult(int resultCode, Intent intent) {if (resultCode == Activity.RESULT_CANCELED&& mCaughtActivityNotFoundException) {// Couldn't resolve an activity, we are going to try again so// skip// this result.mCaughtActivityNotFoundException = false;return;}Uri result = (intent == null || resultCode != Activity.RESULT_OK) ? null: intent.getData();// As we ask the camera to save the result of the user taking// a picture, the camera application does not return anything other// than RESULT_OK. So we need to check whether the file we expected// was written to disk in the in the case that we// did not get an intent returned but did get a RESULT_OK. If it// was,// we assume that this result has came back from the camera.if (result == null && intent == null&& resultCode == Activity.RESULT_OK) {File cameraFile = new File(mCameraFilePath);if (cameraFile.exists()) {result = Uri.fromFile(cameraFile);// Broadcast to the media scanner that we have a new photo// so it will be added into the gallery for the user.mController.getActivity().sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE,result));}}mUploadMessage.onReceiveValue(result);mHandled = true;mCaughtActivityNotFoundException = false;}public void openFileChooser(ValueCallback<Uri> uploadMsg,String acceptType, String capture) {final String imageMimeType = "image/*";final String videoMimeType = "video/*";final String audioMimeType = "audio/*";final String mediaSourceKey = "capture";final String mediaSourceValueCamera = "camera";final String mediaSourceValueFileSystem = "filesystem";final String mediaSourceValueCamcorder = "camcorder";final String mediaSourceValueMicrophone = "microphone";// According to the spec, media source can be 'filesystem' or// 'camera' or 'camcorder'// or 'microphone' and the default value should be 'filesystem'.String mediaSource = mediaSourceValueFileSystem;if (mUploadMessage != null) {// Already a file picker operation in progress.return;}mUploadMessage = uploadMsg;// Parse the accept type.String params[] = acceptType.split(";");String mimeType = params[0];if (capture.length() > 0) {mediaSource = capture;}if (capture.equals(mediaSourceValueFileSystem)) {// To maintain backwards compatibility with the previous// implementation// of the media capture API, if the value of the 'capture'// attribute is// "filesystem", we should examine the accept-type for a MIME// type that// may specify a different capture value.for (String p : params) {String[] keyValue = p.split("=");if (keyValue.length == 2) {// Process key=value parameters.if (mediaSourceKey.equals(keyValue[0])) {mediaSource = keyValue[1];}}}}// Ensure it is not still set from a previous upload.mCameraFilePath = null;if (mimeType.equals(imageMimeType)) {if (mediaSource.equals(mediaSourceValueCamera)) {// Specified 'image/*' and requested the camera, so go ahead// and launch the// camera directly.startActivity(createCameraIntent());return;} else {// Specified just 'image/*', capture=filesystem, or an// invalid capture parameter.// In all these cases we show a traditional picker filetered// on accept type// so launch an intent for both the Camera and image/*// OPENABLE.Intent chooser = createChooserIntent(createCameraIntent());chooser.putExtra(Intent.EXTRA_INTENT,createOpenableIntent(imageMimeType));startActivity(chooser);return;}} else if (mimeType.equals(videoMimeType)) {if (mediaSource.equals(mediaSourceValueCamcorder)) {// Specified 'video/*' and requested the camcorder, so go// ahead and launch the// camcorder directly.startActivity(createCamcorderIntent());return;} else {// Specified just 'video/*', capture=filesystem or an// invalid capture parameter.// In all these cases we show an intent for the traditional// file picker, filtered// on accept type so launch an intent for both camcorder and// video/* OPENABLE.Intent chooser = createChooserIntent(createCamcorderIntent());chooser.putExtra(Intent.EXTRA_INTENT,createOpenableIntent(videoMimeType));startActivity(chooser);return;}} else if (mimeType.equals(audioMimeType)) {if (mediaSource.equals(mediaSourceValueMicrophone)) {// Specified 'audio/*' and requested microphone, so go ahead// and launch the sound// recorder.startActivity(createSoundRecorderIntent());return;} else {// Specified just 'audio/*', capture=filesystem of an// invalid capture parameter.// In all these cases so go ahead and launch an intent for// both the sound// recorder and audio/* OPENABLE.Intent chooser = createChooserIntent(createSoundRecorderIntent());chooser.putExtra(Intent.EXTRA_INTENT,createOpenableIntent(audioMimeType));startActivity(chooser);return;}}// No special handling based on the accept type was necessary, so// trigger the default// file upload chooser.startActivity(createDefaultOpenableIntent());}private void startActivity(Intent intent) {try {mController.getActivity().startActivityForResult(intent,Controller.FILE_SELECTED);} catch (ActivityNotFoundException e) {// No installed app was able to handle the intent that// we sent, so fallback to the default file upload control.try {mCaughtActivityNotFoundException = true;mController.getActivity().startActivityForResult(createDefaultOpenableIntent(),Controller.FILE_SELECTED);} catch (ActivityNotFoundException e2) {// Nothing can return us a file, so file upload is// effectively disabled.Toast.makeText(mController.getActivity(),"File uploads are disabled.", Toast.LENGTH_LONG).show();}}}private Intent createDefaultOpenableIntent() {// Create and return a chooser with the default OPENABLE// actions including the camera, camcorder and sound// recorder where available.Intent i = new Intent(Intent.ACTION_GET_CONTENT);i.addCategory(Intent.CATEGORY_OPENABLE);i.setType("*/*");Intent chooser = createChooserIntent(createCameraIntent(),createCamcorderIntent(), createSoundRecorderIntent());chooser.putExtra(Intent.EXTRA_INTENT, i);return chooser;}private Intent createChooserIntent(Intent... intents) {Intent chooser = new Intent(Intent.ACTION_CHOOSER);chooser.putExtra(Intent.EXTRA_INITIAL_INTENTS, intents);chooser.putExtra(Intent.EXTRA_TITLE, "Choose file for upload");return chooser;}private Intent createOpenableIntent(String type) {Intent i = new Intent(Intent.ACTION_GET_CONTENT);i.addCategory(Intent.CATEGORY_OPENABLE);i.setType(type);return i;}private Intent createCameraIntent() {Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);File externalDataDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM);File cameraDataDir = new File(externalDataDir.getAbsolutePath()+ File.separator + "browser-photos");cameraDataDir.mkdirs();mCameraFilePath = cameraDataDir.getAbsolutePath() + File.separator+ System.currentTimeMillis() + ".jpg";cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT,Uri.fromFile(new File(mCameraFilePath)));return cameraIntent;}private Intent createCamcorderIntent() {return new Intent(MediaStore.ACTION_VIDEO_CAPTURE);}private Intent createSoundRecorderIntent() {return new Intent(MediaStore.Audio.Media.RECORD_SOUND_ACTION);}}class Controller {final static int FILE_SELECTED = 4;Activity getActivity() {return WebViewCustomer.this;}}

以上代码就可以解决我们在webview加载H5界面的时候无法调用手机本地图库的问题。网上也有很多,和这个类似或者差不多的,我只能说我亲测这个是没有问题的。

如果你的webview是在fragment里面,那你用了我以上的方法就很难受,会骂我在网上随便百度一个就发播客,我也曾经这样过。

我们现在说一下在fragment是怎么回事。在activity里面的时候说过,解决这个问题需要我们设置myChromeViewClient和myWebViewClinet,会根据不用的系统调用不用的回调,2.0 3.0 4.0 5.0+,需要注意的是回调在onActivityResult方法里面,但是fragment的回调是在fragment所在的activity的onActivityResult方法里面,所以fragment里面需要实现的话,需要在他所在的activity的onActivityResult方法里面设置监听或者发送广播,然后调用fragment里面onActivityResult这个方法执行。(可以将这个方法重命名,只要执行就可以)。也可以将fragment里面的这个方法做成静态方法,直接去调用,然后将数据传递进行执行就可以。这里可能有些新手或者刚入门的小伙伴在开发中遇到这个问题,还是希望我不要这么多废话,直接给代码和解决方案:(在这我先说一下根本原因:就是图库选择的图片再回传的时候,传给了fragment所在的activity)

那么首先:
在fragment所在的activity里面重写

@Overrideprotected void onActivityResult(int arg0, int arg1, Intent arg2) {super.onActivityResult(arg0, arg1, arg2);if(arg0 == 2 || arg0 == 4){Intent intent = new Intent("pictureCallback"); intent.putExtra("requestCode", arg0);  intent.putExtra("resultCode", arg1);  if(arg2 == null){intent.putExtra("webviewintent", ""); }else{intent.putExtra("webviewintent", arg2.getData().toString()); }sendBroadcast(intent);}}

有人会问了,你这个2和4是什么鬼。我这样告诉你,我也不知道是什么鬼。但是我在fragment所在的activity里面的onActivityResult方法里面监听,每当我点击打开图片这个功能,都会在activity的onActivityResult方法里面获取到这两个数字,我觉得应该是每次activity去打开其他activity都有标识的,可能这个2和4就是。我试了很多次都是这样。如果你们用着不行,那建议你们在这个方法里面打印一下log日志,看一下你们的arg0是多少。但是我觉得应该是我这个没错。你们现在应该很清楚,我用的是广播这种方法。有人可能会吐槽,但是我觉得还不错。
我们继续,后面只需要在fragment里面去接收一下这个广播就可以了。就能执行上面的那些方法

在fragment里面定义变量

WebViewPictureCallback myBroadcastReceiver = null;

注册广播

myBroadcastReceiver = new WebViewPictureCallback();
IntentFilter filter = new IntentFilter("pictureCallback"); 
((MainActivity)getActivity()).registerReceiver(myBroadcastReceiver, filter);

自定义广播接收者

    public class WebViewPictureCallback extends BroadcastReceiver{@Overridepublic void onReceive(Context context, Intent intent) {if(intent != null&& intent.getAction().equals("pictureCallback")&& getResultCode() == Activity.RESULT_OK){Uri uri = null;if("".equals(intent.getStringExtra("webviewintent"))){uri = null;}else{uri = Uri.parse((intent.getStringExtra("webviewintent")));}gallery(intent.getIntExtra("requestCode", 0),intent.getIntExtra("resultCode", 0),uri);}}}

大家看到了gallery方法,其实就是前面activity里面说的onActivityResult方法,只是重新起了个方法名,然后去调用

    public void gallery(int requestCode, int resultCode, Uri data) {if (requestCode == Controller.FILE_SELECTED) {// Chose a file from the file picker.if (mUploadHandler != null) {mUploadHandler.onResult(resultCode, data);}} else if (requestCode == FILECHOOSER_RESULTCODE_FOR_ANDROID_5) {if (null == mUploadMessageForAndroid5){return;}Uri result = (data == null || resultCode != Activity.RESULT_OK) ? null: data;if (result != null) {mUploadMessageForAndroid5.onReceiveValue(new Uri[] { result });} else {mUploadMessageForAndroid5.onReceiveValue(new Uri[] {});}mUploadMessageForAndroid5 = null;}}

所以总结一下,在activity里面使用web view调用不了本地图库的方法我已经给出了,在fragment里面只是fragmnet没法响应回调,即打开图库,之后在图库里面选择的图片值传回来,fragment本身无法接收到,所以就多了一个中间环节,在fragment所在的activity里面先接受,然后把这个值不管你是想用我前面说的方法,还是我用的广播。在fragment里面去接收一下图库返回的值,然后其他的就还是按照上面的方法执行的。
以上是webview加载H5无法打开手机本地图库的问题。本来还有很多webview开发过程中的坑,发现一篇博客没法给大家说完,所以我之后还会写一些其他的webview开发遇到的坑。如果各位大佬有什么问题欢迎吐槽,如果对你有帮助,解决了问题。请帮忙顶一下。
谢谢

这篇关于webview之加载H5界面无法调用手机本地图库的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1142502

相关文章

Java调用DeepSeek API的最佳实践及详细代码示例

《Java调用DeepSeekAPI的最佳实践及详细代码示例》:本文主要介绍如何使用Java调用DeepSeekAPI,包括获取API密钥、添加HTTP客户端依赖、创建HTTP请求、处理响应、... 目录1. 获取API密钥2. 添加HTTP客户端依赖3. 创建HTTP请求4. 处理响应5. 错误处理6.

0基础租个硬件玩deepseek,蓝耘元生代智算云|本地部署DeepSeek R1模型的操作流程

《0基础租个硬件玩deepseek,蓝耘元生代智算云|本地部署DeepSeekR1模型的操作流程》DeepSeekR1模型凭借其强大的自然语言处理能力,在未来具有广阔的应用前景,有望在多个领域发... 目录0基础租个硬件玩deepseek,蓝耘元生代智算云|本地部署DeepSeek R1模型,3步搞定一个应

Deepseek R1模型本地化部署+API接口调用详细教程(释放AI生产力)

《DeepseekR1模型本地化部署+API接口调用详细教程(释放AI生产力)》本文介绍了本地部署DeepSeekR1模型和通过API调用将其集成到VSCode中的过程,作者详细步骤展示了如何下载和... 目录前言一、deepseek R1模型与chatGPT o1系列模型对比二、本地部署步骤1.安装oll

关于Spring @Bean 相同加载顺序不同结果不同的问题记录

《关于Spring@Bean相同加载顺序不同结果不同的问题记录》本文主要探讨了在Spring5.1.3.RELEASE版本下,当有两个全注解类定义相同类型的Bean时,由于加载顺序不同,最终生成的... 目录问题说明测试输出1测试输出2@Bean注解的BeanDefiChina编程nition加入时机总结问题说明

一分钟带你上手Python调用DeepSeek的API

《一分钟带你上手Python调用DeepSeek的API》最近DeepSeek非常火,作为一枚对前言技术非常关注的程序员来说,自然都想对接DeepSeek的API来体验一把,下面小编就来为大家介绍一下... 目录前言免费体验API-Key申请首次调用API基本概念最小单元推理模型智能体自定义界面总结前言最

Idea实现接口的方法上无法添加@Override注解的解决方案

《Idea实现接口的方法上无法添加@Override注解的解决方案》文章介绍了在IDEA中实现接口方法时无法添加@Override注解的问题及其解决方法,主要步骤包括更改项目结构中的Languagel... 目录Idea实现接China编程口的方法上无法添加@javascriptOverride注解错误原因解决方

JAVA调用Deepseek的api完成基本对话简单代码示例

《JAVA调用Deepseek的api完成基本对话简单代码示例》:本文主要介绍JAVA调用Deepseek的api完成基本对话的相关资料,文中详细讲解了如何获取DeepSeekAPI密钥、添加H... 获取API密钥首先,从DeepSeek平台获取API密钥,用于身份验证。添加HTTP客户端依赖使用Jav

一文教你使用Python实现本地分页

《一文教你使用Python实现本地分页》这篇文章主要为大家详细介绍了Python如何实现本地分页的算法,主要针对二级数据结构,文中的示例代码简洁易懂,有需要的小伙伴可以了解下... 在项目开发的过程中,遇到分页的第一页就展示大量的数据,导致前端列表加载展示的速度慢,所以需要在本地加入分页处理,把所有数据先放

本地搭建DeepSeek-R1、WebUI的完整过程及访问

《本地搭建DeepSeek-R1、WebUI的完整过程及访问》:本文主要介绍本地搭建DeepSeek-R1、WebUI的完整过程及访问的相关资料,DeepSeek-R1是一个开源的人工智能平台,主... 目录背景       搭建准备基础概念搭建过程访问对话测试总结背景       最近几年,人工智能技术

如何在本地部署 DeepSeek Janus Pro 文生图大模型

《如何在本地部署DeepSeekJanusPro文生图大模型》DeepSeekJanusPro模型在本地成功部署,支持图片理解和文生图功能,通过Gradio界面进行交互,展示了其强大的多模态处... 目录什么是 Janus Pro1. 安装 conda2. 创建 python 虚拟环境3. 克隆 janus