Получение пути к карте памяти SD Card на Android

от автора

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

Итак, начнем с теории.

Терминология

Гугл нам говорит, что есть следующие понятия:

  1. Внутренняя (internal) память — это часть встроенной в телефон карты памяти. При ее использовании по умолчанию папка приложения защищена от доступа других приложений (Using the Internal Storage).
  2. Внешняя (external) память — это общее «внешнее хранилище», т.е. это может быть как часть встроенной памяти, так и удаляемое устройство. Обычно это часть встроенной памяти, как удаляемое устройство я видел в последний раз на андройде 2.2, где встроенная память была около 2Гб, и подключаемая память становилась внешней (Using the External Storage).
  3. Удаляемая (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 и сделано это для поддержки многопользовательского режима работы системы. Более подробно тут. И вот, чтобы не получать одну и туже карту памяти как различные устройства, необходим способ определения идентичности. Если бы был доступ к конфигурации монтирования, то и вопросов не было. Но доступа нет. Поэтому я тут подсмотрел решение с расчетом хэша для каждого устройства:

  1. создаем StringBuilder
  2. записываем в него общий размер устройства и размер используемого пространства устройства
  3. обходим содержимое корня устройства
  4. записываем имя каталога
  5. записываем имя файла и размер
  6. вычисляем 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<MountDevice> 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.

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

ссылка на оригинал статьи http://habrahabr.ru/post/254813/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *