前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >让 Android 的 WebView 支持 type 为 file 的 input,同时支持拍照

让 Android 的 WebView 支持 type 为 file 的 input,同时支持拍照

作者头像
LeoXu
发布于 2018-08-15 06:31:45
发布于 2018-08-15 06:31:45
1.7K00
代码可运行
举报
文章被收录于专栏:LeoXu的博客LeoXu的博客
运行总次数:0
代码可运行

Android 的 WebView 组件默认是不启用 type 为 file 的 input 的,需要在代码中做一些类似 hack 的编码(因为解决问题的目标对象的方法都是加了@hide注解的)才能召唤神龙。

目标对象:WebChromeClient

实例化一个目标对象,并重写它的几个隐藏方法(针对不同的Android系统版本,方法名和入参都不一样,所以方法有多个),然后将目标对象作为参数传递给 WebView 对象的 setWebChromeClient 方法。

目标对象隐藏方法的重写

// For Android 3.0+

public void openFileChooser( ValueCallback uploadMsg, String acceptType )...

// For Android 3.0+

public void openFileChooser(ValueCallback uploadMsg)...

//For Android 4.1

public void openFileChooser(ValueCallback uploadMsg, String acceptType, String capture)...

// For Lollipop 5.0+ Devices

public boolean onShowFileChooser(WebView mWebView, ValueCallback<Uri[]> filePathCallback,  WebChromeClient.FileChooserParams fileChooserParams)...

代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
	private WebChromeClient mWebChromeClient = new WebChromeClient(){
		
		// For Android 3.0+
		@SuppressWarnings({ "rawtypes" })
		public void openFileChooser( ValueCallback uploadMsg, String acceptType ) {
			vCbFileChooser = uploadMsg;
			/*Intent i = new Intent(Intent.ACTION_GET_CONTENT);
			i.addCategory(Intent.CATEGORY_OPENABLE);
			i.setType("image/*");
			MainActivity.this.startActivityForResult(
					Intent.createChooser(i,"文件选择"), 
					FILECHOOSER_RESULTCODE
			);*/
			selPic();
		}
		
		// For Android 3.0+
		@SuppressWarnings({ "unused", "rawtypes" })
		public void openFileChooser(ValueCallback uploadMsg) {
			openFileChooser(uploadMsg, "");
		}
		
		//For Android 4.1
		@SuppressWarnings({ "unused", "rawtypes" })
		public void openFileChooser(ValueCallback uploadMsg, String acceptType, String capture){
			openFileChooser(uploadMsg, acceptType);
		}
		
		// For Lollipop 5.0+ Devices
		@SuppressWarnings("unchecked")
		@TargetApi(Build.VERSION_CODES.LOLLIPOP)
		public boolean onShowFileChooser(
				WebView mWebView, ValueCallback<Uri[]> filePathCallback, 
				WebChromeClient.FileChooserParams fileChooserParams
		) {
			if (vCbFileChooser != null) {
				vCbFileChooser.onReceiveValue(null);
				vCbFileChooser = null;
			}
			
			vCbFileChooser = filePathCallback;
			
			selPic();
			
			return true;
		}
	};

在上面的代码中:

    1、所有被重写的方法最后都会调用 selPic 方法,这个方法会显示一个对话框,让用户选择是拍照选取照片还是直接从已保存的文件中选取图片。

    2、vCbFileChooser 变量维持着向页面回传值的 ValueCallback 对象,直到 onActivityResult。

