Android NDK+OpenGL ES3.0使用stb_image访问本地存储权限异常
解决Android NDK中stb_image加载外部存储图片的权限问题
1. 补全权限申请流程
- Android 6.0(API 23)及以上,危险权限必须动态申请,哪怕
AndroidManifest.xml里配置了READ_EXTERNAL_STORAGE、WRITE_EXTERNAL_STORAGE,也要在Java层主动请求用户授权:String[] permissions = {Manifest.permission.READ_EXTERNAL_STORAGE}; if (ContextCompat.checkSelfPermission(this, permissions[0]) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, permissions, 1001); } - Android 11(API 30)及以上,
MANAGE_EXTERNAL_STORAGE是特殊权限,无法通过普通动态申请流程获取,必须引导用户到应用设置页手动开启:
注意:该权限属于高敏感权限,Google Play会限制滥用此权限的应用上架,仅在必要场景使用。Intent intent = new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION); Uri uri = Uri.fromParts("package", getPackageName(), null); intent.setData(uri); startActivity(intent);
2. 适配Android 10+分区存储机制
Android 10(API 29)默认开启分区存储,直接通过绝对路径访问外部存储文件会被拦截,这是导致stbi_load调用失败的核心原因之一,可通过以下两种方案解决:
- 方案一:通过MediaStore获取文件描述符(FD)传给NDK
先在Java层通过MediaStore查询目标图片的Content URI,打开文件描述符后传给NDK,再用stb_image从FD加载:
Java层示例:
NDK层示例:ContentResolver resolver = getContentResolver(); Uri imageUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; String[] projection = {MediaStore.Images.Media._ID}; String selection = MediaStore.Images.Media.DISPLAY_NAME + "=?"; String[] selectionArgs = {"frame.png"}; Cursor cursor = resolver.query(imageUri, projection, selection, selectionArgs, null); if (cursor != null && cursor.moveToFirst()) { long id = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Images.Media._ID)); Uri targetUri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id); ParcelFileDescriptor pfd = resolver.openFileDescriptor(targetUri, "r"); if (pfd != null) { int fd = pfd.getFd(); // 调用NDK方法传入文件描述符 loadImageFromFd(fd); pfd.close(); } cursor.close(); }#include <unistd.h> #include "stb_image.h" void loadImageFromFd(int fd) { FILE* file = fdopen(fd, "rb"); if (!file) { // 错误处理逻辑 return; } int width, height, channels; unsigned char* imgData = stbi_load_from_file(file, &width, &height, &channels, 0); // 处理图片数据 stbi_image_free(imgData); fclose(file); close(fd); } - 方案二:临时关闭分区存储(仅兼容Android 10)
在AndroidManifest.xml的<application>标签中添加属性,仅对Android 10生效,Android 11及以上仍需特殊权限:<application ... android:requestLegacyExternalStorage="true"> ... </application>
3. 验证文件路径有效性
- 避免硬编码路径,用系统API获取标准存储目录:
File picDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES); String imgPath = new File(picDir, "frame.png").getAbsolutePath(); - 在Java层先检查文件是否存在:
if (new File(imgPath).exists()) { // 执行NDK加载逻辑 } else { // 文件不存在处理 }
4. 错误日志精准排查
如果仍报错,提取完整错误日志定位问题:
MediaProvider: Permission denied: reading com.android.providers.media.MediaProvider uri content://media/external/images/media/xxx from pid=xxx, uid=xxx
- 若日志显示权限拒绝:检查权限申请是否成功,特殊权限是否已手动开启;
- 若日志显示
No such file or directory:确认文件路径是否正确,文件是否真实存在。
内容的提问来源于stack exchange,提问作者Ankit Kumar
相关产品推荐
相关产品推荐

