Android 使用腾讯X5 Webview浏览器拍照或从相册上传图片
2017-04-17 11:08
761 查看
版权声明:本文原创作者:一叶飘舟 作者博客地址:http://blog.csdn.NET/jdsjlzx
最近在项目开发中,需要使用WebView上传文件。默认情况下情况下,使用Android的WebView是不能够支持上传文件的。
经过查找资料,得知需要重新WebChromeClient,根据选择到的文件Uri,传给页面去上传就可以了。
上面openFileChooser是系统未暴露的接口,因此不需要加Override的注解,同时不同版本有不同的参数,其中的参数,第一个ValueCallback用于我们在选择完文件后,接收文件回调到网页内处理,acceptType为接受的文件mime type。在Android
5.0之后,系统提供了onShowFileChooser来让我们实现选择文件的方法,仍然有ValueCallback,在FileChooserParams参数中,同样包括acceptType。我们可以根据acceptType,来打开系统的或者我们自己创建文件选择器。当然如果需要打开相机拍照,也可以自己去使用打开相机拍照的Intent去打开即可。
注意事项:
由于不同版本的差别,Android 5.0以下的版本,ValueCallback 的onReceiveValue接收的参数类型是Uri, 5.0及以上版本接收的是Uri数组,在传值的时候需要注意。
选择文件会使用系统提供的组件或者其他支持的app,返回的uri有的直接是文件的url,有的是contentprovider的uri,因此我们需要统一处理一下,转成文件的uri,可参考以下代码(获取文件的路径)。
即使获取的结果为null,也要传给webview,即直接调用mUploadMessage.onReceiveValue(null),否则网页会阻塞。
在打release包的时候,因为我们会混淆,要特别设置不要混淆WebChromeClient子类里面的openFileChooser方法,由于不是继承的方法,所以默认会被混淆,然后就无法选择文件了。
FileUtils工具类如下:
看了上面的代码,你是不是感觉有点复杂呢?下面我们将介绍怎么通过使用腾讯X5 Webview浏览器实现拍照或从相册上传图片功能。
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
环境调好后,我们就可以愉快的开始调试了。
这里选择图片使用了三方图片选择组件:PhotoPicker,项目地址:https://github.com/donglua/PhotoPicker
其中choosePicture方法如下,
在onActivityResult中接收到选择的结果,处理如下:
这里再强调下,即使获取的结果为null(比如按back键取消了),也要传给webview,即直接调用mUploadMessage.onReceiveValue(null),否则网页会阻塞。
上传相关的js代码如下:
如果使用过程中有问题,请留言反馈。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; 4000 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.comjar包下载: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代码排版始终不舒服,请大家包涵
相关文章推荐
- Android 使用腾讯X5 Webview浏览器拍照或从相册上传图片
- android使用webview上传文件(支持相册和拍照)
- Android使用WebView从相册/拍照中添加图片
- android使用webview上传文件(支持相册和拍照)
- [置顶] 【Android开发技巧】 关于Webview拍照或从相册上传图片处理总结
- android使用webview上传文件(支持相册和拍照)
- Android:使用webview上传文件(支持相册和拍照) .
- Android使用WebView从相册/拍照中添加图片
- android使用webview上传文件(支持相册和拍照),支持最高6.0安卓系统(改进版)
- android使用webview上传文件(支持相册和拍照)
- [Android] WebView中拍照或从相册上传图片
- android使用webview上传文件(支持相册和拍照)
- android webview使用html5<input id="input" type="file"/> 上传相册、拍照照片
- android使用webview上传文件(支持相册和拍照)
- Android WebView 选择图片并上传(调用相机拍照/相册/选择文件)
- android使用webview上传文件(支持相册和拍照)
- Android使用WebView从相册/拍照中添加图片
- android使用webview上传文件(图片)
- 在Android浏览器中通过WebView调用相机拍照/选择文件 上传到服务器
- Android 开发 使用WebUploader解决安卓微信浏览器上传图片中遇到的bug