What is core data in android

Хранение данных и файлов

В целом хранение файлов и данных можно условно разделить на две группы: во внутреннем или внешнем хранилище. Но разница между ними довольна тонка. В целом политика Гугла в отношение данных ужесточается с каждой версии системы.

Android поддерживает различные варианты хранения данных и файлов.

  • Специфичные для приложения файлы. Доступ к файлам имеет только приложение, их создавшее. Файлы могут находиться во внутреннем и внешнем хранилище. У других приложений нет доступа (кроме случаев, когда файлы хранятся на внешнем хранилище). Методы getFilesDir(), getCacheDir(), getExternalFilesDir(), getExternalCacheDir(). Разрешений на доступ не требуется. Файлы удаляются, когда приложение удаляется пользователем.
  • Разделяемое хранилище. Приложение может создавать файлы, которыми готово поделиться с другими приложениями — медиафайлы (картинки, видео, аудио), документы. Для медифайлов требуется разрешение READ_EXTERNAL_STORAGE или WRITE_EXTERNAL_STORAGE.
  • Настройки. Хранение простых данных по принципу ключ-значение. Доступно внутри приложения. Реализовано через Jetpack Preferences. Настройки удаляются, когда приложение удаляется пользователем.
  • Базы данных. Хранение данных в SQLite. На данный момент реализовано через библиотеку Room. Доступ только у родного приложения.

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

Следует быть осторожным при работе с внутренним и внешним хранилищем. Внутренне хранилище всегда есть в системе, но оно может быть не слишком большим по объёму. Вдобавок к внутреннему хранилищу, устройство может иметь внешнее хранилище. В старых моделях таким хранилищем выступала съёмная SD-карта. Сейчас чаще используют встроенную и недоступную для извлечения флеш-память. Если ваше приложение слишком большое, можно попросить систему устанавливать программу во внешнее хранилище, указав просьбу в манифесте.

В разных версиях Android требования к разрешению для работы с внешним хранилищем постоянно менялись. На данный момент (Android 10, API 29) требования выглядят следующим образом.

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

Доступ к общим файлам достигается через FileProvider API или контент-провайдеры.

Для просмотра файлов через студию используйте инструмент Device File Explorer.

Внешняя карта памяти

Когда появились первые устройства на Android, то практически у всех были внешние карточки памяти, которые вставлялись в телефон. Обычно там хранили фотки, видео и свои файлы. Всё было понятно — были различные методы для доступа к файловой системе. А потом началась чехарда. В телефонах также была и собственная «внешняя» память. Она вроде как и внешняя, но вставлена на заводе и вытащить её пользователь не мог, т.е. практически внутренняя. Затем пошла мода на телефоны, у которых была только такая внутреннее-внешняя карта. Пользователи поворчали, но привыкли. Сейчас встречаются оба варианта. Как правило, у телефонов с спрятанной картой больше памяти и выше степень водонепроницаемости.

Подобные фокусы с картой породили и другую проблему — Гугл озаботился безопасностью файлов и стала думать, как осложнить жизнь разработчику. С выходом каждой новой версии системы компания то давала добро на полный доступ к карточке, то ограничивала, то давала права с ограничениями, то откатывала свои решения назад. Короче, запутались сами и запутали всех.

Попробуем немного разобраться с этим зоопарком. Но помните, что процесс путаницы продолжается.

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

Вот что я (кажется) понял, попытавшись загрузить картинку с внешней SD карточки.

External это не External
«EXTERNAL_STORAGE» называется так не потому, что это внешняя память по отношению к устройству, а потому что она выглядит как внешняя память для компьютера, если устройство подключить кабелем к компьютеру. Причём именно выглядит, потому что обмен идёт по протоколу MTP – устройство только показывает компьютеру список папок и файлов, а при необходимости открыть или скопировать файл он специально загружается на компьютер, в отличие от настоящей флешки, файлы которой становятся файлами в файловой системе самого компьютера. Обмен по MTP позволяет устройству продолжать работать, когда оно подключено к компьютеру.

Emulated это не Emulated
Сначала я пытался прочесть файл с карточки на эмуляторе (из этого так ничего и не вышло). Функция getExternalStorageDirectory() давала мне /storage/emulated/0, и я думал, что «emulated» – это потому что на эмуляторе. Но когда я подцепил реальный планшет, слово «emulated» никуда не исчезло. Я стал рыться в интернете и обнаружил, что «Emulated storage is provided by exposing a portion of internal storage through an emulation layer and has been available since Android 3.0.» – то есть это просто кусок внутренней памяти, которая путём какой-то эмуляции делается доступной для пользователя, в отличие от собственно внутренней памяти.

