понедельник, 6 октября 2014 г.

Android 4.4 и запись на внешнюю карту памяти...

До Android 4.4 (API 19), нам приходилось читать файлы (vold.fstab, mounts), чтобы понять, установлена ли внешняя карта памяти и какой до неё путь.  Теперь у меня есть устройство с Android 4.4.2 и я могу рассказать о некоторых изменениях (с точки зрения пользователя и разработчика).

Сравнивая Android 4.4.2 и Android 4.1.2…
Поменялось меню настроек, меню для разработчика по умолчанию скрыто, появилась поддержка новой виртуальной машины ART (замена для Dalvik) и т.д. А ещё версия 4.4 очень серьёзно оптимизирована и может работать на устройствах с 512 МБ ОЗУ, работает очень шустро.

В данной статье рассмотрим новые правила работы с внешними карточками и файлами.

UPD (21.02.15): Для агрессивных и невнимательных, сообщаю, что статья предназначена для программистов, а не простых пользователей Андроида. Те, кто хочет получить рут или просто поменять память местами на своём устройстве, то вам на 4пда в тему вашего устройства.


Вкратце, теперь ваше приложение может работать с файлами только вашего приложения и не может свободно работать с картой памяти как раньше. По сути, компания Гугл ввела ограничения, о которых я писал, ещё в феврале 2014 общаясь в комментариях с Алхимовым Павлом, только тогда речь шла о морали и более мягких ограничениях :).

Что мы теперь имеем:
Если приложение не подготовлено для работы с Андроид 4.4 и использует большой кэш, то, скорее всего оно не установится на ваше устройство, выдавая при этом окно «Не хватает места для установки». Это связано с тем, что:
  1. Внутренняя память, в большинстве случаев не превышает 4 Гб, из них пользователю отдают 1.5 Гб, которые в свою очередь уже забиты каким-нибудь гов*** (приложениями) от производителя устройства.
  2. Гугл не любит внешние карты памяти и советует всем производителям использовать в устройствах только внутреннюю память, без поддержки карт памяти.
  3. И главное, т.к. есть второй пункт, то при установке приложения, система пытается установить его только во внутреннюю память.

По новым правилам, приложения могут работать с внешней картой только в своей папке. Документация. Думаю данное правило, плавно перетечёт в новый Android L.

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

Способов существует как минимум два, во всех способах необходимо получение ROOT-прав. Самый нормальный, на мой взгляд – это внести изменения в файл /system/etc/permissions/platform.xml. Расписывать способы не буду, т.к. этой информации полно на 4pda и в интернете.

Если приложение предусматривает выбор папки для хранения кэша, то вы можете создать (через стандартный файловый менеджер) специальные папки на карте памяти, после чего прописать путь до этих папок в приложении. 
Например: приложение Яндекс карты (Умеет хранить и использовать кэш карт без подключения к интернету.)
В приложении предусмотрена смена места хранения кэша.
Т.е. нам необходимо создать папки /storage/extSdCard/Android/data/ru.yandex.yandexmaps и вставить данный путь в приложение.
После этих действий, приложение начнёт скачивание кэша на внешнюю карту памяти.

Как решить проблему в вашем приложении (разработка с поддержкой Android 4.4):
Всё, что написано ниже, основано на официальной документации от Гугла и моих экспериментах. По большей части на документации, я только написал код, чтобы всё проверить. Я не знаю, почему разработчики приложений ещё не используют данные методы, может слишком ленивы (это я про Яндекс карты :)…

Компания Google реализовала для нас несколько способов получения путей для сохранения файлов. Начиная с API 19 у нас есть специальные методы, которые возвращают список доступных для записи папок.

Первый элемент возвращаемого массива всегда содержит путь для внутренней записи. Второй и далее элементы содержат пути для внешней записи. 

Важное замечание! Данные методы не видят подключённые USB-устройства, только карту/ы памяти, вставляемые в аккумуляторном отсеке! 

Если внешней карты нет в устройстве, то список будет состоять только из одного элемента (внутреннее место хранения). 

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


Правило массива:
  • Элемент с индексом 0 – содержит внутренний путь для записи.
  • Элемент с индексом 1 – содержит внешний путь для записи.
