Android 使用腾讯X5 Webview浏览器拍照或从相册上传图片

来源:互联网 发布:万能数据大师破解版 编辑:程序博客网 时间:2024/04/28 17:50

最近在项目开发中,需要使用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中接收到选择的结果。代码如下:

@Overrideprotected 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;    }}

注意事项:

  1. 由于不同版本的差别,Android 5.0以下的版本,ValueCallback 的onReceiveValue接收的参数类型是Uri, 5.0及以上版本接收的是Uri数组,在传值的时候需要注意。
  2. 选择文件会使用系统提供的组件或者其他支持的app,返回的uri有的直接是文件的url,有的是contentprovider的uri,因此我们需要统一处理一下,转成文件的uri,可参考以下代码(获取文件的路径)。
  3. 即使获取的结果为null,也要传给webview,即直接调用mUploadMessage.onReceiveValue(null),否则网页会阻塞。
  4. 在打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 lzkfunction 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代码排版始终不舒服,请大家包涵!

6 0
原创粉丝点击