详解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中的windowSoftInputMode属性详解
- android 使用uinput模拟输入设备的方法
- Android WebView 不支持 H5 input type="file" 解决方法
- Android 数据存储之 FileInputStream 工具类及FileInputStream类的使用
- Android编程开发之EditText中inputType属性小结
- Android WebView支持input file启用相机/选取照片功能
- Android网页H5 Input选择相机和系统相册
- Android自定义PasswordInputView密码输入
- Android InputMethodManager输入法简介
- 从"Show tabs"了解Android Input系统