Webview 支持文件上传

时间:2022-09-14 15:25:55
默认情况下情况下,在一个带有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());
    }

}


附件列表

Webview 支持文件上传的更多相关文章

  1. RPC基于http协议通过netty支持文件上传下载

    本人在中间件研发组(主要开发RPC),近期遇到一个需求:RPC基于http协议通过netty支持文件上传下载 经过一系列的资料查找学习,终于实现了该功能 通过netty实现文件上传下载,主要在编解码时 ...

  2. Openresty &plus; nginx-upload-module支持文件上传

    0. 说明 这种方式其实复杂,麻烦!建议通过这个方式搭建Openresty文件上传和下载服务器:http://www.cnblogs.com/lujiango/p/9056680.html 1. 包下 ...

  3. java nio 写一个完整的http服务器 支持文件上传 chunk传输 gzip 压缩 使用过程 和servlet差不多

    java nio 写一个完整的http服务器  支持文件上传   chunk传输    gzip 压缩      也仿照着 netty处理了NIO的空轮询BUG        本项目并不复杂 代码不多 ...

  4. springmvc学习笔记--支持文件上传和阿里云OSS API简介

    前言: Web开发中图片上传的功能很常见, 本篇博客来讲述下springmvc如何实现图片上传的功能. 主要讲述依赖包引入, 配置项, 本地存储和云存储方案(阿里云的OSS服务). 铺垫: 文件上传是 ...

  5. 让nginx支持文件上传的几种模式

    文件上传的几种不同语言和不同方法的总结. 第一种模式 : PHP 语言来处理 这个模式比较简单, 用的人也是最多的, 类似的还有用 .net 来实现, jsp来实现, 都是处理表单.只有语言的差别, ...

  6. 让UpdatePanel支持文件上传(2):服务器端组件 &period;

    我们现在来关注服务器端的组件.目前的主要问题是,我们如何让页面(事实上是ScriptManager控件)认为它接收到的是一个异步的回送?ScriptManager控件会在HTTP请求的Header中查 ...

  7. 让UpdatePanel支持文件上传(1):开始 &period;

    UpdatePanel从一开始就无法支持AJAX的文件上传方式.Eilon Lipton写了一篇文章解释了这个问题的原因.文章中提供了两个绕开此问题的方法: 将“上传”按钮设为一个传统的PostBac ...

  8. 配置servlet支持文件上传

    Servlet3.0为Servlet添加了multipart配置选项,并为HttpServletRequest添加了getPart和getParts方法获取上传文件.为了使Servlet支付文件上传需 ...

  9. 让SAE下的wordpress支持文件上传

    非PHP程序员照着源码打的小布丁… SAE是不允许本地磁盘读写的,所以使用wordpress撰写文章的时候, 上传控件默认是用不了的,幸好SAE提供了storage服务来存储文件,那就可以修改word ...

随机推荐

  1. 1187&colon; &lbrack;HNOI2007&rsqb;神奇游乐园 - BZOJ

    Description 经历了一段艰辛的旅程后,主人公小P乘坐飞艇返回.在返回的途中,小P发现在漫无边际的沙漠中,有一块狭长的绿地特别显眼.往下仔细一看,才发现这是一个游乐场,专为旅途中疲惫的人设计. ...

  2. C&num; 泛型转换 将object&lbrack;&rsqb; 数组转换为泛型列表

    public List<TaskSoftInfo> ReadSoftDownTaskList()        {                string[] Tid = (strin ...

  3. poj3903 Stock Exchange(最长上升子序列)

    转载请注明出处:http://blog.csdn.net/u012860063 题目链接:id=3903">http://poj.org/problem?id=3903 Descrip ...

  4. CodeForces 625D Finals in arithmetic

    神奇的构造题,我的思路比较奇葩.搞了好久,看到WA on 91我绝望了,然后自己造数据,找到了错误,总算是AC了,现在是凌晨0:24分,看到AC之后,感动China! 我写的代码无比的长.....应该 ...

  5. POJ2689-Prime Distance-区间筛素数

    最近改自己的错误代码改到要上天,心累. 这是迄今为止写的最心累的博客. Prime Distance Time Limit: 1000MS   Memory Limit: 65536K Total S ...

  6. 【收藏】ETH以太坊各个环境的公共的RPC服务!!!

    Choose a Network Use one of these endpoints as your Ethereum client provider or IPFS endpoint. NOTE: ...

  7. Flink的广播变量

    Flink支持广播变量,就是将数据广播到具体的taskmanager上,数据存储在内存中,这样可以减缓大量的shuffle操作: 比如在数据join阶段,不可避免的就是大量的shuffle操作,我们可 ...

  8. jmeter读取csv文件

    操作步骤: 1.读取csv文件 2.编辑httpSampler

  9. &lbrack;Pytorch&rsqb;Pytorch 细节记录(转)

    文章来源 https://www.cnblogs.com/king-lps/p/8570021.html 1. PyTorch进行训练和测试时指定实例化的model模式为:train/eval eg: ...

  10. qq iOS环境配置及调用

    1.下载官方iOS sdk:地址:相关文档 2. 将iOS SDK中的TencentOpenAPI.framework和TencentOpenApi_IOS_Bundle.bundle文件拷贝到应用开 ...