API 29 & 30范围存储限制下图片加载失败问题求助
这问题我太熟了——API 29+强制推行的范围存储(Scoped Storage)确实把很多依赖文件路径的老代码都干翻了,你遇到的正是典型的「路径失效」问题,我帮你一步步解决:
核心原因:范围存储下文件路径不再具备访问权限
在API 29及以上,公共共享存储(比如Pictures文件夹)里的文件,APP不能再通过直接的文件路径(比如/storage/emulated/0/Pictures/xxx.jpg)访问——即使是你自己APP存进去的也不行。降SDK到28能正常工作,是因为API 28及以下没有强制范围存储,APP还拥有对外部存储的全访问权限。
而你试过的File Provider在这里没用,因为它是用来共享APP私有目录下的文件给其他APP的,公共Pictures文件夹不属于你的APP私有目录,所以File Provider帮不上忙。
解决方案:改用MediaStore的Content URI加载图片
这是适配范围存储的标准做法,分两种场景处理:
1. 新保存的图片:直接存储Content URI到数据库
以后再通过MediaStore保存图片时,不要自己拼接文件路径,而是把MediaStore返回的Content URI存到数据库里:
// 保存图片到MediaStore的示例代码 ContentValues values = new ContentValues(); values.put(MediaStore.Images.Media.DISPLAY_NAME, "my_image.jpg"); values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg"); values.put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_PICTURES); Uri contentUri = getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values); // 把这个contentUri转换成字符串存到数据库,比如content://media/external/images/media/1234 db.insert("images", null, new ContentValues().put("uri", contentUri.toString()));
之后加载时,直接用这个URI给Glide:
String savedUriStr = db.query(...).getString(...); Uri contentUri = Uri.parse(savedUriStr); Glide.with(this).load(contentUri).into(imageView);
2. 已存储的旧路径:把路径转换成Content URI
如果数据库里已经存了一堆旧的文件路径,你需要把它们转换成对应的Content URI才能继续访问:
// 根据文件路径查询对应的Content URI public Uri getContentUriFromFilePath(Context context, String filePath) { Uri mediaUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; String[] projection = {MediaStore.Images.Media._ID}; String selection = MediaStore.Images.Media.DATA + " = ?"; String[] selectionArgs = {filePath}; Cursor cursor = context.getContentResolver().query( mediaUri, projection, selection, selectionArgs, null); if (cursor != null && cursor.moveToFirst()) { int idColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media._ID); long mediaId = cursor.getLong(idColumn); cursor.close(); return ContentUris.withAppendedId(mediaUri, mediaId); } if (cursor != null) cursor.close(); return null; }
调用这个方法拿到URI后,再传给Glide加载即可。
过渡方案(仅适用于API 29)
如果你暂时不想大规模修改代码,在API 29(Android 10)上可以在AndroidManifest.xml里添加以下属性临时绕过范围存储:
<application ... android:requestLegacyExternalStorage="true">
但注意:这个属性在API 30及以上完全失效,Android 11+必须用Content URI的方式,所以这只是临时救急,最终还是要彻底适配。
最佳实践提醒
- 永远用Content URI而非文件路径访问共享存储里的媒体文件
- 避免申请
MANAGE_EXTERNAL_STORAGE权限(这个权限需要Google Play审核,且仅适用于特殊场景,比如文件管理器类APP)
内容的提问来源于stack exchange,提问作者David Nachmani

