默认情况下情况下,在一个带有input tpye=file标签的Html页面,使用Android的WebView是不能够支持上传文件的(在iOS和微信上完全正常工作)。而这个,也是在我们的前端工程师告知之后才了解的。因为Android的每个版本WebView的实现有差异,因此需要对不同版本去适配。
主要思路是重写WebChromeClient,然后在Activity中接收选择到的文件Uri,传给页面去上传就可以了。
由于不同版本的差别,Android 5.0以下的版本,ValueCallback 的onReceiveValue接收的参数类型是Uri, 5.0及以上版本接收的是Uri数组,在传值的时候需要注意。
选择文件会使用系统提供的组件或者其他支持的app,返回的uri有的直接是文件的url,有的是contentprovider的uri,因此我们需要统一处理一下,转成文件的uri。
调用getPath可以将Uri转成真实文件的Path,然后可以自己生成文件的Uri
WebView设置WebChromeClient
重写WebChromeClient中关于文件选择的方法,onShowFileChooser和openFileChooser。(项目中只需要选择图片,所以加上了图片过滤。)
private WebChromeClient mWebChromeClient = new WebChromeClient() {
// android 5.0 这里需要使用android5.0 sdk
public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, WebChromeClient.FileChooserParams fileChooserParams) {
if (mFilePathCallback != null) mFilePathCallback.onReceiveValue(null);
mFilePathCallback = filePathCallback;
/**
Open Declaration String android.provider.MediaStore.ACTION_IMAGE_CAPTURE = "android.media.action.IMAGE_CAPTURE"
Standard Intent action that can be sent to have the camera application capture an image and return it.
The caller may pass an extra EXTRA_OUTPUT to control where this image will be written.
If the EXTRA_OUTPUT is not present, then a small sized image is returned as a Bitmap object in the extra field.
This is useful for applications that only need a small image.
If the EXTRA_OUTPUT is present, then the full-sized image will be written to the Uri value of EXTRA_OUTPUT.
As of android.os.Build.VERSION_CODES.LOLLIPOP, this uri can also be supplied through android.content.Intent.setClipData(ClipData).
If using this approach, you still must supply the uri through the EXTRA_OUTPUT field for compatibility with old applications.
If you don't set a ClipData, it will be copied there for you when calling Context.startActivity(Intent).
See Also:EXTRA_OUTPUT
标准意图,被发送到相机应用程序捕获一个图像,并返回它。通过一个额外的extra_output控制这个图像将被写入。如果extra_output是不存在的,
那么一个小尺寸的图像作为位图对象返回。如果extra_output是存在的,那么全尺寸的图像将被写入extra_output URI值。
*/
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
File photoFile = null;// Create the File where the photo should go
try {
//设置MediaStore.EXTRA_OUTPUT路径,相机拍照写入的全路径
photoFile = createImageFile();
takePictureIntent.putExtra("PhotoPath", mCameraPhotoPath);
} catch (Exception ex) {
// Error occurred while creating the File
Log.e("WebViewSetting", "Unable to create Image File", ex);
}
// Continue only if the File was successfully created
if (photoFile != null) {
mCameraPhotoPath = "file:" + photoFile.getAbsolutePath();
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(photoFile));
System.out.println(mCameraPhotoPath);
} else {
takePictureIntent = null;
}
}
Intent contentSelectionIntent = new Intent(Intent.ACTION_GET_CONTENT);
contentSelectionIntent.addCategory(Intent.CATEGORY_OPENABLE);
contentSelectionIntent.setType("image/*");
Intent[] intentArray;
if (takePictureIntent != null) {
intentArray = new Intent[] { takePictureIntent };
System.out.println(takePictureIntent);
} else {
intentArray = new Intent[0];
}
Intent chooserIntent = new Intent(Intent.ACTION_CHOOSER);
chooserIntent.putExtra(Intent.EXTRA_INTENT, contentSelectionIntent);
chooserIntent.putExtra(Intent.EXTRA_TITLE, "Image Chooser");
chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, intentArray);
startActivityForResult(chooserIntent, INPUT_FILE_REQUEST_CODE);
return true;
}
//The undocumented magic method override
//Eclipse will swear at you if you try to put @Override here
// For Android 3.0+
@SuppressWarnings("unused")
public void openFileChooser(ValueCallback<Uri> uploadMsg) {
Log.d("bqt", "openFileChooser1");
mUploadMessage = uploadMsg;
Intent i = new Intent(Intent.ACTION_GET_CONTENT);
i.addCategory(Intent.CATEGORY_OPENABLE);
i.setType("image/*");
WebViewActivity.this.startActivityForResult(Intent.createChooser(i, "Image Chooser"), FILECHOOSER_RESULTCODE);
}
// For Android 3.0+
@SuppressWarnings("unused")
public void openFileChooser(ValueCallback uploadMsg, String acceptType) {
Log.d("bqt", "openFileChooser2");
mUploadMessage = uploadMsg;
Intent i = new Intent(Intent.ACTION_GET_CONTENT);
i.addCategory(Intent.CATEGORY_OPENABLE);
i.setType("image/*");
WebViewActivity.this.startActivityForResult(Intent.createChooser(i, "Image Chooser"), FILECHOOSER_RESULTCODE);
}
//For Android 4.1
@SuppressWarnings("unused")
public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture) {
Log.d("bqt", "openFileChooser3");
mUploadMessage = uploadMsg;
Intent i = new Intent(Intent.ACTION_GET_CONTENT);
i.addCategory(Intent.CATEGORY_OPENABLE);
i.setType("image/*");
WebViewActivity.this.startActivityForResult(Intent.createChooser(i, "Image Chooser"), WebViewActivity.FILECHOOSER_RESULTCODE);
}
};
选择结果的回调
在onActivityResult中获取对应的选取文件的返回结果
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == FILECHOOSER_RESULTCODE) {
if (null == mUploadMessage) return;
Uri result = data == null || resultCode != RESULT_OK ? null : data.getData();
if (result != null) {
String imagePath = ImageFilePath.getPath(this, result);
if (!TextUtils.isEmpty(imagePath)) result = Uri.parse("file:///" + imagePath);
}
mUploadMessage.onReceiveValue(result);
mUploadMessage = null;
} else if (requestCode == INPUT_FILE_REQUEST_CODE && mFilePathCallback != null) {
// 5.0的回调
Uri[] results = null;
if (resultCode == Activity.RESULT_OK) {
if (data == null) {
// If there is not data, then we may have taken a photo
if (mCameraPhotoPath != null) results = new Uri[] { Uri.parse(mCameraPhotoPath) };
} else {
String dataString = data.getDataString();
if (dataString != null) results = new Uri[] { Uri.parse(dataString) };
}
}
mFilePathCallback.onReceiveValue(results);
mFilePathCallback = null;
} else {
super.onActivityResult(requestCode, resultCode, data);
}
}
文件路径的获取
返回文件的解析,因为html页面需要的是文件,所以客户端需要返回的是对应文件的路径。这样,就会存在一个问题,在Android 4.4上,通过文件选择返回的结果都是对应以content开头格式的对应的路径。这就得需要咱们来进行判断,最终都需要转回成以file开头对应的格式文件。下面,我封装成了一个ImageFilePath的类,通过调用getPath方法来获取最终的结果。这个类的方法如下:
public class ImageFilePath {
/**
* Method for return file path of Gallery image
*/
public static String getPath(final Context context, final Uri uri) {
// check here to KITKAT or new version
boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
// DocumentProvider
if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
// ExternalStorageProvider
if (isExternalStorageDocument(uri)) {
String docId = DocumentsContract.getDocumentId(uri);
String[] split = docId.split(":");
String type = split[0];
if ("primary".equalsIgnoreCase(type)) return Environment.getExternalStorageDirectory() + "/" + split[1];
}
// DownloadsProvider
else if (isDownloadsDocument(uri)) {
String id = DocumentsContract.getDocumentId(uri);
Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
return getDataColumn(context, contentUri, null, null);
}
// MediaProvider
else if (isMediaDocument(uri)) {
String docId = DocumentsContract.getDocumentId(uri);
String[] split = docId.split(":");
String type = split[0];
Uri contentUri = null;
if ("image".equals(type)) contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
else if ("video".equals(type)) contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
else if ("audio".equals(type)) contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
String selection = "_id=?";
String[] selectionArgs = new String[] { split[1] };
return getDataColumn(context, contentUri, selection, selectionArgs);
}
}
// MediaStore (and general)
else if ("content".equalsIgnoreCase(uri.getScheme())) {
// Return the remote address
if (isGooglePhotosUri(uri)) return uri.getLastPathSegment();
return getDataColumn(context, uri, null, null);
}
// File
else if ("file".equalsIgnoreCase(uri.getScheme())) return uri.getPath();
return null;
}
/**
* Get the value of the data column for this Uri. This is useful for MediaStore Uris, and other file-based ContentProviders.
* @param context The context.
* @param uri The Uri to query.
* @param selection (Optional) Filter used in the query.
* @param selectionArgs (Optional) Selection arguments used in the query.
* @return The value of the _data column, which is typically a file path.
*/
public static String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) {
Cursor cursor = null;
String column = "_data";
String[] projection = { column };
try {
cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null);
if (cursor != null && cursor.moveToFirst()) {
int index = cursor.getColumnIndexOrThrow(column);
return cursor.getString(index);
}
} finally {
if (cursor != null) cursor.close();
}
return null;
}
public static boolean isExternalStorageDocument(Uri uri) {
return "com.android.externalstorage.documents".equals(uri.getAuthority());
}
public static boolean isDownloadsDocument(Uri uri) {
return "com.android.providers.downloads.documents".equals(uri.getAuthority());
}
public static boolean isMediaDocument(Uri uri) {
return "com.android.providers.media.documents".equals(uri.getAuthority());
}
public static boolean isGooglePhotosUri(Uri uri) {
return "com.google.android.apps.photos.content".equals(uri.getAuthority());
}
}
附件列表
- RPC基于http协议通过netty支持文件上传下载
本人在中间件研发组(主要开发RPC),近期遇到一个需求:RPC基于http协议通过netty支持文件上传下载 经过一系列的资料查找学习,终于实现了该功能 通过netty实现文件上传下载,主要在编解码时 ...
- Openresty + nginx-upload-module支持文件上传
0. 说明 这种方式其实复杂,麻烦!建议通过这个方式搭建Openresty文件上传和下载服务器:http://www.cnblogs.com/lujiango/p/9056680.html 1. 包下 ...
- java nio 写一个完整的http服务器 支持文件上传 chunk传输 gzip 压缩 使用过程 和servlet差不多
java nio 写一个完整的http服务器 支持文件上传 chunk传输 gzip 压缩 也仿照着 netty处理了NIO的空轮询BUG 本项目并不复杂 代码不多 ...
- springmvc学习笔记--支持文件上传和阿里云OSS API简介
前言: Web开发中图片上传的功能很常见, 本篇博客来讲述下springmvc如何实现图片上传的功能. 主要讲述依赖包引入, 配置项, 本地存储和云存储方案(阿里云的OSS服务). 铺垫: 文件上传是 ...
- 让nginx支持文件上传的几种模式
文件上传的几种不同语言和不同方法的总结. 第一种模式 : PHP 语言来处理 这个模式比较简单, 用的人也是最多的, 类似的还有用 .net 来实现, jsp来实现, 都是处理表单.只有语言的差别, ...
- 让UpdatePanel支持文件上传(2):服务器端组件 .
我们现在来关注服务器端的组件.目前的主要问题是,我们如何让页面(事实上是ScriptManager控件)认为它接收到的是一个异步的回送?ScriptManager控件会在HTTP请求的Header中查 ...
- 让UpdatePanel支持文件上传(1):开始 .
UpdatePanel从一开始就无法支持AJAX的文件上传方式.Eilon Lipton写了一篇文章解释了这个问题的原因.文章中提供了两个绕开此问题的方法: 将“上传”按钮设为一个传统的PostBac ...
- 配置servlet支持文件上传
Servlet3.0为Servlet添加了multipart配置选项,并为HttpServletRequest添加了getPart和getParts方法获取上传文件.为了使Servlet支付文件上传需 ...
- 测试平台系列(92) 让http请求支持文件上传
大家好~我是米洛! 我正在从0到1打造一个开源的接口测试平台, 也在编写一套与之对应的教程,希望大家多多支持. 欢迎关注我的公众号米洛的测开日记,获取最新文章教程! 回顾 上一节呢,我们编写了oss的 ...
随机推荐
- HDU - 4777 离线树状数组
离线树状数组搞一搞. #include<bits/stdc++.h> #define LL long long #define fi first #define se second #de ...
- python沙盒逃逸
前言 最近遇到了很多python沙盒逃逸的题目(不知道是不是因为现在python搭的站多了--),实际使用时发现只会复制别人的payload是不够用的,于是自己来总结一波(顺带一提python沙盒逃逸 ...
- shell 从变量中切割字符串
1. 在shell变量中切割字符串 shell中截取字符串的方法有很多中,${expression}一共有9种使用方法.${parameter:-word}${parameter:=word}${pa ...
- dp 神奇的口袋
有一个神奇的口袋,总的容积是40,用这个口袋可以变出一 些物品,这些物品的总体积必须是40. John现在有n(1≤n ≤ 20)个想要得到的物品,每个物品 的体积分别是a1,a2--an.Joh ...
- SPOJ1557 GSS2
不知道第几次回顾了,每次回顾感觉都有新的收获 这题YYZ认为非常的简单,我们一起去%%%她吧(洛谷$id: 54050$) 题面 给出$n$个数,有$q$个询问,求最大子段和,注意相同的数只算一次 做 ...
- CodeForces - 1016C Vasya And The Mushrooms
题面在这里! 好久没有体会这种A题的快感了23333 一开始看错了,以为权值是从1开始的,不过这样不要紧,最后把算的答案减去两行数的和就是正确的答案了. 然后发现位于一个角上的时候,我们其实只有两种选 ...
- POJ1716 贪心
题目大意:在[0,10000]上给出n个区间,要求在区间选整数点,每个区间至少包含两个点,问至少要几个点.题目保证有解决方案. 题目分析: 我们做过在区间上至少包含一个点的题目.类似的方法,我们先排序 ...
- 【最小点覆盖】POJ3041-Asteroids
[题目大意] 在n*n的网格上有n个点,每次删除一行或者一列,问至少要删除几次才能删除完全部的这些店? [思路] 在国庆最后一天到来前,把二分图的三个基本情况[最小点覆盖][DAG图的最小路径覆盖]和 ...
- Android Studio 2.3更换默认的ConstraintLayout布局
首先打开你的Android Sudio安装目录,我的为D:\Program Files\Android\Android Studio,进入到以下文件夹\plugins\android\lib\temp ...
- Nginx服务器的启动控制
写在前面的话:读书破万卷,编码如有神--------------------------------------------------------------------主要内容包括: nginx服 ...