问题描述
最近做APP开发时,需要从相册选取图片后在APP的PictureActivity
中显示,且每次都需要把获取到的图片URI缓存起来,用以下次进入页面时显示之前选择的图片。但这样偶尔会出现问题:用缓存的Uri并不能获取图片,并报错如下:
1
| java.lang.SecurityException: Permission Denial: opening provider com.android.providers.media.MediaDocumentsProvider from ProcessRecord{9ebe048 23462:cn.com.xxxx.xxxxx/u0a209} (pid=23462, uid=10209) requires android.permission.MANAGE_DOCUMENTS or android.permission.MANAGE_DOCUMENTS
|
PictureActivity
相关代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| private fun addPicture() { val intent = Intent() intent.type = "image/*" intent.action = Intent.ACTION_GET_CONTENT intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE) startActivityForResult(Intent.createChooser(intent, "Select Picture"), REQUEST_CODE_ADD_PICTURE) }
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) if(REQUEST_CODE_ADD_PICTURE == requestCode) { cacheUri = data?.data?:return Glide.with(this).load(data?.data).into(iv_picture) } }
|
分析和解决
Uri有生命周期
查了一下官网以及相关文章,说是用Uri获取图片的权限是暂时赋予的,有生命周期,即选择图片的当前Activity的生命周期。当Activity销毁后,之前的Uri就不能用来获取图片了,否则就会上面的报错。
上面的例子中,我的应用从相册应用选择图片后,相册会返回图片的Uri,这些Uri获取相册图片的权限是暂时的,即只能在接收这些Uri的PictureActivity
中使用,当PictureActivity
销毁后,Uri权限即失效,不能用来获取图片。
Glide有缓存
Glide默认会有缓存,因此才会导致问题不容易复现,只是偶尔出现。去掉磁盘缓存和内存缓存后,每次回退后再次进入PictureActivity
,就一定会复现这个问题。
1 2 3 4 5
| Glide.with(this) .applyDefaultRequestOptions(RequestOptions() .diskCacheStrategy(DiskCacheStrategy.NONE) .skipMemoryCache(true)) .load(item).into(iv_picture)
|
解决
知道问题的原因所在,就可以对症下药了。既然Uri的生命周期是短暂的,那么就不要缓存Uri了,缓存图片的绝对路径就可以了。
获取图片Uri后,将其转换为绝对路径形式,代码如下(参考了网上他人的代码,网上相关的代码有很多):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
| public static String getFilePathByUri(Context context, Uri uri) { String path = null; if (ContentResolver.SCHEME_FILE.equals(uri.getScheme())) { path = uri.getPath(); return path; } if (ContentResolver.SCHEME_CONTENT.equals(uri.getScheme()) && Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { Cursor cursor = context.getContentResolver().query(uri, new String[]{MediaStore.Images.Media.DATA}, null, null, null); if (cursor != null) { if (cursor.moveToFirst()) { int columnIndex = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA); if (columnIndex > -1) { path = cursor.getString(columnIndex); } } cursor.close(); } return path; } if (ContentResolver.SCHEME_CONTENT.equals(uri.getScheme()) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { if (DocumentsContract.isDocumentUri(context, uri)) { if (isExternalStorageDocument(uri)) { final String docId = DocumentsContract.getDocumentId(uri); final String[] split = docId.split(":"); final String type = split[0]; if ("primary".equalsIgnoreCase(type)) { path = Environment.getExternalStorageDirectory() + "/" + split[1]; return path; } } else if (isDownloadsDocument(uri)) { final String id = DocumentsContract.getDocumentId(uri); final Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), Long.valueOf(id)); path = getDataColumn(context, contentUri, null, null); return path; } 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]}; path = getDataColumn(context, contentUri, selection, selectionArgs); return path; } } else { String[] projection = { MediaStore.Images.Media.DATA }; Cursor cursor = null; try { cursor = context.getContentResolver().query(uri, projection, null, null, null); int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA); if (cursor.moveToFirst()) { return cursor.getString(column_index); } } catch (Exception e) { e.printStackTrace(); } finally { if (cursor != null) { cursor.close(); } } } } return null; }
|
参考
- Uri Access Lifetime: Shorter Than You Might Think
- FileProvider
- TakePhoto