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
相关产品推荐
相关产品推荐