selPic 方法实现

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
	/**
	 * 弹出对话框,提示拍照或者选择照片文件
	 */
	@SuppressWarnings("unused")
	protected final void selPic() {
		if (!checkSDcard()){return;}
		String[] selectPicTypeStr = { "拍照","选择照片" };
		AlertDialog alertDialog = new AlertDialog.Builder(this)
			.setItems(
				selectPicTypeStr,
				new DialogInterface.OnClickListener() {
					
					@Override
					public void onClick(DialogInterface dialog, int which) {
						switch (which) {
							case 0://拍照
								chkPrivBeforeTakePhoto();
								break;
							case 1://选择图片文件
								choosePicFile();
								break;
							default:
								break;
						}
						
					}
				}
			).setOnCancelListener(
				new DialogInterface.OnCancelListener() {
					
					@SuppressWarnings("unchecked")
					@Override
					public void onCancel(DialogInterface dialog) {
						if (null != vCbFileChooser) {
							vCbFileChooser.onReceiveValue(null);
							vCbFileChooser = null;
						}
					}
				}
			).show();
	}

上述代码:

    1、chkPrivBeforeTakePhoto 方法执行拍照选取流程(之所以这样取名,是因为在拍照之前,还要考虑到Android 6.0以上版本权限系统机制的变化);

    2、choosePicFile 方法执行直接从已保存文件中选取图片的流程;

   3、如果两中流程都没有,而是执行了取消操作(按下返回键或者点击了界面空白处),那么 vCbFileChooser 变量也必须调用 onReceivValue 方法回传空值,保证type=file的input能反复使用。

    4、checkSDcard 方法的作用是在拍照以前判断有没有存储。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
	/**
	 * 检查SD卡是否存在
	 */
	public final boolean checkSDcard() {
		boolean flag = Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED);
		if(!flag){Toast.makeText(this, "请插入手机存储卡再使用本功能", Toast.LENGTH_SHORT).show();}
		return flag;
	}

chkPrivBeforeTakePhoto 方法

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
	private static final int PERMISSIONS_REQUEST_CODE_TAKE_PHOTO = 1;
	@SuppressWarnings("unchecked")
	private void chkPrivBeforeTakePhoto(){
		if(
				ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED ||
				ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED
		) {
			if (null != vCbFileChooser) {
				vCbFileChooser.onReceiveValue(null);
				vCbFileChooser = null;
			}
			new AlertDialog
				.Builder(this)
				.setTitle("提示信息")
				.setMessage("该功能需要您接受应用对一些关键权限(拍照)的申请,如之前拒绝过,可到手机系统的应用管理授权设置界面再次设置。")
				.setPositiveButton("确认", new OnClickListener() {
		
					@Override
					public void onClick(DialogInterface dialog, int which) {
						ActivityCompat.requestPermissions(MainActivity.this, new String[]{
								Manifest.permission.CAMERA,
								Manifest.permission.WRITE_EXTERNAL_STORAGE
						}, PERMISSIONS_REQUEST_CODE_TAKE_PHOTO);
					}
				})
				.show();
		} else {
			chooseTakePhoto();
		}
	}
	
	private void chooseTakePhoto(){
		pathTakePhoto = Environment.getExternalStorageDirectory().getPath()
				+ "/mbossclient/camera/temp/"
				+ (System.currentTimeMillis() + ".jpg");
		File vFile = new File(pathTakePhoto);
		if (!vFile.exists()) {//必须确保文件夹路径存在,否则拍照后无法完成回调
			File vDirPath = vFile.getParentFile();
			vDirPath.mkdirs();
		} else {
			if (vFile.exists()) {
				vFile.delete();
			}
		}
		
		Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
		uriTakePhoto = Uri.fromFile(vFile);
		intent.putExtra(MediaStore.EXTRA_OUTPUT, uriTakePhoto);
		startActivityForResult(intent, TAKEPHOTO_RESULTCODE);
	}

上述代码:

    1、Android 6.0 及以上版本都需要就权限进行询问操作;

    2、chooseTakePhoto 方法执行实际的拍照流程;

    3、TAKEPHOTO_RESULTCODE 用于在 onActivityResult 方法中识别出是执行了拍照选取的流程。

