You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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是特殊权限,无法通过普通动态申请流程获取,必须引导用户到应用设置页手动开启:
    Intent intent = new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION);
    Uri uri = Uri.fromParts("package", getPackageName(), null);
    intent.setData(uri);
    startActivity(intent);
    
    注意:该权限属于高敏感权限,Google Play会限制滥用此权限的应用上架,仅在必要场景使用。

2. 适配Android 10+分区存储机制

Android 10(API 29)默认开启分区存储,直接通过绝对路径访问外部存储文件会被拦截,这是导致stbi_load调用失败的核心原因之一,可通过以下两种方案解决:

  • 方案一:通过MediaStore获取文件描述符(FD)传给NDK
    先在Java层通过MediaStore查询目标图片的Content URI,打开文件描述符后传给NDK,再用stb_image从FD加载:
    Java层示例:
    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();
    }
    
    NDK层示例:
    #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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 18:42:01