Android: устранение неоднозначности путей к файлам

В моем приложении пользователи выбирают файлы. Внутри я храню информацию о файле, который я использую на основе пути к файлу. В следующий раз, когда этот файл используется, я делаю материал с сохраненной информацией. Проблема заключается в том, что я создаю файлы с помощью:

File file1 = new File(Environment.getExternalStorageDirectory() + "/test.txt");

И затем, на конкретном устройстве JB, file1.getCanonicalPath() дает: "/storage/emulated/0/test.txt".

Проблема в том, что когда другие приложения запускают мое приложение с файловым путем в Intent, пути, которые они отправляют, имеют тенденцию выглядеть так: "/mnt/sdcard/test.txt".

Есть ли разумная стратегия для устранения двусмысленности этих двух путей? Возможно, мне нужно создавать экземпляры файлов по-другому?

Изменить:

Проблема состоит в том, что два канонических пути для двух файлов не равны. Для ниже, cp1=="mnt/sdcard/test/txt" и cp2=="/storage/emulated/0/text/txt":

File file1 = new File("/mnt/sdcard/test.txt");
File file2 = new File("/storage/emulated/0/test.txt");

String cp1 = file1.getCanonicalPath();
String cp2 = file2.getCanonicalPath();

Ответ 1

Во-первых, единственный правильный способ получить внешний путь - использовать getExternalStorageDirectory и другие getExternalStorageXXX в Android.

Android сначала попытается решить две системные переменные:

String rawExternalStorage = System.getenv(ENV_EXTERNAL_STORAGE);
String rawEmulatedStorageTarget = System.getenv(ENV_EMULATED_STORAGE_TARGET);

а ENV_EXTERNAL_STORAGE = "EXTERNAL_STORAGE" и ENV_EMULATED_STORAGE_TARGET = "EMULATED_STORAGE_TARGET". Если задана переменная EMULATED_STORAGE_TARGET, это означает, что устройство имеет эмуляцию хранилища, то путь к хранилищу будет EMULATED_STORAGE_TARGET. (После Android 4.2 он поддерживает многопользовательское внешнее хранилище, тогда будет /0 или 0 после path) Но если он не установлен и EXTERNAL_STORAGE установлен, путь будет EXTERNAL_STORAGE. Если оба из них не установлены, по умолчанию будет /storage/sdcard0. Поэтому разные устройства могут содержать разные пути для внешнего хранилища.

Как Техническая информация по внешнему хранилищу, вы можете настроить хранилище устройства, настроив файл init.rc. Например, в по-умолчанию золотой рыбе:

export EXTERNAL_STORAGE /mnt/sdcard
mkdir /mnt/sdcard 0000 system system
symlink /mnt/sdcard /sdcard

Если вы используете getExternalStorageDirectory, вы получите /mnt/sdcard, но /sdcard является символической ссылкой на этот каталог.

Итак, в вашем случае init.rc может содержать:

export EMULATED_STORAGE_TARGET /storage/emulated
symlink /storage/emulated/0 /mnt/sdcard

Таким образом, они не являются двусмысленными, они на самом деле одинаковы.

Я думаю, что getCanonicalPath() может работать для подавляющего большинства ваших случаев использования.

Канонический путь является абсолютным и уникальным. Точный определение канонической формы зависит от системы. Сначала этот метод при необходимости преобразует этот путь в абсолютную форму, как если бы вызывая метод getAbsolutePath(), а затем сопоставляет его с его уникальным формы в зависимости от системы. Это обычно включает удаление избыточные имена, такие как "." и ".." из имени пути, разрешая символические ссылки (на платформах UNIX) и преобразование букв дисков в (на платформах Microsoft Windows).

Каждое имя пути, которое обозначает существующий файл или каталог, имеет уникальный канонической форме. Каждое имя пути, которое обозначает несуществующий файл или каталог также имеет уникальную каноническую форму. Каноническая форма имя несуществующего файла или каталога может отличаться от каноническая форма одного и того же пути после того, как файл или каталог создано. Аналогично, каноническая форма пути к существующему файл или каталог могут отличаться от канонической формы того же имя пути после удаления файла или каталога.

Ответ 2

Вероятно, для этого нет простого решения. Проблема в том, что, по-видимому, в файловой системе есть две точки монтирования, которые фактически указывают на одно и то же местоположение. File.getCanonicalPath() может разрешать только символические ссылки, но не разные точки монтирования.

Например, на моем Nexus 4 этот код:

File file1 = new File(Environment.getExternalStorageDirectory() + "/Android");
System.out.println("file 1: " + file1.getCanonicalPath());
File file2 = new File("/sdcard/Android");
System.out.println("file 2: " + file2.getCanonicalPath());

печатает

file 1: /storage/emulated/0/Android
file 2: /storage/emulated/legacy/Android

Я использовал этот код для exec "mount" и распечатал вывод:

Process exec = Runtime.getRuntime().exec("mount");
InputStream in = exec.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(in));
while (true) {
    String line = br.readLine();
    if (line == null)
        break;
    System.out.println(line);
}
in.close();

Соответствующий вывод:

/dev/fuse /storage/emulated/0 fuse rw,nosuid,nodev,relatime,user_id=1023,group_id=1023,default_permissions,allow_other 0 0
/dev/fuse /storage/emulated/legacy fuse rw,nosuid,nodev,relatime,user_id=1023,group_id=1023,default_permissions,allow_other 0 0

Ответ 3

Посмотрите на ответ здесь. Он также использует канонический путь, но несколько иным образом, который может сработать для вас.

Ответ 4

В новых версиях Android SD-хранилище доступно из многих путей, например:

/storage/emulated/0
/storage/emulated/legacy (root account most of the time)
/sdcard
/data/media

Если вы проверяете, на каком устройстве расположены эти пути, некоторые из них находятся на разных устройствах (из-за "виртуальной" файловой системы плавкого предохранителя), поэтому получение их канонического пути не приводит к одному и тому же пути к файлу, хотя они фактически являются те же файлы.

Кроме того, похоже, что на Marshmallow все ухудшается, и даже путь к файлу в /sys (полный перенаправления/ссылки) не сообщается должным образом, а getCanonicalPath() возвращает исходный путь вместо фактического канонического пути.

В то время как ls -l (или readlink) на указанном пути к файлу покажет фактический канонический путь, API больше не работает. К сожалению, работа с readlink или ls -l очень медленная (в среднем 130 мс, когда оболочка уже запущена) по сравнению с уже медленным, но гораздо более быстрым getCanonicalPath(), жаль.

Заключение, getCanonicalPath сломан и всегда был сломан.