Читайте также:  Обновления андроид 10 для asus zenfone max pro m1

При этом с точки зрения системы доступная для пользователя папка называется /storage/emulated/0, а при подключении к компьютеру по USB это просто одна из двух главных папок устройства – у меня в Windows Explorer она называется Tablet. Вторая папка у меня называется Card, и это и есть настоящая внешняя карточка.

Нет стандартных средств добраться из приложения до файлов на внешней карточке. Все попытки добраться до настоящей внешней карточки делаются с помощью неких трюков. Самое интересное, что я нашел, это статья на http://futurewithdreams.blogspot.com/2014/01/get-external-sdcard-location-in-android.html — парень читает таблицу смонтированных устройств /proc/mounts, таблицу volume daemons /system/etc/vold.fstab, сравнивает их и выбирает те тома, которые оказываются съёмными (с помощью Environment.isExternalStorageRemovable()).

Оказалось, что несистемным приложениям в принципе запрещено напрямую обращаться к съёмной карточке! Похоже, что это было так всегда, но вот начиная с версии Android 6 Marshmallow написано: внешняя карточка может быть определена как Portable либо Adoptable. Adoptable – это как бы «усыновляемая» память которая может быть «adopted», то есть взята в систему (примерно как кот с улицы в дом – это тоже называется to adopt) и использована как внутренняя. Для этого ее надо особым образом отформатировать и не вынимать, иначе не факт, что система продолжит нормально работать.

Portable – это нормальная съёмная карточка, но несистемным приложениям запрещено обращаться из программ к файлам на ней! Вот что написано в https://source.android.com/devices/storage/traditional.html:

Android 6.0 supports portable storage devices which are only connected to the device for a short period of time, like USB flash drives. When a user inserts a new portable device, the platform shows a notification to let them copy or manage the contents of that device. In Android 6.0, any device that is not adopted is considered portable. Because portable storage is connected for only a short time, the platform avoids heavy operations such as media scanning. Third-party apps must go through the Storage Access Framework to interact with files on portable storage; direct access is explicitly blocked for privacy and security reasons.

Если я правильно понял, этот самый Storage Access Framework позволяет работать с документом на карточке через диалог (открыть файл/сохранить файл), а вот прочитать или записать файл на карточке непосредственно из программы невозможно.

Общий вывод – реально из программы можно работать только с файлами на предоставляемой пользователю части встроенной памяти устройства, а на съёмной карточке – нет.

Это напоминает войну Microsoft с пользователями и разработчиками по поводу диска C:, компания уговаривала не устраивать беспорядок в корне этого диска, а ещё лучше — перенести свои файлы на другой диск. Но явных запретов не было.

Состояние на текущий момент

Гугл утверждает, что с версии Android 10 Q стандартный доступ к файлам будет прекращён. Ещё в Android 4.4 появился Storage Access Framework, который и должен стать заменой для работы с файлами.

Методы Environment.getExternalStorageDirectory() и Environment.getExternalStoragePublicDirectory() признаны устаревшими и будут недоступны. Даже если они будут возвращать корректные значения, ими вы не сможете воспользоваться.

В Android 7.0 добавили исключение FileUriExposedException, чтобы разработчики перестали использовать схему file://Uri.

Можно создавать файлы в корневой папке карточки при помощи Environment.getExternalStorageDirectory(), а также папки с вложенными файлами. Если папка уже существует, то у вас не будет доступа на запись (если это не ваша папка).

Если вы что-то записали, то сможете и прочитать. Чужое читать нельзя.

Кстати, разрешения на чтение и запись файлов не требуются, а READ_EXTERNAL_STORAGE и WRITE_EXTERNAL_STORAGE объявлены устаревшими.

Другие приложения не могут получить доступ к файлам вашего приложения. Файлы, которые вы создали через getExternalFilesDir(), доступны через Storage Access Framework, кроме файлов, созданных в корне карточки (что-то я совсем запутался). Ещё можно дать доступ через FileProvider.

При подключении USB-кабеля через getExternalFilesDir(), вы можете увидеть свои файлы и папки, а также файлы и папки пользователя. При этом файлы и папки пользователя на корневой папке вы не увидите. Вам не поможет даже adb или Device File Explorer студии.

Что делать?

Пользуйтесь методами класса Context, типа getExternalFilesDir(), getExternalCacheDir(), getExternalMediaDirs(), getObbDir() и им подобными, чтобы найти место для записи.

Используйте Storage Access Framework.

Используйте MediaStore для мультимедийных файлов.

Используйте FileProvider, чтобы файлы были видимы другим приложениям через ACTION_VIEW/ACTION_SEND.

Читайте также:  Гугл точка ком андроид