choosePicFile 方法

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
	/**
	 * 选择文件
	 */
	private void choosePicFile(){
		Intent i = new Intent(Intent.ACTION_GET_CONTENT);
		i.addCategory(Intent.CATEGORY_OPENABLE);
		i.setType("image/*");
		MainActivity.this.startActivityForResult(
				Intent.createChooser(i,"文件选择"), 
				FILECHOOSER_RESULTCODE
		);
	}

FILECHOOSER_RESULTCODE 用于在onActivityResult方法中识别出是执行了从已保存文件中选取图片文件的流程。

onActivityResult 方法

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
	@SuppressLint("NewApi")
	@SuppressWarnings("unchecked")
	@Override
	protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
		if (requestCode == FILECHOOSER_RESULTCODE) {//从文件选择器选择照片
			if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
				if(null == vCbFileChooser) {return;}
				vCbFileChooser.onReceiveValue(WebChromeClient.FileChooserParams.parseResult(resultCode, intent));
				vCbFileChooser = null;
			} else {
				if(null == vCbFileChooser) {return;}
				Uri result = (intent == null || resultCode != RESULT_OK)? null:intent.getData();
				vCbFileChooser.onReceiveValue(result);
				vCbFileChooser = null;
			}
		} else if(requestCode == TAKEPHOTO_RESULTCODE){
			if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
			    if(null == vCbFileChooser) {return;}
				if(null == uriTakePhoto) {
					vCbFileChooser.onReceiveValue(null);
					vCbFileChooser = null;
					return;
				}
				addImageGallery(pathTakePhoto);
				Uri[] uris = new Uri[1];
				uris[0] = uriTakePhoto;
				vCbFileChooser.onReceiveValue(uris);
				vCbFileChooser = null;
				uriTakePhoto = null;
				pathTakePhoto = null;
		    } else {
				if(null == vCbFileChooser) {return;}
				if(null == uriTakePhoto) {
					vCbFileChooser.onReceiveValue(null);
					vCbFileChooser = null;
					return;
				}
				addImageGallery(pathTakePhoto);
				vCbFileChooser.onReceiveValue(uriTakePhoto);
				vCbFileChooser = null;
				uriTakePhoto = null;
				pathTakePhoto = null;
			}
		}
		
		super.onActivityResult(requestCode, resultCode, intent);
	}

上述代码:

    1、以Android Lollipop版本为届,低于该版本的系统与等于或高于该版本的系统处理方式不一样,表面上看主要是使用API获取uri数据的方法不同;

    2、无论取没取到 uri 数据,只要 vCbFileChooser 变量不为空,都必须调用一次 onReceiveValue 方法,而且这之后要将它以及相关变量置为null,以保证type=file的input能反复使用。

    3、addImageGallery 方法的作用是将拍照生成的图片(不是缩略图)添加到相册,保证后续还能从系统中索取到。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
	/**
	 * 解决拍照后在相册中找不到的问题 
	 */
	private void addImageGallery(String path) {
		if (null == path || "".equals(path)) {
			return;
		}
		File file = new File(pathTakePhoto);
		ContentValues values = new ContentValues();
		values.put(MediaStore.Images.Media.DATA, file.getAbsolutePath());
		values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
		getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
	}
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2017/04/23 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Android WebView 上传文件支持全解析
声明:原文地址:http://blog.isming.me/2015/12/21/android-webview-upload-file/,转载请注明出处。 默认情况下情况下,使用Android的WebView是不能够支持上传文件的。而这个,也是在我们的前端工程师告知之后才了解的。因为Android的每个版本WebView的实现有差异,因此需要对不同版本去适配。花了一点时间,参考别人的代码,这个问题已经解决,这里把我踩过的坑分享出来。 主要思路是重写WebChromeClient,然后在WebViewAct
非著名程序员
2018/02/02
64.5K0
Webview与H5交互——支持Intput type=”file“属性
  利用原生加H5进行混合开发时,遇到问题:在H5利用Input type=“file” 调用android本地图库上传图片时,在普通浏览器可以执行,在Webview上出现了问题。是因为 android webview 由于考虑安全原因屏蔽了 <input type="file"/> 这个功能 。重写webview 的WebChromeClient可以解决。
