一、引言
webview怎么实现web的<input type="file" />选择图片功能,如何让h5通过webview调用系统相册和相机,并在图片传回h5的时已经将图片做了压缩处理?本篇就是解决这方面的问题。
这边h5 的<input type="file" />上传文件只是限定于图片类型,不需要pdf、txt等其他类型,如果要写一个普通的不限定于图片的上传文件功能可以参考https://www.jianshu.com/p/b0f1fdbfd502。
二、Webview实现
Webview要调用系统相册/相机,需要setWebChromeClient并重写WebChromeClient的方法。
mWebView.setWebChromeClient(new WebChromeClient(){
// For Android < 3.0
public void openFileChooser(ValueCallback<Uri> valueCallback) {
mUploadCallBack = valueCallback;
showFileChooser();
}
// For Android >= 3.0
public void openFileChooser(ValueCallback valueCallback, String acceptType) {
mUploadCallBack = valueCallback;
showFileChooser();
}
//For Android >= 4.1
public void openFileChooser(ValueCallback<Uri> valueCallback, String acceptType, String capture) {
mUploadCallBack = valueCallback;
showFileChooser();
}
// For Android >= 5.0
@Override
public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, WebChromeClient.FileChooserParams fileChooserParams) {
mUploadCallBackAboveL = filePathCallback;
showFileChooser();
return true;
}
});
/**
* 打开选择图片/相机
*/
private void showFileChooser() {
Intent intent1 = new Intent(Intent.ACTION_PICK, null);
intent1.setDataAndType(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/*");
// Intent intent1 = new Intent(Intent.ACTION_GET_CONTENT);
// intent1.addCategory(Intent.CATEGORY_OPENABLE);
// intent1.setType("image/*");
Intent intent2 = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
mCameraFilePath = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator +
System.currentTimeMillis() + ".jpg";
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
// android7.0注意uri的获取方式改变
Uri photoOutputUri = FileProvider.getUriForFile(
MainActivity.this,
BuildConfig.APPLICATION_ID + ".fileProvider",
new File(mCameraFilePath));
intent2.putExtra(MediaStore.EXTRA_OUTPUT, photoOutputUri);
} else {
intent2.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(new File(mCameraFilePath)));
}
Intent chooser = new Intent(Intent.ACTION_CHOOSER);
chooser.putExtra(Intent.EXTRA_TITLE, "File Chooser");
chooser.putExtra(Intent.EXTRA_INTENT,intent1);
chooser.putExtra(Intent.EXTRA_INITIAL_INTENTS, new Intent[]{intent2});
startActivityForResult(chooser, REQUEST_CODE_FILE_CHOOSER);
}
然后,在onActivityResult里处理获取到的图片。
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_CODE_FILE_CHOOSER) {
Uri result = data == null || resultCode != RESULT_OK ? null : data.getData();
// 压缩到多少宽度以内
int maxW = 1000;
// 压缩到多少大小以内,1024kb
int maxSize = 1024;
if (result == null) {
// 看是否从相机返回
File cameraFile = new File(mCameraFilePath);
if (cameraFile.exists()) {
result = Uri.fromFile(cameraFile);
sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, result));
}
}
if (result != null) {
// 根据uri获取路径
String path = FileUtils.getPath(this, result);
if (!TextUtils.isEmpty(path)) {
File f = new File(path);
if (f.exists() && f.isFile()) {
// 按大小和尺寸压缩图片
Bitmap b = getCompressBitmap(path, maxW, maxW, maxSize);
String basePath = Environment.getExternalStorageDirectory().getAbsolutePath();
String compressPath = basePath + File.separator + "photos" + File.separator
+ System.currentTimeMillis() + ".jpg";
// 压缩完保存在文件里
if (saveBitmapToFile(b, compressPath)) {
Uri newUri = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
newUri = FileProvider.getUriForFile(
MainActivity.this,
BuildConfig.APPLICATION_ID + ".fileProvider",
new File(compressPath));
} else {
newUri = Uri.fromFile(new File(compressPath));
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if (mUploadCallBackAboveL != null) {
if (newUri != null) {
mUploadCallBackAboveL.onReceiveValue(new Uri[]{newUri});
mUploadCallBackAboveL = null;
return;
}
}
} else if (mUploadCallBack != null) {
if (newUri != null) {
mUploadCallBack.onReceiveValue(newUri);
mUploadCallBack = null;
return;
}
}
}
}
}
}
clearUploadMessage();
return;
}
}
如果没有图片返回给h5,记得要执行下面代码,避免h5下次点击选择图片时无法响应。
/**
* webview没有选择图片也要传null,防止下次无法执行
*/
private void clearUploadMessage(){
if (mUploadCallBackAboveL != null) {
mUploadCallBackAboveL.onReceiveValue(null);
mUploadCallBackAboveL = null;
}
if (mUploadCallBack != null) {
mUploadCallBack.onReceiveValue(null);
mUploadCallBack = null;
}
}
相关的压缩和保存图片代码如下:
/**
* 根据路径获取bitmap(压缩后)
*
* @param srcPath 图片路径
* @param width 最大宽(压缩完可能会大于这个,这边只是作为大概限制,避免内存溢出)
* @param height 最大高(压缩完可能会大于这个,这边只是作为大概限制,避免内存溢出)
* @param size 图片大小,单位kb
* @return 返回压缩后的bitmap
*/
public static Bitmap getCompressBitmap(String srcPath, float width, float height, int size) {
BitmapFactory.Options newOpts = new BitmapFactory.Options();
// 开始读入图片,此时把options.inJustDecodeBounds 设回true了
newOpts.inJustDecodeBounds = true;
BitmapFactory.decodeFile(srcPath, newOpts);
newOpts.inJustDecodeBounds = false;
int w = newOpts.outWidth;
int h = newOpts.outHeight;
int scaleW = (int) (w / width);
int scaleH = (int) (h / height);
int scale = scaleW < scaleH ? scaleH : scaleW;
if (scale <= 1) {
scale = 1;
}
newOpts.inSampleSize = scale;// 设置缩放比例
// 重新读入图片,注意此时已经把options.inJustDecodeBounds 设回false了
Bitmap bitmap = BitmapFactory.decodeFile(srcPath, newOpts);
// 压缩好比例大小后再进行质量压缩
return compressImage(bitmap, size);
}
/**
* 图片质量压缩
*
* @param image 传入的bitmap
* @param size 压缩到多大,单位kb
* @return 返回压缩完的bitmap
*/
public static Bitmap compressImage(Bitmap image, int size) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
// 质量压缩方法,这里100表示不压缩,把压缩后的数据存放到baos中
int options = 100;
image.compress(Bitmap.CompressFormat.JPEG, options, baos);
// 循环判断如果压缩后图片是否大于size,大于继续压缩
while (baos.toByteArray().length / 1024 > size) {
// 重置baos即清空baos
baos.reset();
// 每次都减少10
options -= 10;
// 这里压缩options%,把压缩后的数据存放到baos中
image.compress(Bitmap.CompressFormat.JPEG, options, baos);
}
// 把压缩后的数据baos存放到ByteArrayInputStream中
ByteArrayInputStream isBm = new ByteArrayInputStream(baos.toByteArray());
// 把ByteArrayInputStream数据生成图片
Bitmap bitmap = BitmapFactory.decodeStream(isBm, null, null);
return bitmap;
}
/**
* bitmap保存为文件
*
* @param bm bitmap
* @param filePath 文件路径
* @return 返回保存结果 true:成功,false:失败
*/
public static boolean saveBitmapToFile(Bitmap bm, String filePath) {
try {
File file = new File(filePath);
file.deleteOnExit();
if (!file.getParentFile().exists()) {
file.getParentFile().mkdirs();
}
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file));
boolean b = false;
if (filePath.toLowerCase().endsWith(".png")) {
b = bm.compress(Bitmap.CompressFormat.PNG, 100, bos);
} else {
b = bm.compress(Bitmap.CompressFormat.JPEG, 100, bos);
}
bos.flush();
bos.close();
return b;
} catch (FileNotFoundException e) {
} catch (IOException e) {
}
return false;
}
对于根据uri获取图片路径的代码也比较关键。相关代码可以参考https://www.jianshu.com/p/25c35da68db2
如果你的应用混淆了要注意下,openFileChooser方法并不是WebChromeClient的对外开放的方法,因此这个方法会被混淆,解决办法也比较简单,只需要在混淆文件里控制一下即可:
-keepclassmembers class * extends android.webkit.WebChromeClient{
public void openFileChooser(...);
}
好了,这样就可以让webview调用原生相机和相册选择图片,并对图片做压缩处理。