Android 10: Появился новый флаг android:allowExternalStorageSandbox=»false» и метод Environment.isExternalStorageSandboxed() для работы с песочницей. Флаг android:requestLegacyExternalStorage=»true» для приложений, которые ещё используют старую модель доступа к файлам.

Как временное решение можно добавить в блок манифеста application атрибут android:requestLegacyExternalStorage=»true», чтобы доступ к файлам был как раньше в Android 4.4-9.0.

Android 11

Если вы создаёте файловый менеджер, то ему нужны возможности для просмотра файлов. Для этого следует установить разрешение MANAGE_EXTERNAL_STORAGE или использовать атрибут android:requestLegacyExternalStorage=»true» (см. выше).

Источник

Core Data в деталях

Недавно я начала работать над большим проектом с использованием Core Data. Обычное дело, что люди на проектах меняются, опыт теряется, а нюансы забываются. Углубить всех в изучение конкретного фреймворка невозможно — у всех полно своих рабочих проблем. Поэтому я подготовила небольшую презентацию, из тех пунктов, которые считаю важными или недостаточно освещенными в туториалах. Делюсь со всеми и надеюсь, что это поможет писать эффективный код и не делать ошибок. Предполагается, что вы уже немного в теме.

Начну с банального.

Core Data – это фреймворк, который управляет и хранит данные в приложении. Можно рассматривать Core Data, как оболочку над физическим реляционным хранилищем, представляющую данные в виде объектов, при этом сама Core Data не является базой данных.

Объекты Core Data

Для создания хранилища в приложении используются классы NSPersistentStoreCoordinator или NSPersistentContainer. NSPersistentStoreCoordinator создает хранилище указанного типа на основе модели, можно указать размещение и дополнительные опции. NSPersistentContainer можно использовать с IOS10, дает возможность создания с минимальным количеством кода.

Работает это следующим образом: если по указанному пути существует база данных, то координатор проверяет ее версию и, при необходимости, делает миграцию. Если база не существует, то она создается на основании модели NSManagedObjectModel. Чтобы все это работало правильно, перед внесением изменений в модель создавайте новую версию в Xcode через меню Editor -> Add Model Version. Если вывести путь, то можно найти и открыть базу в эмуляторе.

Core Data использует 4 типа хранилища:

— SQLite
— Binary
— In-Memory
— XML (только для Mac OS)

Если, например, по соображениям безопасности Вы не хотите хранить данные в файловом виде, но при этом хотите использовать кеширование в течении сессии и данные в виде объектов, вполне подойдет хранилище типа «In-Memory». Собственно, не запрещается иметь в одном приложении несколько хранилищ разного типа.

Несколько слов хочется сказать об объекте NSManagedObjectContext. Вообще, Apple дает весьма туманную формулировку для NSManagedObjectContext — среда для работы с объектами Core Data. Все это от желания отмежеваться от ассоциаций с реляционными базами, и представить Core Data, как простое в использовании средство, не требующее понимания ключей, транзакций и прочей базданской атрибутики. Но на языке реляционных баз NSManagedObjectContext можно, в некотором смысле, назвать менеджером транзакций. Вы, наверное, заметили, что он имеет методы save и rollback, хотя скорее всего вы пользуетесь только первым.

Недопонимание этого простого факта, приводит к использованию одноконтекстной модели, даже в тех ситуациях, где ее недостаточно. К примеру, вы редактируете большой документ, и при этом вам потребовалось загрузить пару справочников. В какой момент вызывать save? Если бы мы работали с реляционной базой, то тут бы не возникло вопросов, поскольку каждая операция выполнялась бы в своей транзакции. В Core Data тоже есть вполне удобный способ для решения этой проблемы — это ответвление дочернего контекста. Но к сожалению, это почему-то используется редко. Вот тут есть неплохая статья на эту тему.

Наследование

По непонятной мне причине, существует очень большое количество мануалов и примеров, где никак не используется наследование для Entity/NSManagedObject(таблиц). Между тем, это очень удобный инструмент. Если вы не используете наследование, то присваивать значения атрибутам (полям) можно только через KVС механизм, который, не выполняет проверку имен и типов атрибутов, а это легко может привести к ошибкам времени выполнения.

Переопределение классов для NSManagedObject делается в дизайнере Core Data:

Наследование и кодогенерация

После указания названия класса для Entity, можно воспользоваться кодогенерацией и получить класс с готовым кодом:

Если вы хотите посмотреть автогенерируемый код, но при этом, не хотите добавлять файлы в проект, можно воспользоваться другим способом: установить у Entity опцию «Codegen». В этом случае код нужно поискать в ../DerivedData/…

Используйте кодогенерацию для создания классов, опечатки в названии переменных могут привести к ошибкам времени выполнения.

