[Из песочницы] Получение пути к карте памяти 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 storages = StorageHelper.getInstance () .getRemovableMountedDevices (); // проверяем съемные карты памяти if (storages.size () != 0) { setBasePath (storages.get (0).getPath () + mAppPath); } else if ((storages = StorageHelper.getInstance () // Проверяем // внутреннюю // память .getExternalMountedDevices ()).size () != 0) { setBasePath (storages.get (0).getPath () + mAppPath); } } else { // Вытаскиваем из сохранненых настроек mBasePath = mPreferences.getString (PREFS_BASEBATH, context .getFilesDir ().getParent ()); } Заключение Подробные рассуждения по этому вопросу понимания памяти в Android, некоторые советы можно прочитать тут.Исходный код всего класса расположен еще нигде не расположен. На днях постараюсь разместить на gitHub.

Кто еще какими способами пользуется?

© Habrahabr.ru