Методы (с версии 4.4):
  • Context.getExternalFilesDirs(String type) – пути для хранения файлов на внутренней (открытые папки, не общие) и внешней памяти (открытые папки, не общие)
  • Context.getExternalCacheDirs() – пути для хранения кэша (открытые папки, не общие)
  • Context.getObbDirs() – пути для хранения OBB-файлов (открытые папки, не общие)

У меня на устройстве LG L70 D325 (Android 4.4.2) + карта памяти, значения такие:

Context.getExternalFilesDirs(nil):
  • /storage/emulated/0/Android/data/com.embarcadero.Project1/files
  • /storage/external_SD/Android/data/com.embarcadero.Project1/files

Context.getExternalFilesDirs(Environment.DIRECTORY_DOWNLOADS):
  • /storage/emulated/0/Android/data/com.embarcadero.Project1/files/Download
  • /storage/external_SD/Android/data/com.embarcadero.Project1/files/Download

Context.getExternalFilesDirs(Environment. DIRECTORY_PICTURES):
  • /storage/emulated/0/Android/data/com.embarcadero.Project1/files/Pictures
  • /storage/external_SD/Android/data/com.embarcadero.Project1/files/Pictures

Context.getExternalCacheDirs():
  • /storage/emulated/0/Android/data/com.embarcadero.Project1/cache
  • /storage/external_SD/Android/data/com.embarcadero.Project1/cache

Context.getObbDirs():
  • /storage/emulated/0/Android/obb/com.embarcadero.Project1
  • /storage/external_SD/Android/obb/com.embarcadero.Project1

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

Пример кода, использование новых методов:
uses
  Androidapi.JNI, Androidapi.Helpers, Androidapi.JNIBridge,
   Androidapi.JNI.JavaTypes;

procedure TForm1.Button1Click(Sender: TObject);
var
  ArrayPath: TJavaObjectArray<JFile>;
  OnePath, TwoPath: JFile;
begin

  ArrayPath := SharedActivity.getExternalCacheDirs;
  OnePath := TJFile.Wrap(ArrayPath.GetRawItem(0));
  TwoPath := TJFile.Wrap(ArrayPath.GetRawItem(1));
  Label1.Text := JStringToString(OnePath.getAbsolutePath);
  Label2.Text := JStringToString(TwoPath.getAbsolutePath);

end;

Итог:
Мы можем создавать, изменять и удалять файлы:
  1. Внутренняя память, скрытые папки (пример: /data/data/<application ID>/files)
  2. Внутренняя память, открытые папки вашего приложения (пример: /storage/emulated/0/Android/data/com.embarcadero.Project1/files)
  3. Внешняя память, открытые папки вашего приложения (пример: /storage/external_SD/Android/data/com.embarcadero.Project1/files)
  4. (Не проверено) Внутренняя память, «открытые-общие» папки (пример: /storage/emulated/0/Pictures)
Получить путь до всех этих папок, можно при помощи Android API, однако в Rad Studio уже реализована часть методов (модуль «System.IOUtils.pas»), а конкретно, по 1, 2 и 4 пунктам. Получить путь до папок из пунктов 2 и 3, можно при помощи новых методов в Android API 19.

На этом всё.

5 комментариев:

  1. Видит только 1 SD карту, а внутреннюю память нет

    ОтветитьУдалить
    Ответы
    1. А подробнее можно? Что за устройство? Версия Андроида? Вообще не представляю себе, чтобы данные методы неправильно работали, хотя возможно всё... может какой баг или производитель что-то сделал на свой лад...

      Удалить
  2. А у меня вообще начались проблемы с sd картой, искала как их решить и как-то попала на ваш сайт)) Интересная статья, автору спасибо! А проблемы с картой решились форматированием по этой инструкции http://grand-screen.com/blog/kak-otformatirovat-kartu-pamyati-na-android/ Ну может у кого тоже карта тормозит, будет полезно)

    ОтветитьУдалить
  3. Для Andrey Efimov. Всё это очень интересно! Спасибо!!! Но публиковать нужно только проверенные варианты. Это сберегает время!

    ОтветитьУдалить
    Ответы
    1. А в чем собственно проблема, вариант отлично работал. Что-то изменилось в этих методах?

      Удалить