Android - 使用 Google Scoped Storage API 访问本机 C/C++ 代码中的文件

gre*_*gko 8 java-native-interface android scoped-storage

我需要在原生 C/C++ 代码中的 Android 应用程序中按文件名打开文件。本机代码是我不想修改的第 3 方库,但它们通常需要文件名作为读/写文件的参数。使用 Google 的“范围存储”API 并在 Android 10 或更高版本中禁用对文件的本机访问,这是一个真正的问题。

一个众所周知的解决方案是获取文件描述符并使用“proc/self/fd/FD_NUMER”技巧,例如:

       ParcelFileDescriptor mParcelFileDescriptor = null;

       String getFileNameThatICanUseInNativeCode(Context context, DocumentFile doc) { 
           try {
                Uri uri = doc.getUri();
                mParcelFileDescriptor =
                        context.getContentResolver().openFileDescriptor(uri, "r");
                if (mParcelFileDescriptor != null) {
                    int fd = mParcelFileDescriptor.getFd();
                    return "/proc/self/fd/" + fd;
                }
            }
            catch (FileNotFoundException fne) {
                return "";
            }
        }

        // Don't forget to close mParcelFileDescriptor when done!
Run Code Online (Sandbox Code Playgroud)

将此传递给本机 C/C++ 代码有效,但前提是文件位于手机主存储中。如果用户尝试打开插入手机插槽的外部 SD 卡上的文件,则不起作用 - 以这种方式打开的文件没有读取权限。我只能获取文件描述符 int 号并使用 fdopen(fd)。但这将需要修改 3rd 方库(开源或许可)的源代码,并且每次更新这些库的原始源时都会令人头疼。

有没有更好的解决这个问题的方法?不,我不想听到添加的解决方案

android:requestLegacyExternalStorage="true"
Run Code Online (Sandbox Code Playgroud)

到 AndroidManifest.xml 应用程序部分 - 谷歌威胁要在 2020 年的下一个 Android 版本中禁用它,因此需要一个永久的解决方案。另一个简单但愚蠢的解决方案是将用户尝试打开的整个(可能很大)文件复制到私有应用程序目录中。愚蠢而无用...

gre*_*gko 8

2020 年 5 月 19 日更新:刚刚发现:READ_EXTERNAL_STORAGE 权限允许您在 Android 11 上运行并面向 API 30(或更高版本)时读取文件,但不能列出目录内容。我将它作为错误提交,并得到了“这是按预期工作”的回复(https://issuetracker.google.com/issues/156660903)。如果有人关心,请在那里以及他们的“调查”中发表评论:https : //google.qualtrics.com/jfe/form/SV_9HOzzyeCIEw0ij3?Source=scoped-storage我不知道如何继续在 Android 上开发应用程序这些限制。

2020 年 5 月 17 日更新:Google 终于承认并允许在 Android 11 及更高版本中使用 READ_EXTERNAL_STORAGE 权限。在花了几个月将我的应用程序转换为存储访问框架 (SAF) 之后,我现在需要一两个星期的时间将其转换回常规文件访问,至少是读取文件部分……谢谢谷歌!讽刺地感谢您损失的时间和精力,真诚地感谢您至少部分理解了我们的观点!

所以,我之前列出的答案不再需要了,松了一口气!

在 Android “Scoped Storage” BS 上浪费了我生命中的又一天美好时光之后,我找到了一个有效的解决方案,无需修改 3rd 方本机库的源代码,前提是拥有这些库的源代码并且可以构建它们。

我(非常不满意)的解决方案是:在 C/C++ 编译命令中添加以下选项:

-include "[some/path/]idiocy_fopen_fd.h"
Run Code Online (Sandbox Code Playgroud)

并且 idiocy_fopen_fd.h 如下所示,如您所见,对常规 fopen() 的每次调用都被替换为 idiocy_fopen_fd() 代码,该代码检查文件名是否以“/proc/self/fd/”开头,如果因此,提取文件描述符编号并调用 fdopen() 而不是 fopen() ...如果有人有更好的解决方案,最好在您没有第 3 方库的源代码时也能工作,请分享。

#ifndef fopen_fd

#include <stdio.h>
#include <string.h>
#include <unistd.h> // for dup()

#ifdef __cplusplus
extern "C" {
#endif

inline FILE* idiocy_fopen_fd(const char* fname, const char * mode) {
  if (strstr(fname, "/proc/self/fd/") == fname) {
    int fd = atoi(fname + 14);
    if (fd != 0) {
      // Why dup(fd) below: if we called fdopen() on the
      // original fd value, and the native code closes
      // and tries re-open that file, the second fdopen(fd)
      // would fail, return NULL - after closing the
      // original fd received from Android, it's no longer valid.
      FILE *fp = fdopen(dup(fd), mode);
      // Why rewind(fp): if the native code closes and 
      // opens again the file, the file read/write position
      // would not change, because with dup(fd) it's still
      // the same file...
      rewind(fp);
      return fp;
    }
  }
  return fopen(fname, mode);
}
// Note that the above leaves the original file descriptor
// opened when finished - close parcelFileDescriptor in
// Java/Kotlin when your native code returns!

#ifdef __cplusplus
}
#endif

#define fopen idiocy_fopen_fd

#endif
Run Code Online (Sandbox Code Playgroud)


gre*_*gko 6

2020 年 5 月 19 日更新:刚刚发现:READ_EXTERNAL_STORAGE 权限允许您在 Android 11 上运行并以 API 30(或更高版本)为目标时读取文件,但不能列出目录内容。我将它作为错误提交,并得到了“这是按预期工作”的回复(https://issuetracker.google.com/issues/156660903)。如果有人关心,请在那里以及在他们的“调查”中发表评论:https : //google.qualtrics.com/jfe/form/SV_9HOzzyeCIEw0ij3?Source=scoped-storage我不知道如何继续在 Android 上开发应用程序这些限制。

o 考虑将应用切换到 SAF 的任何人,我都会从 Google Play 开发者控制台发布我的应用的月平均收视率图表。2020 年 1 月,我使用 SAF 而不是常规文件访问发布了我的应用程序的第一个版本,请参见附图。随着越来越多的用户更新,收视率下降,很多评论说我毁了一个好应用。即使现在该应用程序的活跃安装量也超过了 100 万。

在此处输入图片说明

2020 年 5 月 17 日更新: Google 终于承认并允许在 Android 11 及更高版本中使用 READ_EXTERNAL_STORAGE 权限。在花了几个月将我的应用程序转换为存储访问框架 (SAF) 之后,我现在需要一两个星期的时间将其转换回常规文件访问,至少是读取文件部分……谢谢谷歌!讽刺地感谢您损失的时间和精力,真诚地感谢您至少部分理解了我们的观点!

这并不是“范围存储”噩梦的结束。Google 正在 Android 11 中再次“改进”它(请参阅https://developer.android.com/preview/privacy/storage) - 我们迄今为止为使其工作所做的一切可能很快就会被扔进垃圾桶。

请考虑评论并支持我的请求,即不限制对 Android 11 中下载目录的 Scoped Storage 访问,和/或改进系统“文件”界面:https : //issuetracker.google.com/issues/151759806

我正在认真考虑放弃Android平台的进一步开发……在我看来,傲慢、控制欲强的“父母”接管了Android系统的进化,他们认为我们其他人都是小孩子,他们需要安装安全门到处都是我们的房子,严重限制了我们的行动自由。好难过。

格雷格