饮水思源为名
2018/09/06
1K0
Android 使用腾讯X5内核, Webview浏览器拍照或从相册上传图片
最近在项目开发中,需要使用WebView上传文件。默认情况下情况下,使用Android的WebView是不能够支持上传文件的。经过查找资料,得知需要重新WebChromeClient,根据选择到的文件Uri,传给页面去上传就可以了。
开发者技术前线
2020/11/23
2.2K0
Android 使用腾讯X5内核, Webview浏览器拍照或从相册上传图片
webview拉起拍照和录像的爬坑终结篇
即只需在配置里加上摄像头和麦克风的使用权限。具体做法是在App 的info.plist中加入:
老码小张
2021/01/23
4.2K1
webview拉起拍照和录像的爬坑终结篇
Android WebView选择图片、发送图片
主要代码来自:http://blog.csdn.net/woshinia/article/details/19030437 有删改
yechaoa
2022/06/10
9570
Android开发笔记(一百六十六)H5通过WebView录像上传
前面的博文《Android开发笔记(一百五十二)H5通过WebView上传图片》介绍了如何拍照上传给网页,不料客户又要求再加个摄像上传给网页。既然如此,那么再探讨一下如何实现这个摄像上传的功能。 与拍照上传一样,摄像上传也要重写WebChromeClient的openFileChooser/onShowFileChooser方法,在这两个方法内部跳转到系统的摄像机页面,示例代码如下:
aqi00
2019/01/18
1.4K0
WebView 的 input 上传照片的兼容问题
问题 前几天接到的一个需求,是关于第三方理财产品的 H5 上传照片问题。 对方说他们的新的需求,需要接入方配合上传资产照片的需求,测试之后发现我们这边的 app 端,IOS 端上传没有问题,而 Android 端则点击没有任何反应。 对方 H5 调用的方式是通过<input type='file' accept='image/*'/>的方式调用,本来以为这个问题很简单,就是 app 端没有设置相机权限,造成的点击无反应情况,而实际上加了之后发现,并非简单的权限问题。 解决问题 因为 Android 的版本
非著名程序员
2018/02/09
2.2K0
Android开发笔记(一百五十二)H5通过WebView上传图片
上一篇文章介绍了WebView与JS之间的数据交互,其实就是把字符串传来传去,这对文本格式的信息传输来说倒还凑合,倘若要传输图片信息就不管用了。所以,要想让h5网页支持从手机上传图片,还得另外想办法,当然各版本的Android系统也都提供了相应的解决办法。在Android 4.*系统上面,开发者可以重写WebChromeClient的openFileChooser函数;在Android 5.0以上的系统,开发者可以重写WebChromeClient的onShowFileChooser函数。话虽如此,可实际编码的时候,会发现并不容易,因为不但要兼容各种版本的安卓系统,而且要考虑不同操作方式下面的处理步骤。 首先是Android不同系统的适配问题,对于4.*版本要重写openFileChooser方法,对于5.0以上版本要重写onShowFileChooser方法。另外注意二者的回调方式也不一样,4.*的回调参数类型是ValueCallback<Uri>,而5.0以上的回调参数类型是ValueCallback<Uri[]>,因此要声明两个回调参数变量,分别用来保存二者各自的回调信息。相关代码如下所示:
aqi00
2019/01/18
1.4K0
Android WebView那些坑之上传文件
最近公司项目需要在WebView上调用手机系统相册来上传图片,开发过程中发现在很多机器上无法正常唤起系统相册来选择图片。 解决问题之前我们先来说说WebView上传文件的逻辑:当我们在Web页面上点击选择文件的控件(<input type="file">)时,会回调WebChromeClient下的openFileChooser()(5.0及以上系统回调onShowFileChooser())。这个时候我们在openFileChooser方法中通过Intent打开系统相册或者支持该Intent的第三方
张磊BARON
2018/04/13
2.8K0
Android WebView那些坑之上传文件
js与android webview交互
0x01 js调用java代码 android webview中支持通过添加js接口 webview.addJavascriptInterface(new JsInteration(), "control"); 参数说明: 第一个:java对象对应这个WebView的JavaScript上下文 第二个:调用java对象的js中引用对象 Parameters:  1 object the Java object to inject into this WebView's JavaScript context.
用户1148881
2018/01/17
4.2K0
项目需求讨论 - WebView下拍照及图片选择功能
现在很多app里面,都会有这么一个需求,就是上传图片的按钮,当然按了这个按钮之后,就会出现二种选择: 1. 直接拍照,2. 相册选择现有图片。
青蛙要fly
2018/08/29
2.1K0
项目需求讨论 - WebView下拍照及图片选择功能
助你快速搭建一个健壮可控的WebApp
  笔者因公司需求,从0打造一款WebApp,一直维护到现在。整个接口算是从混乱到现在的有序。笔者也从一个WebView+H5的小菜鸟,磨炼成了中等生。   WebApp简单来讲,就是利用原生的WebView承载H5的html页面,并且实现JS和原生之间的通信。   WebApp的好处是显而易见的。业务页面来源于H5,原生作为一个承载壳提供流畅性支持,能够低成本的实现跨平台的实施以及快速嵌入微信小程序、钉钉、OA等APP中。与纯H5的App相比较,它能够更轻易的使用原生底层库,并且更加流畅;而与纯原生
