Android WebView 上传文件支持全解析
默认情况下情况下,使用android的webview是不能够支持上传文件的。而这个,也是在我们的前端工程师告知之后才了解的。因为android的每个版本webview的实现有差异,因此需要对不同版本去适配。花了一点时间,参考别人的代码,这个问题已经解决,这里把我踩过的坑分享出来。
主要思路是重写webchromeclient,然后在webviewactivity中接收选择到的文件uri,传给页面去上传就可以了。
创建一个webviewactivity的内部类
public class xhswebchromeclient 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中接收到选择的结果。show code:
@override protected void onactivityresult(int requestcode, int resultcode, intent data) { super.onactivityresult(requestcode, resultcode, data); if (requestcode == filechooser_resultcode) { if (null == muploadmessage) return; uri result = data == null || resultcode != result_ok ? null : data.getdata(); if (result == null) { muploadmessage.onreceivevalue(null); muploadmessage = null; return; } clog.i("upfile", "onactivityresult" + result.tostring()); string path = fileutils.getpath(this, result); if (textutils.isempty(path)) { muploadmessage.onreceivevalue(null); muploadmessage = null; return; } uri uri = uri.fromfile(new file(path)); clog.i("upfile", "onactivityresult after parser uri:" + uri.tostring()); if (build.version.sdk_int >= build.version_codes.lollipop) { muploadmessage.onreceivevalue(new uri[]{uri}); } else { muploadmessage.onreceivevalue(uri); } muploadmessage = null; } }
以上代码主要就是调用valuecallback的onreceivevalue方法,将结果传回web。
注意,其他要说的,重要
由于不同版本的差别,android 5.0以下的版本,valuecallback 的onreceivevalue接收的参数类型是uri, 5.0及以上版本接收的是uri数组,在传值的时候需要注意。
选择文件会使用系统提供的组件或者其他支持的app,返回的uri有的直接是文件的url,有的是contentprovider的uri,因此我们需要统一处理一下,转成文件的uri,可参考以下代码(获取文件的路径)。
调用getpath可以将uri转成真实文件的path,然后可以自己生成文件的uri
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; } }
再有,即使获取的结果为null,也要传给web,即直接调用muploadmessage.onreceivevalue(null),否则网页会阻塞。
最后,在打release包的时候,因为我们会混淆,要特别设置不要混淆webchromeclient子类里面的openfilechooser方法,由于不是继承的方法,所以默认会被混淆,然后就无法选择文件了。
就这样吧。
原文地址:
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
下一篇: .net验证码的刷新或局部刷新的方法实例
推荐阅读