Android - доступ к файлам в собственном коде C/C++ с помощью Google Scoped Storage API
Мне нужно открывать файлы по имени файла в приложениях Android в собственном коде C/C++. Собственный код - это сторонние библиотеки, которые я бы предпочел не изменять, но они часто требуют имени файла в качестве аргумента для чтения / записи файлов. С API "ограниченного хранилища" Google и отключением собственного доступа к файлам в 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-карте, вставленной в слот телефона, это не сработает - для файла, открытого таким образом, нет разрешения на чтение. Я могу только взять номер дескриптора файла и использовать fdopen (fd). Но это потребует изменения исходного кода сторонних библиотек (с открытым исходным кодом или лицензионных) и большой головной боли при каждом обновлении исходного кода этих библиотек.
Есть ли лучшее решение этой проблемы? И нет, я не хочу слышать решение с добавлением
android:requestLegacyExternalStorage="true"
в раздел приложения AndroidManifest.xml - Google угрожает отключить это в следующей версии Android в 2020 году, поэтому необходимо постоянное решение. Еще одно простое, но глупое решение - скопировать весь (возможно, огромный) файл, который пользователь пытается открыть, в частный каталог приложения. Тупой и бесполезный...
3 ответа
Обновление от 19 мая 2020 г.: только что обнаружено: разрешение 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 со всеми эти ограничения.
Обновление 17 мая 2020 г.: Google наконец уступил и разрешает READ_EXTERNAL_STORAGE в Android 11 и более поздних версиях. Потратив месяцы на преобразование моих приложений в Storage Access Framework (SAF), я теперь трачу неделю или две, чтобы преобразовать его обратно, по крайней мере, в части чтения файлов, в обычный доступ к файлам... Спасибо, Google! Саркастически благодарим вас за потерянное время и усилия и искренне благодарим вас за хотя бы частичное понимание нашей точки зрения!
Итак, мой предыдущий ответ, указанный ниже, больше не понадобится, вздох облегчения!
Потратив еще один хороший день своей жизни на BS Android "Scoped Storage", я нашел решение, которое работает без изменения источника сторонних собственных библиотек, при условии, что у кого-то есть исходный код этих библиотек и он может их создавать.
Мое (очень неудовлетворительное) решение: добавить следующую опцию в команду компиляции C/C++:
-include "[some/path/]idiocy_fopen_fd.h"
а idiocy_fopen_fd.h выглядит следующим образом, как вы можете видеть, каждый вызов обычного fopen() заменяется кодом idiocy_fopen_fd(), который проверяет, начинается ли имя файла с "/proc/self/fd/", и если поэтому извлекает номер дескриптора файла и вызывает fdopen() вместо fopen()... Если у кого-то есть лучшее решение, желательно, которое будет работать также, когда у вас нет исходного кода сторонних библиотек, поделитесь.
#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
Обновление от 19 мая 2020 года: только что обнаружено: разрешение 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 со всеми эти ограничения.
Всем, кто собирается переключить свое приложение на SAF, я публикую диаграмму среднемесячных рейтингов моего приложения из консоли разработчика Google Play. В январе 2020 года я выпустил первую версию своего приложения с использованием SAF вместо обычного доступа к файлам, см. Прикрепленное изображение. По мере того, как все больше и больше пользователей обновляют, рейтинги падают, много комментариев, что я уничтожил хорошее приложение. У приложения уже более 1 миллиона активных установок.
Обновление 17 мая 2020 г.: Google наконец уступил и разрешает READ_EXTERNAL_STORAGE в Android 11 и более поздних версиях. Потратив месяцы на преобразование моих приложений в Storage Access Framework (SAF), я теперь трачу неделю или две, чтобы преобразовать его обратно, по крайней мере, в части чтения файлов, в обычный доступ к файлам... Спасибо, Google! Саркастически благодарим вас за потерянное время и усилия и искренне благодарим вас за хотя бы частичное понимание нашей точки зрения!
Это еще не конец кошмара "ограниченного хранилища". Google снова "улучшает" его в Android 11 (см. https://developer.android.com/preview/privacy/storage) - все, что мы сделали до сих пор, чтобы заставить его работать, скоро может быть выброшено в мусор.
Пожалуйста, рассмотрите возможность прокомментировать и поддержать мой запрос не ограничивать доступ к хранилищу с ограниченным объемом доступа к каталогу загрузки в Android 11 и / или улучшить интерфейс системных файлов: https://issuetracker.google.com/issues/151759806
Я серьезно подумываю отказаться от дальнейшей разработки платформы Android... Мне кажется, что надменные, контролирующие "родители" взяли на себя эволюцию системы Android, которые думают, что все мы - маленькие дети, и им нужно установить ворота безопасности повсюду в наших домах, серьезно ограничивая нашу свободу передвижения. Так грустно.
Грег
Привет @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/....