Android 11®兼容拍照,系统裁剪图片路径问题
应用适配到android Q后,在android R测试发现拍照,图片裁剪后图片带不回来显示。具体使用场景描述:适配Android Q后,由于存储方式变成沙盒模式,导致之前的file://xxxxx或直接的文件路径,这都无法访问到文件。但系统的多媒体文件夹如果有存储权限还是可以访问到的。测试发现在一加手机系统是 R 上的,给拍照和裁剪指定的路径是无法存储起来的。HUAWEI Q系统的指定的路径如果是/storage/emulated/0/Android/data/自己应用包名/xxxx可以存储起来,否则就存储失败。其他低版本系统指定的存储路径多可以存储。
- 针对上面问题的解决方法
通过ContentResolver来操作,实现一种比较通用的方法,不用去判断手机系统版本。可以覆盖4.4~11的系统
先来看看拍照
String state = Environment.getExternalStorageState();
Uri outputUri = null;
if (!state.equals(Environment.MEDIA_MOUNTED)) {
outputUri = activity.getContentResolver().insert(MediaStore.Images.Media.INTERNAL_CONTENT_URI, new ContentValues());
} else {
outputUri = activity.getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, new ContentValues());
}
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
intent.putExtra(MediaStore.EXTRA_OUTPUT, outputUri);
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
activity.startActivityForResult(intent, RequestCode.ACTIVITY_REQUEST_CODE_TAKE_PHOTO);`
通过contentresolver插入一条记录来生成outputUri,拍完照片outputUri就是图片的路径了。但如果没拍照直接返回就要删掉之前插入的这条记录。不然图库查看图片时可能会多出一张空图片。删除方式在onActivityResult()操作,如下代码
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case MediaUtils.RequestCode.ACTIVITY_REQUEST_CODE_TAKE_PHOTO:// 拍照
if (resultCode == Activity.RESULT_OK) {
starScan(mediaCaptureItem.imgUri, this);
Intent intent = new Intent();
intent.putExtra("capturePath", mediaCaptureItem);
this.setResult(9999, intent);
this.finish();
}else{
//没拍照返回,删除图片库中的图片
int delete = getContentResolver().delete(outputUri, null, null);
}
break;
default:
break;
}
}
系统R有点我觉得挺有意思的,拍照裁剪如果不指定outputUri,也是可以自动生成一个Uri路径,但我们却删除不了,没有权限。深深的觉得安卓的管理越来越严了。
我们获得了outputUri后怎么显示图片,资料还是很多的自己搜,如有不了解的评论留言!
图片裁剪
打开系统裁剪,关于图片路径问题跟拍照一个样的。
private void startPhotoZoom(Uri uri) {
String state = Environment.getExternalStorageState();
if (!state.equals(Environment.MEDIA_MOUNTED)) {//生成一个content://的图片路径,存放截图
outputUri = getContentResolver().insert(MediaStore.Images.Media.INTERNAL_CONTENT_URI, new ContentValues());
} else {
outputUri = getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, new ContentValues());
}
Intent intent = new Intent("com.android.camera.action.CROP");
intent.setDataAndType(uri, IMAGE_UNSPECIFIED);
intent.putExtra("crop", "true");
intent.putExtra("aspectX", 1);
intent.putExtra("aspectY", 1);
intent.putExtra("outputX", DisplayUtils.getScreenWidth(mActivity));
intent.putExtra("outputY", DisplayUtils.getScreenWidth(mActivity));
intent.putExtra("scale", true);
intent.putExtra("scaleUpIfNeeded", true);//去黑边
intent.putExtra(MediaStore.EXTRA_OUTPUT, outputUri);
intent.putExtra("return-data", false);
intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString());
intent.putExtra("noFaceDetection", true); // no face detection
startActivityForResult(intent, PHOTORESOULT);
}
裁剪完成的回调
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == PHOTORESOULT) {//裁剪头像图片成功
if (data != null) {
mAdapter.addPhotoPath(outputUri +"");
}
}
}
因为我图片我是用glide加载的所以对于content://这种Uri转成String,是可以直接加载成功的。在使用裁剪需要注意的是,裁剪完成图库是有生成一张图片的,所以在使用完裁剪图片,如果不想让他显示在图库上,还是用*contentresolver.delete(uri,null,null)*删除掉。比如对图片压缩保存到自己的文件夹后。
在R,Q系统上,如果Uri使用起来不顺手的话,想转成真实的String路径。可以在onActivityResult()回调时把Uri转成String。但只针对多媒体文件。方法如下:
public static String getFilePath(Context context, Uri uri) {
final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
// DocumentProvider
if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
// ExternalStorageProvider
if (isExternalStorageDocument(uri)) {
final String docId = DocumentsContract.getDocumentId(uri);
final String[] split = docId.split(":");
final String type = split[0];
if ("primary".equalsIgnoreCase(type)) {
return Environment.getExternalStorageDirectory() + "/" + split[1];
}
// TODO handle non-primary volumes
}
// DownloadsProvider
else if (isDownloadsDocument(uri)) {
final String id = DocumentsContract.getDocumentId(uri);
final Uri contentUri = ContentUris.withAppendedId(
Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
return getDataColumn(context, contentUri, null, null);
}
// MediaProvider
else if (isMediaDocument(uri)) {
final String docId = DocumentsContract.getDocumentId(uri);
final String[] split = docId.split(":");
final 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;
}
final String selection = "_id=?";
final String[] selectionArgs = new String[] {
split[1]
};
return getDataColumn(context, contentUri, selection, selectionArgs);
}
}
// MediaStore (and general)
else if ("content".equalsIgnoreCase(uri.getScheme())) {
return getDataColumn(context, uri, null, null);
}
// File
else if ("file".equalsIgnoreCase(uri.getScheme())) {
return uri.getPath();
}
return null;
}
public static boolean isMediaDocument(Uri uri) {
return "com.android.providers.media.documents".equals(uri.getAuthority());
}
public static boolean isExternalStorageDocument(Uri uri) {
return "com.android.externalstorage.documents".equals(uri.getAuthority());
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is DownloadsProvider.
*/
public static boolean isDownloadsDocument(Uri uri) {
return "com.android.providers.downloads.documents".equals(uri.getAuthority());
}
以上是自己的适配兼容方式,不敢说最好,但至少可以解决问题。希望可以帮到需要的人。如有疑问欢迎评论。