上一篇文章介绍了WebView与JS之间的数据交互,其实就是把字符串传来传去,这对文本格式的信息传输来说倒还凑合,倘若要传输图片信息就不管用了。所以,要想让h5网页支持从手机上传图片,还得另外想办法,当然各版本的Android系统也都提供了相应的解决办法。在Android 4.*系统上面,开发者可以重写WebChromeClient的openFileChooser函数;在Android 5.0以上的系统,开发者可以重写WebChromeClient的onShowFileChooser函数。话虽如此,可实际编码的时候,会发现并不容易,因为不但要兼容各种版本的安卓系统,而且要考虑不同操作方式下面的处理步骤。

首先是Android不同系统的适配问题,对于4.*版本要重写openFileChooser方法,对于5.0以上版本要重写onShowFileChooser方法。另外注意二者的回调方式也不一样,4.*的回调参数类型是ValueCallback<Uri>,而5.0以上的回调参数类型是ValueCallback<Uri[]>,因此要声明两个回调参数变量,分别用来保存二者各自的回调信息。相关代码如下所示:

 private static ValueCallback<Uri> mUploadMessage;private static ValueCallback<Uri[]> mUploadMessageLollipop;private class MyWebChromeClient extends WebChromeClient {// Android 4.*(包括4.1、4.2、4.3、4.4)public void openFileChooser(ValueCallback<Uri> uploadMsg,String acceptType, String capture) {Log.d(TAG, "openFileChooser 4.*");mUploadMessage = uploadMsg;openSelectDialog();}// Android 5.0+(包括5.*、6.0、7.*、8.*)@Overridepublic boolean onShowFileChooser(WebView webView,ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {Log.d(TAG, "openFileChooser 5.0+");mUploadMessageLollipop = filePathCallback;openSelectDialog();return true;}}

然后就上传图片这个功能而言,既要支持从手机相册中挑选已有的图片,也要支持现场拍照并即时上传拍摄好的照片。如此一来,就不能仅仅从相册选择文件,而要弹出一个列表对话框,好让用户决定是从相册上传图片,还是当场拍照当场上传。所以接下来得同时实现这两种上传方式,示例代码如下:

 private String mCameraPhotoPath = null;private void openSelectDialog() {// 声明相机的拍照行为Intent photoIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);if (photoIntent.resolveActivity(getPackageManager()) != null) {mCameraPhotoPath = "file:" + getExternalFilesDir(Environment.DIRECTORY_PICTURES).toString() + "/" + DateUtil.getNowDateTime("") + ".jpg";Log.d(TAG, "photoFile=" + mCameraPhotoPath);photoIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.parse(mCameraPhotoPath));}Intent[] intentArray = new Intent[] { photoIntent };// 声明相册的打开行为Intent selectionIntent = new Intent(Intent.ACTION_GET_CONTENT);selectionIntent.addCategory(Intent.CATEGORY_OPENABLE);selectionIntent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);selectionIntent.setType("image/*");// 弹出含相机和相册在内的列表对话框Intent chooserIntent = new Intent(Intent.ACTION_CHOOSER);chooserIntent.putExtra(Intent.EXTRA_INTENT, selectionIntent);chooserIntent.putExtra(Intent.EXTRA_TITLE, "请拍照或选择图片");chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, intentArray);startActivityForResult(Intent.createChooser(chooserIntent, "选择图片"), 1);}

选择好图片确定后(含拍照和从相册选取),App代码进入到onActivityResult方法内部,开发者在此校验结果代码,根据图片选取形式分别获得具体的图片数据,然后区分4.*系统和5.+系统将图片传给h5页面。下面是onActivityResult方法的处理代码:

 private static final int FILE_SELECT_CODE = 1;private int mResultCode = Activity.RESULT_CANCELED;@Overridepublic void onActivityResult(int requestCode, int resultCode, Intent data) {Log.d(TAG, "onActivityResult requestCode=" + requestCode + ", resultCode=" + resultCode);if (requestCode != FILE_SELECT_CODE|| (mUploadMessage == null && mUploadMessageLollipop == null)) {super.onActivityResult(requestCode, resultCode, data);return;}mResultCode = resultCode;Log.d(TAG, "mCameraPhotoPath=" + mCameraPhotoPath);if (resultCode == Activity.RESULT_OK) {uploadPhoto(resultCode, data);}}private void uploadPhoto(int resultCode, Intent data) {long fileSize = 0;try {String file_path = mCameraPhotoPath.replace("file:", "");File file = new File(file_path);fileSize = file.length();} catch (Exception e) {e.printStackTrace();}if (data != null || mCameraPhotoPath != null) {Integer count = 1;ClipData images = null;try {images = data.getClipData();} catch (Exception e) {e.printStackTrace();}if (images == null && data != null && data.getDataString() != null) {count = data.getDataString().length();} else if (images != null) {count = images.getItemCount();}Uri[] results = new Uri[count];// Check that the response is a good oneif (resultCode == Activity.RESULT_OK) {Log.d(TAG, "fileSize=" + fileSize);if (fileSize != 0) {// If there is not data, then we may have taken a photoif (mCameraPhotoPath != null) {results = new Uri[] { Uri.parse(mCameraPhotoPath) };}} else if (data.getClipData() == null) {results = new Uri[] { Uri.parse(data.getDataString()) };} else {for (int i = 0; i < images.getItemCount(); i++) {results[i] = images.getItemAt(i).getUri();}}}// 区分不同系统分别返回上传结果if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {mUploadMessageLollipop.onReceiveValue(results);mUploadMessageLollipop = null;} else {mUploadMessage.onReceiveValue(results[0]);mUploadMessage = null;}}}

其后还要注意,用户打开相册或者打开相机的时候,也有可能什么都不做就返回到原页面,由于这个取消选择的操作没有走完全流程,导致h5网页的回调资源没有回收,用户再去上传图片之时会发现页面不会响应了,因此开发者要在代码中手工替h5页面回收回调资源,这样下次用户才能继续上传图片。手工回收资源的办法是重写Activity的onResume函数,具体实现代码见下:

 @Overrideprotected void onResume() {super.onResume();// 取消选择时需要回调onReceiveValue,否则网页会挂住,不会再响应点击事件if (mResultCode == Activity.RESULT_CANCELED) {try {if (mUploadMessageLollipop != null) {mUploadMessageLollipop.onReceiveValue(null);}if (mUploadMessage != null) {mUploadMessage.onReceiveValue(null);}} catch (Exception e) {e.printStackTrace();}}}

接着即可打开实际的h5页面进行图片上传测试啦,这里的h5测试网址用的是http://m.54php.cn/demo/h5_upload,测试的调用代码很简单,设置好WebView的访问地址以及浏览器对象就好了,例子代码如下所示:

     WebView webView = (WebView) findViewById(R.id.webView);WebSettings webSettings = webView.getSettings();webSettings.setJavaScriptEnabled(true);webSettings.setBuiltInZoomControls(true);webView.loadUrl("http://m.54php.cn/demo/h5_upload");webView.setWebViewClient(new MyWebViewClient(this));webView.setWebChromeClient(new MyWebChromeClient());

最后观察一下WebView配合上述测试网址的运行界面,先看看Android4.4手机的测试画面,下面的左图为打开测试网址的初始界面,右图为点击上传按钮后在屏幕中央弹出选择对话框:
  
先在对话框中选择从相册上传,成功上传图片后的h5页面如下面的左图所示;重新点击上传按钮,这次选择使用相机拍照,并把照片成功上传后的h5页面如下面的右图所示:
  

再来看看Android6.0手机的测试画面,下面的左图为打开测试网址的初始界面,右图为点击上传按钮后在屏幕下方弹出选择对话框:
  
先在对话框中选择从相册上传,成功上传图片后的h5页面如下面的左图所示;重新点击上传按钮,这次选择使用相机拍照,并把照片成功上传后的h5页面如下面的右图所示:
  

点此查看Android开发笔记的完整目录

Android开发笔记(一百五十二)H5通过WebView上传图片相关推荐

  1. Android开发笔记(五十二)通知推送Notification

    PendingIntent 准备工作复习一下PendingIntent,前面的博文< Android开发笔记(五十)定时器AlarmManager>已经提到了它.PendingIntent ...

  2. Android开发笔记(五十四)数据共享接口ContentProvider

    ContentProvider 前面几节介绍了进程间通信的几种方式,包括消息包级别的Messenger.接口调用级别的AIDL.启动页面/服务级别的Notification,还有就是本节这个数据库级别 ...

  3. Android开发笔记(五十九)巧用传感器

    传感器Sensor 传感器是Android用来感知周围环境以及运动信息的工具.因为具体的感应信息依赖于相关硬件,所以虽然Android提供了众多的感应器,但不是每部手机都能支持这么多感应器,恰恰相反, ...

  4. Android开发笔记(六十二)HTTP数据格式的解析

    json解析 android有两种主流的json解析方案,一种是sdk自带的由Google提供的json(包名前缀为org.json),另一种是Alibaba提供的第三方jar包fastjson(包名 ...

  5. Android开发笔记(五十八)铃声与震动

    拖动条SeekBar SeekBar继承自进度条ProcessBar,有关ProcessBar的介绍见<Android开发笔记(四十九)异步任务处理AsyncTask>.SeekBar与P ...

  6. Android开发笔记(四十二)Broadcast的生命周期

    Broadcast是什么 广播的特性 广播(Broadcast)用于Android组件之间的灵活通信,它与Activity和Service的区别在于: 1.Activity和Service都只能一对一 ...

  7. Android开发笔记(五十六)摄像头拍照

    相机Camera Camera是直接操作摄像头硬件的工具类.常用的方法如下: getNumberOfCameras : 获取本机的摄像头数目 open : 打开摄像头,默认打开后置摄像头.如果有多个摄 ...

  8. Android开发笔记(八十二)SDK版本兼容

    统一主题与风格 Android控件很多属性都有默认值,比如文字默认黑色.编辑框默认透明背景等等,但因为android是开源的,各厂商都会自行修修补补,所以很多时候默认值并不靠谱.举例如下: 1.在某些 ...

  9. Android开发笔记(七十二)数据加密算法

    编码算法 URL编码 URL编码其实并非加解密算法,只是对特殊字符进行字符转义,从而方便在URL中传输参数.URL编码有两种方式,一种是狭义的URL编码,另一种是广义的URL编码. 狭义的URL编码指 ...

  10. Android开发笔记(五十五)手机设备基本操作

    获取手机基本信息 手机的基本信息分两类,一类是与电话有关的信息,另一类是设备自身的信息. 与电话有关的信息可由TelephonyManager类获得,常用的参数与对应的方法如下所示: 网络运营商名称 ...

最新文章

  1. js怎样和硬件交互_Node.js与JavaScript
  2. tank html5,index.html
  3. oracle adg的特点是什么,Oracle12c ADG新特性
  4. linux如何登陆oracle?如何停止、启动oracle和其监听?
  5. java 网络驱动器_删除多余的网络驱动器
  6. Java数组的十大方法
  7. ArcMap 导入 wrl_flmic拍摄的素材如何无损导入电脑
  8. scrapy中使用css选择器罗列下一级的所有标签
  9. sql select 抛异常_mysql数据库及sql注入
  10. 荣耀份额重回中国市场前三;​特斯拉使用替代芯片重写汽车软件;RabbitMQ 3.9.0 发布|极客日报...
  11. 谈谈能带来高薪报酬的软件技术
  12. UWP开发随笔——UWP新控件!AutoSuggestBox!
  13. Java编程:克鲁斯卡尔算法(未知起点求最小生成树)
  14. 使用微信小程序生成海报
  15. 将一个负数赋值给一个无符号数会出现什么情况呢
  16. Sprint周期开发总结
  17. 仓储系统主要注意事项
  18. 第三届中医药文化传承与技能发展大会召开助推中医药文化传承创新
  19. XP系统常用的登录密码方法破解(一共9种)
  20. 3种解法 - 计算三维形体的表面积

热门文章

  1. 深度学习——卷积神经网络CNN
  2. 吴恩达机器学习【第三天】线性代数基础知识
  3. Windows RDP协议重大漏洞后发现黑客开始大规模扫瞄
  4. (筆記) 如何增加SignalTap II能觀察的reg與wire數量? (SOC) (Quartus II) (SignalTap II)
  5. 使用template.js加载后端数据
  6. envi窗口滤波_高光谱ENVI图像处理之滤波
  7. Perforce的环境变量配置
  8. 四张照片合成一张怎么弄_我在朋友圈发了这张照片后,所有人都求问教程
  9. 客户和顾客是一个意思吗_履约保证金和投标保证金是一个意思吗?
  10. 淮阴工学院计算机专业的考研率,淮阴工学院江淮学院(淮阴工学院考研录取率)...