详解Android WebView的input上传照片的兼容问题
问题
前几天接到的一个需求,是关于第三方理财产品的h5上传照片问题。
对方说他们的新的需求,需要接入方配合上传资产照片的需求,测试之后发现我们这边的app端,ios端上传没有问题,而android端则点击没有任何反应。
对方h5调用的方式是通过<input type='file' accept='image/*'/>的方式调用,本来以为这个问题很简单,就是app端没有设置相机权限,造成的点击无反应情况,而实际上加了之后发现,并非简单的权限问题。
解决问题
因为android的版本碎片问题,很多版本的webview都对唤起函数有不同的支持。
我们需要重写webchromeclient下的openfilechooser()(5.0及以上系统回调onshowfilechooser())。我们通过intent在openfilechooser()中唤起系统相机和支持intent的相关app。
在系统相机或者相关app中一顿操作之后,当返回app的时候,我们在onactivityresult()中将选择好的图片通过valuecallback的onreceivevalue方法返回给webview。
附上代码:
1、首先是重写各个版本的webchromeclient的支持
webview.setwebchromeclient(new webchromeclient() { //for android 3.0+ public void openfilechooser(valuecallback<uri> uploadmsg) { selectimage(); mum = uploadmsg; intent i = new intent(intent.action_get_content); i.addcategory(intent.category_openable); i.settype("*/*"); mybasewebviewactivity.this.startactivityforresult(intent.createchooser(i, "file chooser"), fcr); } // for android 3.0+, above method not supported in some android 3+ versions, in such case we use this public void openfilechooser(valuecallback uploadmsg, string accepttype) { selectimage(); mum = uploadmsg; intent i = new intent(intent.action_get_content); i.addcategory(intent.category_openable); i.settype("*/*"); mybasewebviewactivity.this.startactivityforresult( intent.createchooser(i, "file browser"), fcr); } //for android 4.1+ public void openfilechooser(valuecallback<uri> uploadmsg, string accepttype, string capture) { selectimage(); mum = uploadmsg; intent i = new intent(intent.action_get_content); i.addcategory(intent.category_openable); i.settype("*/*"); mybasewebviewactivity.this.startactivityforresult(intent.createchooser(i, "file chooser"), mybasewebviewactivity.fcr); } //for android 5.0+ public boolean onshowfilechooser( webview webview, valuecallback<uri[]> filepathcallback, webchromeclient.filechooserparams filechooserparams) { selectimage(); if (muma != null) { muma.onreceivevalue(null); } muma = filepathcallback; intent takepictureintent = new intent(mediastore.action_image_capture); if (takepictureintent.resolveactivity(mybasewebviewactivity.this.getpackagemanager()) != null) { file photofile = null; try { photofile = createimagefile(); takepictureintent.putextra("photopath", mcm); } catch (ioexception ex) { log.e(tag, "image file creation failed", ex); } if (photofile != null) { mcm = "file:" + photofile.getabsolutepath(); filepath = photofile.getabsolutepath(); takepictureintent.putextra(mediastore.extra_output, uri.fromfile(photofile)); } else { takepictureintent = null; } } intent contentselectionintent = new intent(intent.action_get_content); contentselectionintent.addcategory(intent.category_openable); contentselectionintent.settype("*/*"); intent[] intentarray; if (takepictureintent != null) { intentarray = new intent[]{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, fcr); return true; } });
2、选完照片之后
/** * 打开图库,同时处理图片 */ private void selectimage() { compresspath = environment.getexternalstoragedirectory().getpath() + "/qwb/temp"; file file = new file(compresspath); if (!file.exists()) { file.mkdirs(); } compresspath = compresspath + file.separator + "compress.png"; file image = new file(compresspath); if (image.exists()) { image.delete(); } } // create an image file private file createimagefile() throws ioexception { @suppresslint("simpledateformat") string timestamp = dateutils.nowtimedetail(); string imagefilename = "img_" + timestamp + "_"; file storagedir = environment.getexternalstoragepublicdirectory(environment.directory_pictures); return file.createtempfile(imagefilename, ".jpg", storagedir); } private string mcm; private string filepath = ""; private valuecallback<uri> mum; private valuecallback<uri[]> muma; private final static int fcr = 1; string compresspath = ""; @override protected void onactivityresult(int requestcode, int resultcode, intent intent) { super.onactivityresult(requestcode, resultcode, intent); if (build.version.sdk_int >= 21) { uri[] results = null; //check if response is positive if (resultcode == activity.result_ok) { if (requestcode == fcr) { if (null == muma) { return; } if (intent == null) { //capture photo if no image available if (mcm != null) { // results = new uri[]{uri.parse(mcm)}; results = new uri[]{afterchosepic(filepath, compresspath)}; } } else { string datastring = intent.getdatastring(); if (datastring != null) { results = new uri[]{uri.parse(datastring)}; logutil.d("tag", intent.tostring()); // string realfilepath = getrealfilepath(uri.parse(datastring)); // results = new uri[]{afterchosepic(realfilepath, compresspath)}; } } } } muma.onreceivevalue(results); muma = null; } else { if (requestcode == fcr) { if (null == mum) return; uri result = intent == null || resultcode != result_ok ? null : intent.getdata(); mum.onreceivevalue(result); mum = null; } } } /** * 选择照片后结束 */ private uri afterchosepic(string oldpath, string newpath) { file newfile; try { newfile = fileutils.compressfile(oldpath, newpath); } catch (exception e) { e.printstacktrace(); newfile = null; } return uri.fromfile(newfile); }
3、工具类
public class fileutils { /** * 把图片压缩到200k * * @param oldpath * 压缩前的图片路径 * @param newpath * 压缩后的图片路径 * @return */ public static file compressfile(string oldpath, string newpath) { bitmap compressbitmap = fileutils.decodefile(oldpath); bitmap newbitmap = ratingimage(oldpath, compressbitmap); bytearrayoutputstream os = new bytearrayoutputstream(); newbitmap.compress(bitmap.compressformat.png, 100, os); byte[] bytes = os.tobytearray(); file file = null ; try { file = fileutils.getfilefrombytes(bytes, newpath); } catch (exception e) { e.printstacktrace(); }finally{ if(newbitmap != null ){ if(!newbitmap.isrecycled()){ newbitmap.recycle(); } newbitmap = null; } if(compressbitmap != null ){ if(!compressbitmap.isrecycled()){ compressbitmap.recycle(); } compressbitmap = null; } } return file; } private static bitmap ratingimage(string filepath,bitmap bitmap){ int degree = readpicturedegree(filepath); return rotaingimageview(degree, bitmap); } /** * 旋转图片 * @param angle * @param bitmap * @return bitmap */ public static bitmap rotaingimageview(int angle , bitmap bitmap) { //旋转图片 动作 matrix matrix = new matrix();; matrix.postrotate(angle); system.out.println("angle2=" + angle); // 创建新的图片 bitmap resizedbitmap = bitmap.createbitmap(bitmap, 0, 0, bitmap.getwidth(), bitmap.getheight(), matrix, true); return resizedbitmap; } /** * 读取图片属性:旋转的角度 * @param path 图片绝对路径 * @return degree旋转的角度 */ public static int readpicturedegree(string path) { int degree = 0; try { exifinterface exifinterface = new exifinterface(path); int orientation = exifinterface.getattributeint(exifinterface.tag_orientation, exifinterface.orientation_normal); switch (orientation) { case exifinterface.orientation_rotate_90: degree = 90; break; case exifinterface.orientation_rotate_180: degree = 180; break; case exifinterface.orientation_rotate_270: degree = 270; break; } } catch (ioexception e) { e.printstacktrace(); } return degree; } /** * 把字节数组保存为一个文件 * * @param b * @param outputfile * @return */ public static file getfilefrombytes(byte[] b, string outputfile) { file ret = null; bufferedoutputstream stream = null; try { ret = new file(outputfile); fileoutputstream fstream = new fileoutputstream(ret); stream = new bufferedoutputstream(fstream); stream.write(b); } catch (exception e) { // log.error("helper:get file from byte process error!"); e.printstacktrace(); } finally { if (stream != null) { try { stream.close(); } catch (ioexception e) { // log.error("helper:get file from byte process error!"); e.printstacktrace(); } } } return ret; } /** * 图片压缩 * * @param fpath * @return */ public static bitmap decodefile(string fpath) { bitmapfactory.options opts = new bitmapfactory.options(); opts.injustdecodebounds = true; opts.indither = false; // disable dithering mode opts.inpurgeable = true; // tell to gc that whether it needs free opts.ininputshareable = true; // which kind of reference will be used to bitmapfactory.decodefile(fpath, opts); final int required_size = 400; int scale = 1; if (opts.outheight > required_size || opts.outwidth > required_size) { final int heightratio = math.round((float) opts.outheight / (float) required_size); final int widthratio = math.round((float) opts.outwidth / (float) required_size); scale = heightratio < widthratio ? heightratio : widthratio;// } log.i("scale", "scal ="+ scale); opts.injustdecodebounds = false; opts.insamplesize = scale; bitmap bm = bitmapfactory.decodefile(fpath, opts).copy(bitmap.config.argb_8888, false); return bm; } /** * 创建目录 * @param path */ public static void setmkdir(string path) { file file = new file(path); if(!file.exists()) { file.mkdirs(); log.e("file", "目录不存在 创建目录 "); }else{ log.e("file", "目录存在"); } } /** * 获取目录名称 * @param url * @return filename */ public static string getfilename(string url) { int lastindexstart = url.lastindexof("/"); if(lastindexstart!=-1) { return url.substring(lastindexstart+1, url.length()); }else{ return null; } } /** * 删除该目录下的文件 * * @param path */ public static void delfile(string path) { if (!textutils.isempty(path)) { file file = new file(path); if (file.exists()) { file.delete(); } } } }
4、需要注意的问题
在打release包的时候,因为混淆的问题,点击又会没有反应,这是因为openfilechooser()是系统api,所以需要在混淆是不混淆该方法。
-keepclassmembers class * extends android.webkit.webchromeclient{ public void openfilechooser(...); }
当点击拍照之后,如果相机是横屏拍照的话,当拍照结束之后跳回app的时候,会导致app端当前的webview页面销毁并重新打开,需要在androidmanifest.xml中当前activity添加:
android:configchanges="orientation|keyboardhidden|screensize"
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
推荐阅读
-
详解Android WebView的input上传照片的兼容问题
-
Android开发中使用WebView控件浏览网页的方法详解
-
基于Android中Webview使用自定义的javascript进行回调的问题详解
-
Android中的webview支持页面中的文件上传实例代码
-
Android Naive与WebView的互相调用详解
-
基于Android中Webview使用自定义的javascript进行回调的问题详解
-
详解移动端HTML5页面端去掉input输入框的白色背景和边框(兼容Android和ios)
-
android上传图片到PHP的过程详解
-
Android Naive与WebView的互相调用详解
-
Android WebView的详解及实例