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

Android SDK32 Scoped Storage下,NDK读取非自有文件失败求助

问题原因分析

在Android 12(SDK 32)的Scoped Storage机制下,直接通过绝对路径访问共享存储(如Download目录)中非本应用创建的文件是被禁止的,这就是你调用open()返回-1的核心原因。Scoped Storage的设计目的就是限制应用对外部存储的直接路径访问,强制通过系统提供的API(MediaStore或存储访问框架SAF)来操作共享文件。

正确实现方案

要读取Download目录中的第三方文件,需先在Java层获取文件的合法URI,将其转换为文件描述符(FileDescriptor)后传递给NDK层,再通过文件描述符完成读取操作。具体步骤如下:

1. 申请必要权限

在AndroidManifest.xml中添加权限:

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

针对SDK 32,还需要在运行时动态申请该权限,确保用户授权。

2. Java层获取文件URI并传递文件描述符给JNI

可以通过两种方式获取URI:

方式一:通过存储访问框架(SAF)让用户选择文件

// 在Activity中触发文件选择
public void pickFileFromDownloads() {
    Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
    intent.setType("text/*");
    intent.addCategory(Intent.CATEGORY_OPENABLE);
    startActivityForResult(intent, 100);
}

// 处理选择结果
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (requestCode == 100 && resultCode == RESULT_OK && data != null) {
        Uri uri = data.getData();
        try {
            // 获取文件描述符
            ParcelFileDescriptor pfd = getContentResolver().openFileDescriptor(uri, "r");
            if (pfd != null) {
                // 调用JNI方法,传入文件描述符的fd值
                readTxtFileFromJNI(pfd.getFd());
                pfd.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

// 声明JNI方法
private native void readTxtFileFromJNI(int fd);

方式二:通过MediaStore查询Download目录中的目标文件

如果已知文件名,可以通过MediaStore查询获取URI:

public Uri getFileUriFromDownloads(String fileName) {
    String[] projection = {MediaStore.Downloads._ID};
    String selection = MediaStore.Downloads.DISPLAY_NAME + "=?";
    String[] selectionArgs = {fileName};
    Cursor cursor = getContentResolver().query(
            MediaStore.Downloads.EXTERNAL_CONTENT_URI,
            projection,
            selection,
            selectionArgs,
            null
    );
    if (cursor != null && cursor.moveToFirst()) {
        long id = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Downloads._ID));
        cursor.close();
        return ContentUris.withAppendedId(MediaStore.Downloads.EXTERNAL_CONTENT_URI, id);
    }
    return null;
}

// 使用示例
Uri fileUri = getFileUriFromDownloads("token.txt");
if (fileUri != null) {
    try {
        ParcelFileDescriptor pfd = getContentResolver().openFileDescriptor(fileUri, "r");
        if (pfd != null) {
            readTxtFileFromJNI(pfd.getFd());
            pfd.close();
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}

3. NDK层通过文件描述符读取文件

修改JNI代码,直接使用传入的文件描述符进行读取:

extern "C"
JNIEXPORT void JNICALL
Java_com_example_testing2_MainActivity_readTxtFileFromJNI(JNIEnv *env, jobject thiz, int fd) {
    char buffer[100] = {};
    int count = read(fd, buffer, 99);
    __android_log_print(ANDROID_LOG_INFO, "Tag", "Read count:%d-->Content:%s\n", count, buffer);
    // 注意:这里不要close(fd),因为文件描述符是Java层打开的,由Java层负责关闭
}
关键注意事项
  • 不要在NDK层关闭传入的文件描述符,否则会导致Java层后续操作出错,文件描述符的生命周期由Java层的ParcelFileDescriptor管理。
  • 对于API 33及以上版本,需替换READ_EXTERNAL_STORAGE为对应媒体类型权限(如文本文件仍需依赖SAF或MediaStore访问)。
  • 如果需要长期访问该文件,可以通过getContentResolver().takePersistableUriPermission()获取持久化权限,避免每次打开都需要用户授权。

内容的提问来源于stack exchange,提问作者Rohan Pande

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.11 16:45:31