Как получить доступ ко всем SD-картам, используя новый Lollipop API?

Когда вы объявляете ссылочную переменную (т. е. объект), вы действительно создаете указатель на объект. Рассмотрим следующий код, в котором вы объявляете переменную примитивного типа int:

int x;
x = 10;

В этом примере переменная x является int, и Java инициализирует ее для 0. Когда вы назначаете его 10 во второй строке, ваше значение 10 записывается в ячейку памяти, на которую указывает x.

Но когда вы пытаетесь объявить ссылочный тип, произойдет что-то другое. Возьмите следующий код:

Integer num;
num = new Integer(10);

Первая строка объявляет переменную с именем num, но она не содержит примитивного значения. Вместо этого он содержит указатель (потому что тип Integer является ссылочным типом). Поскольку вы еще не указали, что указать на Java, он устанавливает значение null, что означает «Я ничего не указываю».

Во второй строке ключевое слово new используется для создания экземпляра (или создания ) объекту типа Integer и переменной указателя num присваивается этот объект. Теперь вы можете ссылаться на объект, используя оператор разыменования . (точка).

Exception, о котором вы просили, возникает, когда вы объявляете переменную, но не создавали объект. Если вы попытаетесь разыменовать num. Перед созданием объекта вы получите NullPointerException. В самых тривиальных случаях компилятор поймает проблему и сообщит вам, что «num не может быть инициализирован», но иногда вы пишете код, который непосредственно не создает объект.

Например, вы можете имеют следующий метод:

public void doSomething(SomeObject obj) {
   //do something to obj
}

В этом случае вы не создаете объект obj, скорее предполагая, что он был создан до вызова метода doSomething. К сожалению, этот метод можно вызвать следующим образом:

doSomething(null);

В этом случае obj имеет значение null. Если метод предназначен для того, чтобы что-то сделать для переданного объекта, целесообразно бросить NullPointerException, потому что это ошибка программиста, и программисту понадобится эта информация для целей отладки.

Альтернативно, там могут быть случаи, когда цель метода заключается не только в том, чтобы работать с переданным в объекте, и поэтому нулевой параметр может быть приемлемым. В этом случае вам нужно будет проверить нулевой параметр и вести себя по-другому. Вы также должны объяснить это в документации. Например, doSomething может быть записано как:

/**
  * @param obj An optional foo for ____. May be null, in which case 
  *  the result will be ____.
  */
public void doSomething(SomeObject obj) {
    if(obj != null) {
       //do something
    } else {
       //do something else
    }
}

Наконец, Как определить исключение & amp; причина использования Трассировки стека

25
задан Community 23 May 2017 в 12:34
поделиться

1 ответ

Можно ли проверить, доступны ли текущие SD-карты, а какие нет, и как-то попросить пользователя получить разрешение на них?

Есть 3 части, чтобы это: обнаружение, какие карты есть, проверка, установлена ​​ли карта, и запрос доступа.

Если производители устройств - хорошие парни, вы можете получить список «внешнего» хранилища, вызвав getExternalFiles с аргументом null (см. Соответствующий javadoc).

1142 Они не могут быть хорошими парнями. И не у всех есть самая новая, самая горячая, без ошибок версия ОС с регулярными обновлениями. Поэтому могут быть некоторые каталоги, которые там не перечислены (например, USB-накопитель OTG и т. Д.). В случае сомнений вы можете получить полный список монтирований ОС из файла /proc/self/mounts. Этот файл является частью ядра Linux, его формат задокументирован здесь . Вы можете использовать содержимое /proc/self/mounts в качестве резервной копии: проанализировать его, найти пригодные для использования файловые системы (fat, ext3, ext4 и т. Д.) И удалить дубликаты с выводом getExternalFilesDirs. Предложите оставшиеся опции пользователям под некоторым ненавязчивым именем, что-то вроде «разных каталогов». Это даст вам все возможное внешнее, внутреннее, любое хранилище.


РЕДАКТИРОВАТЬ : после того, как я дал совет выше, я наконец попытался следовать ему сам. До сих пор все работало нормально, но имейте в виду, что вместо /proc/self/mounts вам лучше разбирать /pros/self/mountinfo (первый по-прежнему доступен в современном Linux, но позже это лучшая, более надежная замена). Также убедитесь, что учли проблемы атомарности , когда делали предположения на основе содержимого списка монтирований.


Вы можете наивно проверить, является ли каталог доступным для чтения / записи, вызывая canRead и canWrite . Если это удастся, нет смысла делать дополнительную работу. Если это не так, у вас либо есть постоянное разрешение Uri, либо нет.

«Получение разрешения» - ужасная часть. AFAIK, нет никакого способа сделать это в рамках инфраструктуры SAF. Intent.ACTION_PICK звучит как что-то, что может работать (поскольку он принимает Uri, из которого можно выбирать), но это не так. Возможно, это можно считать ошибкой, о чем следует сообщать трекеру ошибок Android как таковому.