Вот примерно такой код будет создан:

В swift @NSManaged имеет тот же смысл, что и dynamic в Objective C.
Core Data сама заботится о получении данных (имеет внутренние аксессоры) для атрибутов своих классов. Если у вас есть транзитные поля, то нужно добавлять функции для их расчета.

Читайте также:  После обновления андроид не работает инстаграм

Классы, наследуемые, от NSManagedObject (таблицы), не имели до IOS10 «обычного» конструктора, в отличии от остальных классов. Чтобы создать объект типа Company, нужно было написать достаточно неповоротливую конструкцию с использованием NSEntityDescription. Сейчас появился более удобный метод инициализации через контекст (NSManagedObjectContext). Код ниже. Обратите внимание на преимущество наследования при присвоении атрибутов перед механизм KVC:

Пространство имен для NSManagedObject

Еще одна вещь которая стоит отдельного упоминания — это пространство имен.

У вас не возникнет затруднений, если вы работаете на ObjectiveC или Swift. Обычно, это поле заполняется правильно по умолчанию. А вот в смешанных проектах, возможно, для вас станет сюрпризом, что для классов в swift и ObjectiveC нужно проставить разные опции. В Swift «Модуль» должен быть заполнен. Если это поле не будет заполнено, то к имени класса добавится префикс с названием проекта, что вызовет ошибку выполнения. В Objetive C «Модуль» оставляйте пустым, иначе NSManagedObject не будет найден при обращении к нему через имя класса.

Связи между объектами

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

  • Создаем связь на каждой стороне (таблице)
  • После этого становится доступным поле Inverse, его нужно заполнить в каждой таблице.

Apple очень настаивает на указании инверсных связей. При этом, инверсия не усиливает связанность, а помогает Core Data отслеживать изменения на обоих сторонах связи, это важно для кеширования и обновления информации.

Также важно правильно указывать правило удаления. Правило удаления – это действие, которое будет выполнятся с данным объектом при удалении родительского объекта.

  • Cascade — удаление всех дочерних объектов, при удалении родительского.
  • Deny — запрет удаления родителя, если есть дочерний объект
  • Nullify — обнуление ссылки на родителя
  • No action — действие не указано, выдаст предупреждении при компиляции

В указанном примере, при удалении компании все сотрудники будут удалены (cascade). При удалении сотрудника, ссылка на него в компании будет обнулена (пред экран)

Способы добавления дочерних сущностей в родительскую

1) Первый способ — это добавление через NSSet. Для примера добавим 2 сотрудника в компанию:

Этот способ удобен при первичной инициализации объекта или при заполнении базы. Тут есть небольшой нюанс. Если в компании уже были сотрудники, а вы присвоили новый set, то у бывших сотрудников обнулится ссылка на компанию, но они не будут удалены. Как вариант можно получить список сотрудников и работать уже с этим set’ом.

2) Добавление дочерних объектов через идентификатор родителя

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

3) Добавление дочерних объектов через автогенерируемые методы

Для полноты картины полезно знать про этот метод, но мне он как-то не пригодился, а лишний код я удаляю, чтобы не загромождать проект.

Запросы с условием по дочернему элементу

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

Вызов метода в коде будет выглядеть так:

Не используйте в запросах транзитные поля, их значения не определены в момент выполнения запроса. Ошибки не произойдет, но результат будет неправильный.

Настройка атрибутов (полей)

Вы, наверное заметили, что атрибуты Entity имеют несколько опций.
C опциональностью все понятно из названия.

Oпция «использовать скалярный тип» появилась в swif. В Objective C не используются скалярные типы для атрибутов, так как они не могут принимать значение nil. Попытка присвоить скалярное значение атрибуту через KVC вызовет ошибку выполнения. Отсюда становится понятным, почему типы атрибутов в Core Data не имеют строгого соответствия с типами языка. В swift, и в смешанных проектах атрибуты скалярного типа можно использовать.

Транзитные атрибуты – это расчетные поля, которые не сохраняются в базе. Их можно использовать для шифрования. Эти атрибуты получают значения через переопределенный аксессор, либо через присваивание примитивов по мере надобности (например, в переопределенных willSave и awakeFromFetch).

Аксессоры атрибутов:

Если вам не нужно использовать расчетные поля, например, делать шифрование или что-то другое, то можно вообще не задумываться о том, чем являются аксессуары атрибутов. Между тем операции получения и присвоения значений атрибутам не являются «атомарными». Чтобы понять, что я имею ввиду смотрите код ниже:

Используйте примитивы в event’ах NSManagedObject вместо обычного присваивания, чтобы избежать зацикливания. Пример:

Источник

Оцените статью