[Из песочницы] Получение пути к карте памяти SD Card на Android
Разрабатывая приложение для проведения соревнований, я столкнулся с проблемой хранения базы данных. Проблема состояла в том, как мне определить внешнюю карту памяти. В целом поиск в сети точного ответа не дал. Поэтому, объединив все найденные результаты, я собрал свой класс. Если кому интересно, смотрим под катом.Итак, начнем с теории.ТерминологияГугл нам говорит, что есть следующие понятия: Внутренняя (internal) память — это часть встроенной в телефон карты памяти. При ее использовании по умолчанию папка приложения защищена от доступа других приложений (Using the Internal Storage). Внешняя (external) память — это общее «внешнее хранилище», т.е. это может быть как часть встроенной памяти, так и удаляемое устройство. Обычно это часть встроенной памяти, как удаляемое устройство я видел в последний раз на андройде 2.2, где встроенная память была около 2Гб, и подключаемая память становилась внешней (Using the External Storage). Удаляемая (removable) память — все хранилища, которые могут быть удалены из устройства без «хирургических» вмешательств. До версии KitKat 4.4 API не предоставляло функционала для получения путей к внешней памяти. Начиная с этой версии (API 19) появилась функция public abstract File[] getExternalFilesDirs (String type), которая возвращает массив строк с путями к внутренней и внешней памяти. Но как же быть с нашей SD Card, которая вставлена в слот? Путь к ней мы опять не можем получить.
Результаты поиска Чтобы ответить на поставленный вопрос я обратился к всезнающему гуглу. Но и он мне не дал четкого ответа. Было рассмотрено множество вариантов определения от использования стандартных функций, которые ведут к внешней памяти, но ничего общего с удаляемыми устройствами хранения данных они не имеют, до обработки правил монтирования устройств (Android же на ядре Linux работает). В последних случаях были использованы «зашитые» пути к папке с примонтироваными устройствами (в различных версиях эта директория разная). Не стоит забывать, что от версии к версии правила монтирования меняются.В конечном итоге я решил объединить все полученные знания и написал свой класс, который может нам вернуть пути к внешним и удаляемым устройствам.
Описание кода Был создан класс MountDevice, который содержит в себе путь к устройству, тип устройства и некий хэш.Типов устройств выделено два (внутреннюю память я не стал трогать, так как к ней доступ можно получить через API системы). public enum MountDeviceType { EXTERNAL_SD_CARD, REMOVABLE_SD_CARD } И был создан класс StorageHelper, который и осуществляет поиск доступных карт памяти.В классе StorageHelper реализовано два способа поиска — через системное окружение (Environment) и с использованием утилиты Linux mount, а точнее результата ее выполнения.
Способ первый — Environment При работе с окружением я использую стандартную функцию getExternalStorageDirectory () для получения информации о внешней памяти. Чтобы получить информацию о удаляемой памяти, я использую переменную окружения «SECONDARY_STORAGE».Внешняя память всегда одна и обычно всегда есть, поэтому проверяем ее на читаемость, вычисляем хэш и запоминаем. Удаляемой памяти может быть много, поэтому необходимо полученную строку разбить по разделителю и проверять каждое значение.
Функция fillDevicesEnvirement String path = android.os.Environment.getExternalStorageDirectory () .getAbsolutePath (); if (! path.trim ().isEmpty () && android.os.Environment.getExternalStorageState ().equals ( android.os.Environment.MEDIA_MOUNTED)) { testAndAdd (path, MountDeviceType.EXTERNAL_SD_CARD); }
// Получаем ремувабл String rawSecondaryStoragesStr = System.getenv («SECONDARY_STORAGE»); if (rawSecondaryStoragesStr!= null && ! rawSecondaryStoragesStr.isEmpty ()) { // All Secondary SD-CARDs splited into array final String[] rawSecondaryStorages = rawSecondaryStoragesStr .split (File.pathSeparator); for (String rawSecondaryStorage: rawSecondaryStorages) { testAndAdd (rawSecondaryStorage, MountDeviceType.REMOVABLE_SD_CARD); } } Вариант решения взят со stackoverflow. Ответ где-то там внизу.Способ второй — mount Так как у меня долго не получалось заставить систему мне сказать путь к удаляемой памяти, я решил искать в сторону примонтированных устройств. В системе есть файлы конфигурации, в которых описаны правила монтирования внешних устройств. Все бы хорошо, но на Android версии 4.* к этому файлу простым смертным доступа нет, поэтому рассматривать этот способ не буду.Вернемся к утилите mount. При запуске без параметров команда возвращает список смонтированных файловых систем. Удаляемые устройства имеют обычно формат файловой системы FAT, то будем выделять строки, в которых есть характеристика «fat». Внешняя память будет характеризоваться параметром «fuse».
Примечание: при использовании такого способа не всегда корректно (скорее всего я что-то не учел) определяются типы смотнтированных устройств. Разницу замечал на разных версиях Android. Поэтому этот способ можно использовать как дополнительный.
Функция fillDevicesProcess try { Runtime runtime = Runtime.getRuntime (); proc = runtime.exec («mount»); try { is = proc.getInputStream (); isr = new InputStreamReader (is); br = new BufferedReader (isr); while ((line = br.readLine ()) != null) { if (line.contains («secure»)) continue;
if (line.contains («asec»)) continue;
if (line.contains («fat»)) {// TF card String columns[] = line.split (» »); if (columns!= null && columns.length > 1) { testAndAdd (columns[1], MountDeviceType.REMOVABLE_SD_CARD); } } else if (line.contains («fuse»)) {// internal (External) // storage String columns[] = line.split (» »); if (columns!= null && columns.length > 1) { // mount = mount.concat (columns[1] + »\n»); testAndAdd (columns[1], MountDeviceType.EXTERNAL_SD_CARD); } } } } finally { … } } catch (Exception e) { … }
Вариант решения взят со stackoverflow. Ответов там несколько примерно одинаковых.
Про дублирование Многие замечали в директории монтирования устройств такую картину: /storage/sdcard0/ /storage/emulated/0/ /storage/emulated/legacy/ И что самое интересно, все это одна и та же внешняя карта памяти. Такое дробление начинается с версии Jelly Bean и сделано это для поддержки многопользовательского режима работы системы. Более подробно тут. И вот, чтобы не получать одну и туже карту памяти как различные устройства, необходим способ определения идентичности. Если бы был доступ к конфигурации монтирования, то и вопросов не было. Но доступа нет. Поэтому я тут подсмотрел решение с расчетом хэша для каждого устройства: создаем StringBuilder записываем в него общий размер устройства и размер используемого пространства устройства обходим содержимое корня устройства записываем имя каталога записываем имя файла и размер вычисляем hash Своя функция расчета хэша calcHash private int calcHash (File dir) { StringBuilder tmpHash = new StringBuilder ();
tmpHash.append (dir.getTotalSpace ()); tmpHash.append (dir.getUsableSpace ());
File[] list = dir.listFiles (); for (File file: list) { tmpHash.append (file.getName ()); if (file.isFile ()) { tmpHash.append (file.length ()); } }
return tmpHash.toString ().hashCode ();
}
Пример использования
/* Получаем базовый путь */
if (! mPreferences.contains (PREFS_BASEBATH)) {
// Если еще не сохранялся в настройках, то пытаемся найти карты
// памяти
ArrayList
Кто еще какими способами пользуется?