Учитывая файл / путь к файлу, можно ли просто запросить разрешение на доступ к нему (и проверить, было ли оно передано ранее) или его корневой путь?

Вот для чего ACTION_PICK. Опять же, сборщик SAF не поддерживает ACTION_PICK из коробки. Сторонние файловые менеджеры могут, но очень немногие из них фактически предоставят вам реальный доступ. Вы также можете сообщить об этом как об ошибке, если захотите.


РЕДАКТИРОВАТЬ : этот ответ был написан до выхода Android Nougat. Что касается API 24, все еще невозможно запросить детальный доступ к определенному каталогу, но по крайней мере вы можете динамически запрашивать доступ ко всему тому: определите том , содержащий файл, и запросите доступ используя либо getAccessIntent с аргументом null (для вторичных томов), либо запрашивая разрешение WRITE_EXTERNAL_STORAGE (для основного тома).


Существует ли официальный способ преобразования между документами DocumentFile и реальными путями? Я нашел этот ответ, но в моем случае он разбился, к тому же он выглядит хакерским.

Нет. Никогда. Вы навсегда останетесь подчиненными прихотям создателей Storage Access Framework! злой смех

На самом деле, есть гораздо более простой способ: просто откройте Uri и проверьте расположение файловой системы созданного дескриптора (для простоты, версия только для Lollipop):

public String getFilesystemPath(Context context, Uri uri) {
  ContentResolver res = context.getContentResolver();

  String resolved;
  try (ParcelFileDescriptor fd = res.openFileDescriptor(someSafUri, "r")) {
    final File procfsFdFile = new File("/proc/self/fd/" + fd.getFd());

    resolved = Os.readlink(procfsFdFile.getAbsolutePath());

    if (TextUtils.isEmpty(resolved)
          || resolved.charAt(0) != '/'
          || resolved.startsWith("/proc/")
          || resolved.startsWith("/fd/"))
    return null;
  } catch (Exception errnoe) {
    return null;
  }
}

Если описанный выше метод возвращает местоположение, вам все еще нужен доступ, чтобы использовать его с File. Если он не возвращает местоположение, рассматриваемый Uri не ссылается на файл (даже временный). Это может быть сетевой поток, канал Unix, что угодно. Вы можете получить версию метода выше для более старых версий Android из этого ответа . Он работает с любым Uri, любым ContentProvider - не только SAF - до тех пор, пока Uri можно открыть с помощью openFileDescriptor (например, это из Intent.CATEGORY_OPENABLE).

Обратите внимание, что вышеуказанный подход можно считать официальным, если вы рассматриваете какую-либо часть официального API Linux как таковую. Многие программы для Linux используют его, и я также видел, что он используется в некотором коде AOSP (например, в тестах Launcher3).


РЕДАКТИРОВАТЬ : Android Nougat представил ряд изменений безопасности , прежде всего изменения в разрешениях частных каталогов приложений. Это означает, что, начиная с API 24, приведенный выше фрагмент кода будет всегда завершаться с ошибкой, когда Uri ссылается на файл в частном каталоге приложения. Это так, как задумано: от вас больше не ожидается, что вы узнаете этот путь вообще . Даже если вы каким-либо образом определите путь к файловой системе, вы не сможете получить доступ к файлам по этому пути. Даже если другое приложение сотрудничает с вами и изменяет разрешение файла на доступное для чтения, вы все равно не сможете получить к нему доступ. Это потому, что Linux не разрешает доступ к файлам, если у вас нет доступа для поиска к одному из каталогов в пути . Таким образом, получение файлового дескриптора от ContentProvider является единственным способом доступа к ним.


Можно ли использовать обычный API-интерфейс файла вместо API-интерфейса DocumentFile после получения разрешения?

Вы не можете. По крайней мере, на нуге. Обычный файловый API отправляется в ядро ​​Linux за разрешениями. Согласно ядру Linux, ваша внешняя SD-карта имеет ограниченные разрешения, которые не позволяют вашему приложению использовать ее. Разрешения, предоставляемые Storage Access Framework, управляются SAF (IIRC хранится в некоторых XML-файлах ), и ядро ​​ничего о них не знает. Вы должны использовать промежуточную сторону (Storage Access Framework), чтобы получить доступ к внешнему хранилищу. Обратите внимание, что ядро ​​Linux имеет собственный механизм управления доступом к поддеревам каталогов (он называется bind-mounts ), но создатели Storage Access Framework либо не знают об этом, либо не хотят его использовать. .

Вы можете получить некоторую степень доступа (почти все, что можно сделать с помощью File) с помощью файлового дескриптора, созданного из Uri. Я рекомендую вам прочитать этот ответ , он может содержать некоторую полезную информацию об использовании файловых дескрипторов в целом и по отношению к Android.

12
ответ дан Community 23 May 2017 в 12:34
поделиться
Другие вопросы по тегам:

Похожие вопросы: