来源: Android 使用腾讯X5 Webview浏览器拍照或从相册上传图片 – CSDN博客
最近在项目开发中,需要使用WebView上传文件。默认情况下情况下,使用Android的WebView是不能够支持上传文件的。
经过查找资料,得知需要重新WebChromeClient,根据选择到的文件Uri,传给页面去上传就可以了。
自定义WebChromeClient
先在WebViewActivity里面自定义MyWebChromeClient,代码如下:
public class MyWebChromeClient extends WebChromeClient {
// For Android 3.0+
public void openFileChooser(ValueCallback<Uri> uploadMsg) {
CLog.i("UPFILE", "in openFile Uri Callback");
if (mUploadMessage != null) {
mUploadMessage.onReceiveValue(null);
}
mUploadMessage = uploadMsg;
Intent i = new Intent(Intent.ACTION_GET_CONTENT);
i.addCategory(Intent.CATEGORY_OPENABLE);
i.setType("*/*");
startActivityForResult(Intent.createChooser(i, "File Chooser"), FILECHOOSER_RESULTCODE);
}
// For Android 3.0+
public void openFileChooser(ValueCallback uploadMsg, String acceptType) {
CLog.i("UPFILE", "in openFile Uri Callback has accept Type" + acceptType);
if (mUploadMessage != null) {
mUploadMessage.onReceiveValue(null);
}
mUploadMessage = uploadMsg;
Intent i = new Intent(Intent.ACTION_GET_CONTENT);
i.addCategory(Intent.CATEGORY_OPENABLE);
String type = TextUtils.isEmpty(acceptType) ? "*/*" : acceptType;
i.setType(type);
startActivityForResult(Intent.createChooser(i, "File Chooser"),
FILECHOOSER_RESULTCODE);
}
// For Android 4.1
public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture) {
CLog.i("UPFILE", "in openFile Uri Callback has accept Type" + acceptType + "has capture" + capture);
if (mUploadMessage != null) {
mUploadMessage.onReceiveValue(null);
}
mUploadMessage = uploadMsg;
Intent i = new Intent(Intent.ACTION_GET_CONTENT);
i.addCategory(Intent.CATEGORY_OPENABLE);
String type = TextUtils.isEmpty(acceptType) ? "*/*" : acceptType;
i.setType(type);
startActivityForResult(Intent.createChooser(i, "File Chooser"), FILECHOOSER_RESULTCODE);
}
//Android 5.0+
@Override
@SuppressLint("NewApi")
public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {
if (mUploadMessage != null) {
mUploadMessage.onReceiveValue(null);
}
CLog.i("UPFILE", "file chooser params:" + fileChooserParams.toString());
mUploadMessage = filePathCallback;
Intent i = new Intent(Intent.ACTION_GET_CONTENT);
i.addCategory(Intent.CATEGORY_OPENABLE);
if (fileChooserParams != null && fileChooserParams.getAcceptTypes() != null
&& fileChooserParams.getAcceptTypes().length > 0) {
i.setType(fileChooserParams.getAcceptTypes()[0]);
} else {
i.setType("*/*");
}
startActivityForResult(Intent.createChooser(i, "File Chooser"), FILECHOOSER_RESULTCODE);
return true;
}
}
上面openFileChooser是系统未暴露的接口,因此不需要加Override的注解,同时不同版本有不同的参数,其中的参数,第一个ValueCallback用于我们在选择完文件后,接收文件回调到网页内处理,acceptType为接受的文件mime type。在Android 5.0之后,系统提供了onShowFileChooser来让我们实现选择文件的方法,仍然有ValueCallback,在FileChooserParams参数中,同样包括acceptType。我们可以根据acceptType,来打开系统的或者我们自己创建文件选择器。当然如果需要打开相机拍照,也可以自己去使用打开相机拍照的Intent去打开即可。
处理选择的文件
因为我们前面是使用startActivityForResult来打开的选择页面,我们会在onActivityResult中接收到选择的结果。代码如下:
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == FILECHOOSER_RESULTCODE) {
Uri result = data == null || resultCode != RESULT_OK ? null : data.getData();
if (result == null) {
mUploadMessage.onReceiveValue(null);
mUploadMessage = null;
return;
}
String path = FileUtils.getPath(this, result);
if (TextUtils.isEmpty(path)) {
mUploadMessage.onReceiveValue(null);
mUploadMessage = null;
return;
}
Uri uri = Uri.fromFile(new File(path));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
mUploadMessage.onReceiveValue(new Uri[]{uri});
} else {
mUploadMessage.onReceiveValue(uri);
}
mUploadMessage = null;
}
}
注意事项:
- 由于不同版本的差别,Android 5.0以下的版本,ValueCallback 的onReceiveValue接收的参数类型是Uri, 5.0及以上版本接收的是Uri数组,在传值的时候需要注意。
- 选择文件会使用系统提供的组件或者其他支持的app,返回的uri有的直接是文件的url,有的是contentprovider的uri,因此我们需要统一处理一下,转成文件的uri,可参考以下代码(获取文件的路径)。
- 即使获取的结果为null,也要传给webview,即直接调用mUploadMessage.onReceiveValue(null),否则网页会阻塞。
- 在打release包的时候,因为我们会混淆,要特别设置不要混淆WebChromeClient子类里面的openFileChooser方法,由于不是继承的方法,所以默认会被混淆,然后就无法选择文件了。
FileUtils工具类如下:
public class FileUtils {
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is ExternalStorageProvider.
*/
public static boolean isExternalStorageDocument(Uri uri) {
return "com.android.externalstorage.documents".equals(uri.getAuthority());
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is DownloadsProvider.
*/
public static boolean isDownloadsDocument(Uri uri) {
return "com.android.providers.downloads.documents".equals(uri.getAuthority());
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is MediaProvider.
*/
public static boolean isMediaDocument(Uri uri) {
return "com.android.providers.media.documents".equals(uri.getAuthority());
}
/**
* 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;
final String column = "_data";
final String[] projection = {
column
};
try {
cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs,
null);
if (cursor != null && cursor.moveToFirst()) {
final int column_index = cursor.getColumnIndexOrThrow(column);
return cursor.getString(column_index);
}
} finally {
if (cursor != null)
cursor.close();
}
return null;
}
/**
* Get a file path from a Uri. This will get the the path for Storage Access
* Framework Documents, as well as the _data field for the MediaStore and
* other file-based ContentProviders.
*
* @param context The context.
* @param uri The Uri to query.
* @author paulburke
*/
@SuppressLint("NewApi")
public static String getPath(final Context context, final Uri uri) {
final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
// DocumentProvider
if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
// ExternalStorageProvider
if (isExternalStorageDocument(uri)) {
final String docId = DocumentsContract.getDocumentId(uri);
final String[] split = docId.split(":");
final String type = split[0];
if ("primary".equalsIgnoreCase(type)) {
return Environment.getExternalStorageDirectory() + "/" + split[1];
}
// TODO handle non-primary volumes
}
// DownloadsProvider
else if (isDownloadsDocument(uri)) {
final String id = DocumentsContract.getDocumentId(uri);
final Uri contentUri = ContentUris.withAppendedId(
Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
return getDataColumn(context, contentUri, null, null);
}
// MediaProvider
else if (isMediaDocument(uri)) {
final String docId = DocumentsContract.getDocumentId(uri);
final String[] split = docId.split(":");
final 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;
}
final String selection = "_id=?";
final String[] selectionArgs = new String[] {
split[1]
};
return getDataColumn(context, contentUri, selection, selectionArgs);
}
}
// MediaStore (and general)
else if ("content".equalsIgnoreCase(uri.getScheme())) {
return getDataColumn(context, uri, null, null);
}
// File
else if ("file".equalsIgnoreCase(uri.getScheme())) {
return uri.getPath();
}
return null;
}
}
看了上面的代码,你是不是感觉有点复杂呢?下面我们将介绍怎么通过使用腾讯X5 Webview浏览器实现拍照或从相册上传图片功能。
使用腾讯X5 Webview浏览器
TBS腾讯浏览器服务官网:http://x5.tencent.com
jar包下载:http://x5.tencent.com/doc?id=1004
集成教程:
http://www.jianshu.com/p/8a7224ff371a
http://blog.csdn.net/qq_17387361/article/details/52396338
http://www.jianshu.com/p/e4009688119b
环境调好后,我们就可以愉快的开始调试了。
public class MyWebChromeClient extends WebChromeClient {
@Override
public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> valueCallback, FileChooserParams fileChooserParams) {
TLog.error("onShowFileChooser");
return super.onShowFileChooser(webView, valueCallback, fileChooserParams);
}
// Android > 4.1.1 调用这个方法
public void openFileChooser(ValueCallback<Uri> uploadMsg,
String acceptType, String capture) {
mUploadMessage = uploadMsg;
choosePicture();
}
// 3.0 + 调用这个方法
public void openFileChooser(ValueCallback<Uri> uploadMsg,
String acceptType) {
mUploadMessage = uploadMsg;
choosePicture();
}
// Android < 3.0 调用这个方法
public void openFileChooser(ValueCallback<Uri> uploadMsg) {
mUploadMessage = uploadMsg;
choosePicture();
}
}
这里选择图片使用了三方图片选择组件:PhotoPicker,项目地址:https://github.com/donglua/PhotoPicker
其中choosePicture方法如下,
private void choosePicture() {
PhotoPicker.builder()
.setPhotoCount(1)
.setShowCamera(true)
.setShowGif(true)
.setPreviewEnabled(false)
.start(TBSWebActivity.this, PhotoPicker.REQUEST_CODE);
}
在onActivityResult中接收到选择的结果,处理如下:
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
if (null == mUploadMessage) {
return;
}
if (resultCode == RESULT_OK && requestCode == PhotoPicker.REQUEST_CODE) {
ArrayList<String> photos = intent.getStringArrayListExtra(PhotoPicker.KEY_SELECTED_PHOTOS);
Uri result = Uri.parse(photos.get(0));
mUploadMessage.onReceiveValue(result);
mUploadMessage = null;
} else {
mUploadMessage.onReceiveValue(null);
}
}
这里再强调下,即使获取的结果为null(比如按back键取消了),也要传给webview,即直接调用mUploadMessage.onReceiveValue(null),否则网页会阻塞。
H5前端
最后简单再说一下H5前端调用。
<div class="btn1">上传照片
<input type="file" accept="image/*" id="uploadImage" capture="camera" onchange="selectFileImage(this);">
</div>
上传相关的js代码如下:
var arr=new Array();
function selectFileImage(fileObj) {
var file = fileObj.files['0'];
//图片方向角 added by lzk
var Orientation = null;
if (file) {
console.log("正在上传,请稍后...");
var rFilter = /^(image\/jpeg|image\/png)$/i; // 检查图片格式
if (!rFilter.test(file.type)) {
//showMyTips("请选择jpeg、png格式的图片", false);
return;
}
// var URL = URL || webkitURL;
//获取照片方向角属性,用户旋转控制
EXIF.getData(file, function() {
// alert(EXIF.pretty(this));
EXIF.getAllTags(this);
//alert(EXIF.getTag(this, 'Orientation'));
Orientation = EXIF.getTag(this, 'Orientation');
//return;
});
var oReader = new FileReader();
oReader.onload = function(e) {
//var blob = URL.createObjectURL(file);
//_compress(blob, file, basePath);
var image = new Image();
image.src = e.target.result;
image.onload = function() {
var expectWidth = this.naturalWidth;
var expectHeight = this.naturalHeight;
if (this.naturalWidth > this.naturalHeight && this.naturalWidth > 640) {
expectWidth = 640;
expectHeight = expectWidth * this.naturalHeight / this.naturalWidth;
} else if (this.naturalHeight > this.naturalWidth && this.naturalHeight > 640) {
expectHeight = 640;
expectWidth = expectHeight * this.naturalWidth / this.naturalHeight;
}
//alert(expectWidth+','+expectHeight);
var canvas = document.createElement("canvas");
var ctx = canvas.getContext("2d");
canvas.width = expectWidth;
canvas.height = expectHeight;
ctx.drawImage(this, 0, 0, expectWidth, expectHeight);
//alert(canvas.width+','+canvas.height);
var base64 = null;
var mpImg = new MegaPixImage(image);
mpImg.render(canvas, {
maxWidth: 640,
maxHeight: 640,
quality: 0.8,
orientation: Orientation
});
base64 = canvas.toDataURL("image/jpeg", 0.8);
//alert(base64);
//var img =
//修复ios
if (navigator.userAgent.match(/iphone/i)) {
console.log('iphone');
//alert(expectWidth + ',' + expectHeight);
//如果方向角不为1,都需要进行旋转 added by lzk
/*if(Orientation != "" && Orientation != 1){
alert('旋转处理');
switch(Orientation){
case 6://需要顺时针(向左)90度旋转
alert('需要顺时针(向左)90度旋转');
rotateImg(this,'left',canvas);
break;
case 8://需要逆时针(向右)90度旋转
alert('需要顺时针(向右)90度旋转');
rotateImg(this,'right',canvas);
break;
case 3://需要180度旋转
alert('需要180度旋转');
rotateImg(this,'right',canvas);//转两次
rotateImg(this,'right',canvas);
break;
}
}*/
/*var mpImg = new MegaPixImage(image);
mpImg.render(canvas, {
maxWidth: 800,
maxHeight: 1200,
quality: 0.8,
orientation: Orientation
});
base64 = canvas.toDataURL("image/jpeg", 0.8);*/
}else if (navigator.userAgent.match(/Android/i)) {// 修复android
/*var encoder = new JPEGEncoder();
base64 = encoder.encode(ctx.getImageData(0, 0, expectWidth, expectHeight), 80);*/
}else{
//alert(Orientation);
/*if(Orientation != "" && Orientation != 1){
//alert('旋转处理');
switch(Orientation){
case 6://需要顺时针(向左)90度旋转
alert('需要顺时针(向左)90度旋转');
rotateImg(this,'left',canvas);
break;
case 8://需要逆时针(向右)90度旋转
alert('需要顺时针(向右)90度旋转');
rotateImg(this,'right',canvas);
break;
case 3://需要180度旋转
alert('需要180度旋转');
rotateImg(this,'right',canvas);//转两次
rotateImg(this,'right',canvas);
break;
}
}*/
/*var mpImg = new MegaPixImage(image);
mpImg.render(canvas, {
maxWidth: 800,
maxHeight: 1200,
quality: 0.8,
orientation: Orientation
});
base64 = canvas.toDataURL("image/jpeg", 0.8);*/
}
var imgName = uploadImage(base64);
//alert("img===="+imgName);
$(".overlayer").css("display","none");
if($(fileObj).attr("flag")=="myImage"){
arr[0]=base64;
$("#myImage").attr("src", base64);
$("#myImage").css("opacity","1");
ajax_pram.img0 = imgName;
}else{
arr[1]=base64;
$("#myImage1").attr("src", base64);
$("#myImage1").css("opacity","1");
ajax_pram.img1 = imgName;
}
input_validate();
};
};
oReader.readAsDataURL(file);
}
}
//对图片旋转处理 added by lzk
function rotateImg(img, direction,canvas) {
//alert(img);
//最小与最大旋转方向,图片旋转4次后回到原方向
var min_step = 0;
var max_step = 3;
//var img = document.getElementById(pid);
if (img == null)return;
//img的高度和宽度不能在img元素隐藏后获取,否则会出错
/*var height = img.height;
var width = img.width; */
var height = canvas.height;
var width = canvas.width;
// alert(width+','+height);
//var step = img.getAttribute('step');
var step = 2;
if (step == null) {
step = min_step;
}
if (direction == 'right') {
step++;
//旋转到原位置,即超过最大值
step > max_step && (step = min_step);
} else {
step--;
step < min_step && (step = max_step);
}
//img.setAttribute('step', step);
/*var canvas = document.getElementById('pic_' + pid);
if (canvas == null) {
img.style.display = 'none';
canvas = document.createElement('canvas');
canvas.setAttribute('id', 'pic_' + pid);
img.parentNode.appendChild(canvas);
} */
//旋转角度以弧度值为参数
var degree = step * 90 * Math.PI / 180;
var ctx = canvas.getContext('2d');
switch (step) {
case 0:
canvas.width = width;
canvas.height = height;
ctx.drawImage(img, 0, 0);
break;
case 1:
canvas.width = height;
canvas.height = width;
ctx.rotate(degree);
ctx.drawImage(img, 0, -height);
break;
case 2:
canvas.width = width;
canvas.height = height;
ctx.rotate(degree);
ctx.drawImage(img, -width, -height);
break;
case 3:
canvas.width = height;
canvas.height = width;
ctx.rotate(degree);
ctx.drawImage(img, -width, 0);
break;
}
}
/** 记录上传数据 */
function uploadImage(imageData) {
if (imageData == undefined) {
alert('没有要上传的图片');
return false;
}
var result;
$.ajax({
type: "post",
url: 'http://upload.domain.cn/index.php?r=v1/certificates/saveimg',
data: {'baseStr':imageData},
async:false,
success: function(data) {
result = data
}
});
return result;
}
如果使用过程中有问题,请留言反馈。csdn代码排版始终不舒服,请大家包涵!