饮水思源为名
2018/12/13
1.1K0
助你快速搭建一个健壮可控的WebApp
android系统webview最新版本_webview加载h5页面空白
做android聊天时,遇到过一个问题,h5的页面发送的图片在android端不能响应,ios那边一路畅通。也是相当无奈,目前发现了好多android端与ios端webView的异同。
全栈程序员站长
2022/11/09
1.3K0
强大灵活的WebView代理库-PrimWeb
PrimWeb 是一个代理的WebView基于的 Android WebView 和 腾讯 x5 WebView,容易、灵活使用以及功能非常强大的库,提供了 WebView 一系列的问题解决方案 ,并且轻量和灵活, 更方便 webview 的切换.
用户3045442
2018/09/11
2.1K0
强大灵活的WebView代理库-PrimWeb
首个hybird商业项目踩坑总结
该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列。该系列引用了《Android开发艺术探索》以及《深入理解Android 卷Ⅰ,Ⅱ,Ⅲ》中的相关知识,另外也借鉴了其他的优质博客,在此向各位大神表示感谢,膜拜!!!
LoveWFan
2018/09/27
1.3K0
首个hybird商业项目踩坑总结
WebView深度学习(二)之全面总结WebView遇到的坑及优化
这篇文章讲一下WebView遇到的那些坑,带领各位爬坑。这里如果有你没遇到的问题,欢迎留言告诉我,我尽我所能帮你解决。感谢大家支持。
AWeiLoveAndroid
2018/09/03
6.1K0
WebView深度学习(二)之全面总结WebView遇到的坑及优化
Android平台相机接口的应用
第一部分、前述: Android作为Google移动互联网战略的重要组成部分,将进一步推进“随时随地为每个人提供信息”这一企业目标的实现。Google的目标是让移动通信不依赖于设备,甚至是平台。出于这个目的,Android将完善而不是替代Google长期以来推行的移动发展战略:通过与全球各地的手机制造商和移动运营商成为合作伙伴,开发既实用又有吸引力的移动服务,并推广这些产品。 Android平台的研发队伍阵容强大,包括Google、HTC(宏达电)、T-Mobile、高通、摩托罗拉、三星、LG以及中国移动在
庞小明
2018/03/07
1.6K0
Android平台相机接口的应用
相关推荐
Android WebView 上传文件支持全解析
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验