详解Android WebView的input上传照片的兼容问题

发布时间 - 2026-01-11 02:48:00    点击率:

问题

前几天接到的一个需求,是关于第三方理财产品的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  # input兼容  # WebView  # 上传照片 


相关栏目: 【 网站优化151355 】 【 网络推广146373 】 【 网络技术251813 】 【 AI营销90571


相关推荐: 百度浏览器网页无法复制文字怎么办 百度浏览器复制修复  作用域操作符会触发自动加载吗_php类自动加载机制与::调用【教程】  Java类加载基本过程详细介绍  Laravel模型关联查询教程_Laravel Eloquent一对多关联写法  如何用搬瓦工VPS快速搭建个人网站?  Laravel如何处理文件下载请求?(Response示例)  制作旅游网站html,怎样注册旅游网站?  Laravel安装步骤详细教程_Laravel环境搭建指南  图片制作网站免费软件,有没有免费的网站或软件可以将图片批量转为A4大小的pdf?  laravel怎么配置Redis作为缓存驱动_laravel Redis缓存配置教程  Laravel怎么实现API接口鉴权_Laravel Sanctum令牌生成与请求验证【教程】  Laravel如何实现登录错误次数限制_Laravel自带LoginThrottles限流配置【方法】  Python正则表达式进阶教程_复杂匹配与分组替换解析  Laravel怎么实现观察者模式Observer_Laravel模型事件监听与解耦开发【指南】  Laravel Asset编译怎么配置_Laravel Vite前端构建工具使用  Laravel如何为API生成Swagger或OpenAPI文档  如何在IIS中新建站点并配置端口与物理路径?  php中::能调用final静态方法吗_final修饰静态方法调用规则【解答】  Laravel如何生成URL和重定向?(路由助手函数)  如何挑选最适合建站的高性能VPS主机?  如何在Windows环境下新建FTP站点并设置权限?  Laravel怎么实现模型属性的自动加密  Laravel怎么在Blade中安全地输出原始HTML内容  Laravel如何发送系统通知?(Notification渠道示例)  标准网站视频模板制作软件,现在有哪个网站的视频编辑素材最齐全的,背景音乐、音效等?  香港服务器网站生成指南:免费资源整合与高速稳定配置方案  BootStrap整体框架之基础布局组件  独立制作一个网站多少钱,建立网站需要花多少钱?  如何在Windows虚拟主机上快速搭建网站?  Android中Textview和图片同行显示(文字超出用省略号,图片自动靠右边)  网站制作软件有哪些,制图软件有哪些?  高防服务器租用首荐平台,企业级优惠套餐快速部署  浏览器如何快速切换搜索引擎_在地址栏使用不同搜索引擎【搜索】  IOS倒计时设置UIButton标题title的抖动问题  中国移动官方网站首页入口 中国移动官网网页登录  Laravel Admin后台管理框架推荐_Laravel快速开发后台工具  Laravel Seeder怎么填充数据_Laravel数据库填充器的使用方法与技巧  Android自定义控件实现温度旋转按钮效果  ChatGPT怎么生成Excel公式_ChatGPT公式生成方法【指南】  Laravel如何使用Passport实现OAuth2?(完整配置步骤)  晋江文学城电脑版官网 晋江文学城网页版直接进入  制作ppt免费网站有哪些,有哪些比较好的ppt模板下载网站?  如何用美橙互联一键搭建多站合一网站?  微信公众帐号开发教程之图文消息全攻略  如何撰写建站申请书?关键要点有哪些?  Python文本处理实践_日志清洗解析【指导】  Laravel中间件如何使用_Laravel自定义中间件实现权限控制  Laravel如何与Inertia.js和Vue/React构建现代单页应用  Laravel如何使用Facades(门面)及其工作原理_Laravel门面模式与底层机制  Laravel Pest测试框架怎么用_从PHPUnit转向Pest的Laravel测试教程