Android WebView 调用相机、相册,压缩图片后上传
声明:文中代码根据多篇博客进行修改,由本人缝合而成,对应地方会放出原文链接。
- 重写webChromeClient的onShowFileChooser方法
@Override
public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {
mUploadCallbackAboveL = filePathCallback;
openAlbum();
return true;
}
fileChooserParams可以接收h5中input标签的一些参数,选择好文件后,将文件uri传递给filePathCallback。
原文链接:调用原生相机、相册
- 选择调用相机或相册
private void openAlbum() {
// 指定拍照存储位置的方式调起相机
String filePath = Environment.getExternalStorageDirectory() + File.separator
+ Environment.DIRECTORY_PICTURES + File.separator;
String fileName = "IMG_" + DateFormat.format("yyyyMMdd_hhmmss", Calendar.getInstance(Locale.CHINA)) + ".jpg";
image = new File(filePath + fileName);
imageUri = Uri.fromFile(image);
Intent captureIntent = new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE);
captureIntent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
Intent Photo = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
Intent chooserIntent = Intent.createChooser(Photo, "选择照片");
chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, new Parcelable[]{captureIntent});
startActivityForResult(chooserIntent, REQUEST_CODE);
}
通过imageUri保存调用相机拍照的照片的存储位置。用Intent打开选择相机、相册或文件夹的activity。
- 接收选择照片,或者拍照返回的uri
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_CODE) {
if (mUploadCallbackAboveL != null) {
chooseAbove(resultCode, data);
} else {
Toast.makeText(this, "发生错误", Toast.LENGTH_SHORT).show();
}
}
}
choosAbove方法来处理获取后的uri,并回传给filePathCallback。
- 处理uri
具体的处理步骤,通过uri获取到图片的path,读取图片,压缩图片后重新存储,将压缩后的图片uri进行回传
(1). chooseAbove方法
private void chooseAbove(int resultCode, Intent data) {
if (RESULT_OK == resultCode) {
updatePhotos();
if (data != null) {
// 处理选择的照片
Uri[] results;
Uri uriData = data.getData();
if (uriData != null) {
results = new Uri[]{uriData};
try {
Uri compressImageUri = ImageCompressUtils.compressBmpFromBmp(uriToString(uriData));
mUploadCallbackAboveL.onReceiveValue(new Uri[]{compressImageUri});
} catch (Exception e) {
e.printStackTrace();
requestWritePermission();
}
} else {
mUploadCallbackAboveL.onReceiveValue(null);
}
} else {
// 处理拍照的照片
try {
Uri compressImageUri = ImageCompressUtils.compressBmpFromBmp(imageUri.getPath());
mUploadCallbackAboveL.onReceiveValue(new Uri[]{compressImageUri});
} catch (Exception e) {
e.printStackTrace();
requestWritePermission();
}
}
} else {
mUploadCallbackAboveL.onReceiveValue(null);
}
mUploadCallbackAboveL = null;
}
由于拍照的照片存储在imageUri,这里将拍照和相册选择分开处理,注意,如果没有选择照片,给filePathCallback传一个空值,防止上传只能点击一次后失效。
(2). 压缩图片的工具类ImageCompressUtils
public class ImageCompressUtils {
/**
* @param srcPath
* @return
* @description 将图片从本地读到内存时,即图片从File形式变为Bitmap形式
* 特点:通过设置采样率,减少图片的像素,达到对内存中的Bitmao进行压缩
* 方法说明: 该方法就是对Bitmap形式的图片进行压缩, 也就是通过设置采样率, 减少Bitmap的像素, 从而减少了它所占用的内存
*/
public static Uri compressBmpFromBmp(String srcPath) {
// String srcPathStr = srcPath;
BitmapFactory.Options newOptions = new BitmapFactory.Options();
newOptions.inJustDecodeBounds = true;//只读边,不读内容
Bitmap bitmap = BitmapFactory.decodeFile(srcPath, newOptions);
newOptions.inJustDecodeBounds = false;
int w = newOptions.outWidth;
int h = newOptions.outHeight;
float hh = 800f;
float ww = 480f;
int be = 1;
if (w > h && w > ww) {
be = (int) (newOptions.outWidth / ww);
} else if (w < h && h > hh) {
be = (int) (newOptions.outHeight / hh);
}
if (be <= 0)
be = 1;
newOptions.inSampleSize = be;//设置采样率
newOptions.inPreferredConfig = Bitmap.Config.ARGB_8888;//该模式是默认的,可不设
newOptions.inPurgeable = true;//同时设置才会有效
newOptions.inInputShareable = true;//当系统内存不够时候图片会自动被回收
bitmap = BitmapFactory.decodeFile(srcPath, newOptions);
int degree = readPictureDegree(srcPath);
bitmap = rotateBitmap(bitmap, degree);
return compressBmpToFile(bitmap);
}
/**
* @param bmp
* @description 将图片保存到本地时进行压缩, 即将图片从Bitmap形式变为File形式时进行压缩,
* 特点是: File形式的图片确实被压缩了, 但是当你重新读取压缩后的file为 Bitmap是,它占用的内存并没有改变
* 所谓的质量压缩,即为改变其图像的位深和每个像素的透明度,也就是说JPEG格式压缩后,原来图片中透明的元素将消失,所以这种格式很可能造成失真
*/
public static Uri compressBmpToFile(Bitmap bmp) {
String path = Environment.getExternalStorageDirectory().getAbsolutePath() + "/shoppingMall/compressImgs/";
File file = new File(path + System.currentTimeMillis() + ".jpg");
//判断文件夹是否存在,如果不存在则创建文件夹
if (!file.getParentFile().exists()) {
file.getParentFile().mkdirs();
}
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int options = 80;
bmp.compress(Bitmap.CompressFormat.JPEG, options, baos);
while (baos.toByteArray().length / 1024 > 100) {
baos.reset();
options -= 10;
bmp.compress(Bitmap.CompressFormat.JPEG, options, baos);
}
try {
FileOutputStream fos = new FileOutputStream(file);
fos.write(baos.toByteArray());
fos.flush();
fos.close();
// copyExif(srcPathStr,file.getAbsolutePath());
// return file.getAbsolutePath();
return Uri.fromFile(file);
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
/**
* 获取图片旋转角度
*
* @param srcPath
* @return
*/
private static int readPictureDegree(String srcPath) {
int degree = 0;
try {
ExifInterface exifInterface = new ExifInterface(srcPath);
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;
}
//处理图片旋转
private static Bitmap rotateBitmap(Bitmap bitmap, int rotate) {
if (bitmap == null)
return null;
int w = bitmap.getWidth();
int h = bitmap.getHeight();
// Setting post rotate to 90
Matrix mtx = new Matrix();
mtx.postRotate(rotate);
return Bitmap.createBitmap(bitmap, 0, 0, w, h, mtx, true);
}
}
调用压缩方法,将压缩后的图片的uri传递给filePathCallback,上传结束。
5.需要注意的地方
- 如果应用没有文件存储的权限,要动态请求权限。一下是请求权限及其回调方法。
原文链接:动态获取文件存储权限
private void requestWritePermission() {
if (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, WRITE_PERMISSION);
}
}
@Override
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
if (requestCode == WRITE_PERMISSION) {
if (grantResults[0] != PackageManager.PERMISSION_GRANTED) {
String message = "必须提供文件存储权限,否则无法正常使用.";
AlertDialog.Builder localBuilder = new AlertDialog.Builder(webView.getContext());
localBuilder.setMessage(message).setPositiveButton(getResources().getString(R.string.sure), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
requestWritePermission();
}
});
localBuilder.setCancelable(false);
localBuilder.create().show();
}
}
}
- 通过uri获取path的问题
imageUri保存的图片,通过getPath可以直接获取。但是相册和文件夹选择的uri,getPath获取到的路径在读取照片时却找不到。因此,使用如下方法,通过uri获取path。
原文地址:uri获取path
private String uriToString(Uri uri) {
String path = null;
if (ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) {
if (DocumentsContract.isDocumentUri(this, uri)) {
// ExternalStorageProvider
if ("com.android.externalstorage.documents".equals(uri.getAuthority())) {
String docId = DocumentsContract.getDocumentId(uri);
String[] split = docId.split(":");
String type = split[0];
if ("primary".equalsIgnoreCase(type)) {
path = Environment.getExternalStorageDirectory() + "/" + split[1];
}
} else if ("com.android.providers.downloads.documents".equals(uri.getAuthority())) {
// DownloadsProvider
String id = DocumentsContract.getDocumentId(uri);
Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
path = getDataColumn(this, contentUri, null, null);
} else if ("com.android.providers.media.documents".equals(uri.getAuthority())) {
// MediaProvider
String docId = DocumentsContract.getDocumentId(uri);
String[] split = docId.split(":");
String type = split[0];
Uri contentUri = null;
if ("image".equals(type)) {
contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
} else if ("video".equals(type)) {
contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
} else if ("audio".equals(type)) {
contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
}
String selection = "_id=?";
String[] selectionArgs = new String[]{split[1]};
path = getDataColumn(this, contentUri, selection, selectionArgs);
}
} else {
path = getRealPathFromUri(this, uri);
}
}
return path;
}
private String getRealPathFromUri(Context context, Uri contentUri) {
Cursor cursor = null;
try {
String[] proj = {MediaStore.Files.FileColumns.DATA};
cursor = context.getContentResolver().query(contentUri, proj, null, null, null);
int column_index = cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.DATA);
cursor.moveToFirst();
return cursor.getString(column_index);
} finally {
if (cursor != null) {
cursor.close();
}
}
}
private String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) {
Cursor cursor = null;
final String column = "_data";
final String[] projection = {column};
try {
cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null);
if (cursor != null && cursor.moveToFirst()) {
final int column_index = cursor.getColumnIndexOrThrow(column);
return cursor.getString(column_index);
}
} finally {
if (cursor != null)
cursor.close();
}
return null;
}
- 拍照后,广播通知系统刷新文件
private void updatePhotos() {
// 该广播即使多发(即选取照片成功时也发送)也没有关系,只是唤醒系统刷新媒体文件
Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
intent.setData(imageUri);
sendBroadcast(intent);
}