11

我需要在原生 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!

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

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

android:requestLegacyExternalStorage="true"

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

4

3 回答 3

9

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"

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

#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
于 2019-11-23T03:06:43.253 回答
7

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) 之后,我现在需要一两个星期的时间将其转换回(至少是读取文件部分)到常规文件访问......谢谢你,谷歌!讽刺地感谢您浪费的时间和精力,并真诚地感谢您至少部分理解了我们的观点!

这并不是“范围存储”噩梦的结束。谷歌正在 Android 11 中再次“改进”它(参见https://developer.android.com/preview/privacy/storage)——到目前为止,我们为使其正常工作所做的一切可能很快就会被扔进垃圾箱。

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

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

格雷格

于 2020-02-27T23:15:41.440 回答
0

嗨@gregko 我设法获得了真正的路径而不是 fd 路径并将其发送到我的本地活动,这减轻了一些负担

  String getFileNameThatICanUseInNativeCode(Uri uri) throws FileNotFoundException {
      mParcelFileDescriptor =
              getApplicationContext().getContentResolver().openFileDescriptor(uri, "r");
      if (mParcelFileDescriptor != null) {
        int fd = mParcelFileDescriptor.getFd();
        File file = new File("/proc/self/fd/" + fd);
        String path = null;
        try {
          if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            path = Os.readlink(file.getAbsolutePath()).toString();
          }
        } catch (ErrnoException e) {
          e.printStackTrace();
        }

        return path;
      }
      else{
        return null;
      }
  }

有了这段代码,我基本上做了一个完整的循环,然后我得到 /storage/CC12-FF343/Games/game.zip 而不是 /proc/....

于 2022-01-24T14:14:22.997 回答