文档章节

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

LeoXu
 LeoXu
发布于 2017/04/23 16:48
字数 1298
阅读 124
收藏 1

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)...

代码如下:

	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 方法实现

	/**
	 * 弹出对话框,提示拍照或者选择照片文件
	 */
	@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 方法的作用是在拍照以前判断有没有存储。

	/**
	 * 检查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 方法

	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 方法

	/**
	 * 选择文件
	 */
	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 方法

	@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 方法的作用是将拍照生成的图片(不是缩略图)添加到相册,保证后续还能从系统中索取到。

	/**
	 * 解决拍照后在相册中找不到的问题 
	 */
	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);
	}

 

 

 

 

© 著作权归作者所有

共有 人打赏支持
LeoXu

LeoXu

粉丝 118
博文 85
码字总数 85349
作品 0
长沙
程序员
私信 提问
android WebView总结

浏览器控件是每个开发环境都具备的,这为马甲神功提供了用武之地,windows的有webbrowser,android和ios都有webview。只是其引擎不同,相对于微软的webbrowser,android及ios的webview的引擎...

迷途d书童
2012/03/28
8.5K
1
WebView使用总结(应用函数与JS函数互相调用)

1.当只用WebView的时候,最先注意的当然是在配置文件中添加访问因特网的权限; 2.如果访问的页面中有Javascript,必须设置支持Javascript: Java代码 3.如果希望点击链接由自己处理而不是新开And...

带梦想一7飞
2013/01/04
0
0
android webview如何实现多(批量)文件上传

最近研究webview发现通过html的input[type='file']标签打开android的文件选择器后不能批量选择文件,而且input标签加上了multiple属性也不行,求大神们帮忙。...

zhuran0822
2014/06/24
2.1K
1
关于WebView是否会引起android系统触摸失效?

webView = (WebView)mainView.findViewById(R.id.myWeb); helpApiBtn = (ImageButton)mainView.findViewById(R.id.helpApiBtn); helpApiBtn.setOnClickListener(new OnClickListener() { @Ov......

会打杂的前端攻城狮
2013/05/23
1K
4
android调用js上传文件异常问题

需求: 在android客户端内用webview加载服务url, 类似一个壳,实现上传文件功能。 web代码片段 点击 input触发文件选择器。 android代码片段: 显示文件选择器 将需要上传的文件交给 ValueC...

lufengdie
2015/06/04
1K
1

没有更多内容

加载失败,请刷新页面

加载更多

Spring学习记录

Java类定义配置 @Configuration //标记为配置类@ComponentScan //标记为扫描当前包及子包所有标记为@Component的类@ComponentScan(basePackageClasses = {接口.class,...}) //标记为扫描当...

CHONGCHEN
32分钟前
1
0
如何开发一款以太坊(安卓)钱包系列2 - 导入账号及账号管理

这是如何开发一款以太坊(安卓)钱包系列第2篇,如何导入账号。有时用户可能已经有一个账号,这篇文章接来介绍下,如何实现导入用户已经存在的账号。 导入账号预备知识 从用户需求上来讲,导...

Tiny熊
今天
3
0
intellJ IDEA搭建java+selenium自动化环境(maven,selenium,testng)

1.安装jdk1.8; 2.安装intellJ; 3.安装maven; 3.1 如果是单前用户,配置用户环境变量即可,如果是多用户,则需配置系统环境变量,变量名为MAVEN_HOME,赋值D:\Application\maven,往path中...

不最醉不龟归
今天
4
0
聊聊ShenandoahGC的Brooks Pointers

序 本文主要研究一下ShenandoahGC的Brooks Pointers Shenandoah Shenandoah面向low-pause-time的垃圾收集器,它的GC cycle主要有 Snapshot-at-the-beginning concurrent mark包括Init Mark(P......

go4it
昨天
4
0
Makefile通用编写规则

#简单实用的Makefile模板: objs := a.o b.o test:$(objs) gcc -o test $^ # .a.o.d .b.o.d dep_files := $(foreach f,$(objs),.$(f).d) dep_files := $(wildcard $(dep_files)) ifneq ($(d......

shzwork
昨天
3
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部