Context data что за файл
Android использует файловую систему, похожую на систему на основе дисковых файловых систем на других платформах. Этот урок описывает, как работать с файловой системой Android, чтобы читать и записывать файлы, используя File API.
File объект подходит для чтения или записи больших объемов данных с начала до конца, не пропуская ничего вокруг. Например, это хорошо подходит для файлов изображений или для обмена чем-нибудь по сети.
Этот урок показывает, как выполнять основные задачи связанные с файлами в вашем приложении. Урок предполагает, что вы знакомы с основами файловой системы Linux и стандартным API файлового ввода/вывода в java.io .
Сохраните файл во внутреннем хранилище
При сохранении файла во внутреннее хранилище, вы можете запросить соответствующий каталог, в виде объекта File , вызвав один из двух методов:
getFilesDir() Возвращает File представляющий собой внутренний каталог вашего приложения. getCacheDir() Возвращает File представляющий собой внутренний каталог для временных файлов кэша вашего приложения. Будьте уверены, удалять файлы по одному больше не нужено, и реализуйте разумный предел размера объема памяти, который вы будете использовать в какой-либо момент времени, например, 1 Мб. Если в системе возникает нехватка места, она может удалить ваши файлы кэша без предупреждения.
Чтобы создать новый файл в одном из этих каталогов, вы можете использовать File() конструктор, передав File предоставленный одним из выше указанных методов, который указывает на ваш внутренний каталог. Например:
В качестве альтернативы, вы можете вызвать openFileOutput() для получения FileOutputStream , который пишет в файл в вашем внутреннем каталоге. Например, вот как записать текст в файл:
Или, если вам нужно кэшировать некоторые файлы, то следует использовать createTempFile() . Например, следующий метод извлекает имя файла из URL и создает файл с таким же именем во внутренней каталоге кэша вашего приложения:
Примечание: Каталог внутреннего хранилища вашего приложения использует имя пакета вашего приложения, располагаясь в специальном месте файловой системы Android. Технически, другое приложение может читать ваши внутренние файлы, если вы установите для файла режим доступа на чтение. Тем не менее, другие приложения также должны знать ваше имя пакета и имена файлов. Другие приложения не могут просматривать ваши внутренние каталоги, и не имеют доступа для чтения или записи, если явно не установить разрешения для чтения или записи. Так что, пока вы используете MODE_PRIVATE для ваших файлов во внутренней памяти, они не являются доступными для других приложений.
Контекст приложения
Это singleton-экземпляр (единственный на всё приложение), и к нему можно получить доступ через функцию getApplicationContext() . Этот контекст привязан к жизненному циклу приложения. Контекст приложения может использоваться там, где вам нужен контекст, жизненный цикл которого не связан с текущим контекстом или когда вам нужно передать контекст за пределы Activity .
Например, если вам нужно создать singleton-объект для вашего приложения, и этому объекту нужен какой-нибудь контекст, всегда используйте контекст приложения.
Если вы передадите контекст Activity в этом случае, это приведет к утечке памяти, так как singleton-объект сохранит ссылку на Activity и она не будет уничтожена сборщиком мусора, когда это потребуется.
В случае, когда вам нужно инициализировать какую-либо библиотеку в Activity , всегда передавайте контекст приложения, а не контекст Activity .
Таким образом, getApplicationContext() нужно использовать тогда, когда известно, что вам нужен контекст для чего-то, что может жить дольше, чем любой другой контекст, который есть в вашем распоряжении.
Ну, а как насчет android:sharedUserId ?
android: sharedUserId — это атрибут, который вы можете поместить в манифест, который указывает логический идентификатор пользователя, который будет использоваться для вашего приложения. Любое другое приложение, которое установлено, которое подписывается одним и тем же ключом подписи и запрашивает тот же android:sharedUserId будет использовать одного и того же пользователя Linux с точки зрения безопасности. Эффект заключается в том, что эти два приложения смогут безнаказанно работать с файлами друг друга, так как эти файлы принадлежат одному и тому же пользователю Linux.
Этот атрибут реально предназначен для предварительно установленных приложений, таких как software suite предварительно загруженный производителем устройства, мобильным оператором или разработчиком модифицированной ROM прошивки. В частности, как только вы единожды установите свое приложение, вы не сможете затем безболезненно изменить свое значение android:sharedUserId не заблокировав при этом доступ пользователю к любым существующим файлам… поскольку Android не изменяет права владельца на существующие файлы при изменении учетной записи пользователя Linux, под которой запускается приложение.
Существуют различные риски при одновременном доступе нескольких процессов к файлам. Некоторые подсистемы, такие как SQLite, имеют встроенную логику для решения этой проблемы. Но если вы сами организуете свой собственный доступ к файлу (например, через File и Java I/O), вам нужно что-то делать с одновременным доступом, а это сложно.
Вам также нужно обрабатывать ситуацию, когда одно приложение деинсталлируется, удаляя файлы, которые использовало другое приложение. В модели hub-and-spoke, например, с приложением и набором плагинов, возможно, это не так рискованно. В других моделях, где приложения более равноправны, вы не можете позволить себе потерять данные своего приложения только потому, что пользователь решил удалить какое-то отдельное приложение.
Наконец, вы не знаете, что может принести будущее. Прямо сейчас вы можете рассматривать свой набор приложений в виде набора с тесной связью. Кто-то, кто приобретает эти приложения или приобретает вашу фирму, может пожелать пойти другим путем. Использование возможностей совместного доступа к данным, которые более слабо связаны, например ContentProvider , дает вам большую гибкость. В идеальном мире ваше приложение должно относиться к другим приложениям как к достаточно надежному, но не всегда доступному ресурсу, так же, как к вашему собственному веб-сервису.
Когда нельзя использовать getApplicationContext()?
- Это не полноценный контекст, поддерживающий всё, что может Activity . Некоторые вещи, которые вы попытаетесь сделать с этим контекстом, потерпят неудачу, в основном связанные с графическим интерфейсом.
- Могут появляться утечки памяти, если контекст из getApplicationContext() удерживается на каком-то объекте, который вы не очищаете впоследствии. Если же где-то удерживается контекст Activity , то как только Activity уничтожается сборщиком мусора, всё остальное тоже уничтожается. Объект Application остается на всю жизнь вашего процесса.
getContext() в ContentProvider
Этот контекст является контекстом приложения и может использоваться аналогично контексту приложения. К нему можно получить доступ через метод getContext() .
Должен ли я делать файлы во внутреннем хранилище World-Readable или World-Writeable?
О, $БОГИ, нет. Используйте FileProvider и обслуживайте этот контент с помощью реализации ContentProvider . После этого вы, по крайней мере, имеете возможность использовать систему разрешений Android для управления доступом к этим файлам, в отличие от вашего варианта, когда любое приложение в системе может испортить эти файлы.
Продолжение: Сохранение данных в SQL базу данных
Если не указано иное, этот контент распространяется под лицензией Creative Commons Attribution 2.5. Для получения дополнительной информации и ограничений, см. Лицензия контента.
Когда Вы начинаете использовать CoreData для сохранения данных в Ваших приложениях, Вы начинаете работать с единственным управляемым объективным контекстом / managed object context (MOC). Это — то, что используется в шаблоне при создание проекта в xCode, если Вы при создание проекта ставите галочку рядом с «Use Core Data».
Использование CoreData в сочетании с NSFetchedResultsController значительно упрощает работу с любым видом списка элементов, которые выводятся на экране в табличном представлении.
Есть два сценария, в которых Вы хотели бы разветвляться, т.е. используя несколько управляемых объективных контекстов: 1) чтобы упростить процесс добавления / редактирования новых элементов и 2) избежать блокировать UI. В этом посте я хочу рассмотреть пути создания Ваших контекстов, чтобы получить, то, что Вы хотите.
Во-первых, давайте рассмотрим установку единственного контекста. Вам нужен persistent store coordinator (координатор постоянного хранилища) (PSC), чтобы обращаться к файлу базы данных на диске. Таким образом, чтобы данный координатор понимал, как структурирована данная база данных, Вам нужна модель. Эта модель объединена из всех определений модели, содержавшихся в проекте, и указывает CoreData об этой структуре БД. Координатор устанавливается на управляемый объект контекста через свойство функций. Запомните первое правило: управляемый объективный контекста с помощью координатора запишется на диск, если Вы вызовите saveContext.
Рассмотрим эту схему. Каждый раз, когда Вы вставляете, обновляете или удаляете сущность в этом единственном управляемом объективном контексте, то контроллер выбранных результатов будет уведомлен об этих изменениях и обновит свое содержание табличного представления. Это не зависит от сохранения контекста. Вы можете сохранять так редко или часто, как Вы хотите. Шаблон Apple экономит на каждом добавлении сущности и также (как не странно) на applicationWillTerminate.
Этот подход в основном подходит для большинства основных случаев, но как я говорил раньше, есть две проблемы с ним. Первая связана с добавлением новой сущности. Вы, вероятно, хотите снова использовать то же визуальное представление ьбю/ для добавления и редактирования сущности. Таким образом, вы, возможно, захотите создать новую сущность даже прежде, чем заполните визуализацию представления для него. Это заставило бы уведомления об обновлении инициировать обновление на контроллере выбранных результатов, т.е. пустая строка появится незадолго до того, как концепция MVC полностью появиться для добавления или редактирования.
Вторая проблема была бы очевидна, если бы обновления накапливались, прежде чем saveContext станет слишком обширным, и операция сохранения заняла бы больше времени, чем 1/60-й секунды. Поскольку в этом случае пользовательский интерфейс был бы заблокирован, пока сохранение не завершиться, и у Вас был бы значимый переход, например, при прокрутке.
Обе проблемы могут быть решены, используя несколько управляемых объективных контекстов.
«Традиционный» мультиконтекстный подход
Подумайте о каждом управляемом объективном контекста, как о временном блокноте изменений. Перед выходом iOS 5 Вы наверно слышали о изменениях в других контекстах и объединили изменения с момента уведомления в основной контекст. Типичная установка была бы похожа на эту блок-схему:
Создайте временный контекст для его использования для очереди фоновых задач. И сохраните там изменения, установите тот же координатор постоянного хранилища на временном контексте, как и в основном контексте. По мнению Маркуса Сарра это должно выглядеть так:
Несмотря на то, что NSPersistentStoreCoordinator не ориентирован на многопотоковое исполнение, NSManagedObjectContext знает, как заблокировать его должным образом, когда тот находиться в использовании. Поэтому, мы можем присоединить столько NSManagedObjectContext объектов к NSPersistentStoreCoordinator, сколько мы хотим, не опасаясь коллизии.
Вызов saveContext на фоновом контексте запишет изменения в файл хранилища и также инициирует NSManagedObjectContextDidSaveNotification.
В коде это будет примерно выглядеть следующим образом:
Создание временного контекста происходит очень быстро, таким образом, Вы не должны волновать по поводу частого создания и выпуска этих временный контекстов. Дело в том, что чтобы установить persistentStoreCoordinator на тот же самый главный контекст так, создание также должно произойти в фоновом режиме.
Я предпочитаю эту упрощенную установку стека CoreData:
Теперь рассмотрим обработчик уведомления, который мы устанавливаем для того, чтобы каждый раз, всплывало уведомление didSave.
Во-первых, мы хотим избежать слияния наших собственных изменений. Также, если у нас есть несколько БД CoreData в том же приложении мы пытается избежать слияния изменений, которые предназначены для другой БД. Я сталкивался с такой проблемой в одном из моих приложений, поэтому я и проверяю координатор постоянного хранилища. Наконец осуществите слияние изменений с помощью метода mergeChangesFromContextDidSaveNotification. У уведомления есть словарь всех изменений в его полезной нагрузке, и этот метод знает об интеграции их в контекст.
Передача управляемых объектов между контекстами
Категорически запрещается перемещать управляемый объект, который вы получили из одного контекста в другой. Существует простой способ разобраться с «зеркалом» управляемого объекта через ObjectID. Этот идентификатор ориентирован на многопотоковое исполнение, и Вы можете всегда получить его от одного экземпляра NSManagedObject и затем вызвать objectWithID. Второй контекст тогда получит свою собственную копию управляемых объектов для работы с ним.
Описанный подход является полностью обратно совместимым вплоть до первой версии IOS, которая получила поддержку CoreData с IOS 3. Если Вам нужна поддержка только IOS 5 для вашего приложения, то существует более современный подход, который мы рассмотрим ниже.
Родительский/дочерний контекст
В IOS 5 появилась возможность для управляемого объектного контекста содержать parentContext. Вызов метода saveContext пушит изменения из дочернего контекста к родителю без необходимости прибегать к способу, который включает слияния содержимого из словаря, описывающего изменения. В то же время Apple, добавили возможность для контекстов иметь их собственное отдельную очередь для выполнения изменения как синхронно так и асинхронно.
Тип параллелизма очереди, задается в новом инициализаторе initWithConcurrencyType на NSManagedObjectContext. Обратите внимание на то, что в этой схеме я добавил несколько дочерних контекстов, так что у всех есть та же основная очередь контекста, как и у родителя.
Дочерний контекст каждый раз, при сохранении будет сохранять изменения в своего родителя, и это приводит к тому, что контроллер выбранных результатов должен также знать об этих изменениях. Однако это еще не сохраняет данные, так как фоновый контекст не знает о координаторе постоянного хранилища. Для получения данных на диск нужен дополнительный метод saveContext: на главной очереди контекста.
Первое необходимое изменение для этого подхода заключается в изменении основного контекста типа параллелизма на NSMainQueueConcurrencyType. В вышеупомянутом _setupCoreDataStack изменения начальной строки выглядят, как показано ниже и нет больше необходимости получать уведомления о слиянии.
Длительная фоновая операция будет выглядеть следующим образом:
Каждому контексту теперь необходимо использовать performBlock: (async) или performBlockAndWait: (sync) для работы. Это гарантирует, что операции, содержавшиеся в блоке, используют корректную очередь. В вышеупомянутом примере длинная операция выполняется на фоновой очереди. Как только у вас все будет готово, и изменения перенаправляться к родителю через метод saveContext тогда появиться асинхронный метод performBlock для сохранения mainMOC. И будет снова происходить на корректной очереди, как предусмотрено performBlock.
Дочерние контексты в отличие от родителей не обновляются автоматически. Вы можете загрузить их заново, чтобы получить обновления, но в большинстве случаев они временные, и таким образом мы не должны беспокоиться об этом. Пока основная очередь контекста получает изменения, то контроллеры выбранных результатов обновляются, и мы имеем персистентность при сохранении основного контекста.
Удивительное упрощение, предоставленное этим подходом, состоит в том, что Вы можете создать временный контекст (как дочерний элемент) для любой визуализации представления, у которого есть кнопка Save и Cancel. Если Вы передаете управляемый объект для редактирования, то Вы переносите его (через objectID, упомянутый выше) к временному контексту.Пользователь имеет возможность обновлять все элементы управляемого объекта. Если он нажимает на Save то сохраняется весь временный контекст. Если он нажимает на cancel, то ничего не нужно делать, потому что изменения отбрасываются вместе с временным контекстом.
У вас еще не крутится голова от всей этой информации? Если нет, то вот высший пилотаж о многоконтекстности CoreData.
Асинхронное сохранение данных
Гуру Core Data Marcus Zarra показал мне следующий подход, который основывается на вышеупомянутом Parent/Child методе, но добавляет дополнительный контекст исключительно для записи на диск. Как упоминалось ранее долгую операцию записи, мог бы блокировать основной поток в течение короткого времени, заморозив UI. В рамках этого разумного подхода запись выделяется в отдельную очередь, и пользовательский интерфейс сохраняет плавность работы (остаётся плавным, не «зависает»).
Настройка для CoreData также довольно проста. Нужно только переместить persistentStoreCoordinator в наш новый скрытый контекст и сделать основной контекст дочерним элементом.
Теперь нужно осуществить три разных сохранения для каждого обновления: временного контекста, главного контекста UI и для записи на диск. Но так же легко, как и раньше, можно реализовать стек performBlocks… Пользовательский интерфейс остается разблокирован в течение длительной операции базы данных (например, импорт большого количества записей), а также, когда это записано на диск.
iOS 5 значительно упростил работу с CoreData на фоновых очередях, и получил изменения, исходящих от дочерних контекстов к их родителям. Если Вы все еще используете iOS 3/4 тогда, это все функции не доступны для Вас. Но если Вы начинаете новый проект, у которого есть iOS 5 как минимальное требование, Вы можете сразу создать Турбо Подход Маркуса Сарры, как описано выше.
Зак Волдовский указал мне, что использование скрытого типа параллелизма очереди для “редактирования визуализации представления ” может быть излишним. Если используете NSContainmentConcurrencyType вместо визуализации представления дочернего контекста тогда, Вам не нужно обертка performBlock. Все что нужно, так это performBlock на mainMOC для сохранения.
Тип параллелизма ограничения – это “старый способ” выполнения контекстов, но это не означает, что он был традиционным. Он просто привязывает операции контекста к самоуправляемой модели потоков. Набор оборотов скрытой очереди для каждого нового контроллера является расточительным, ненужным, и медленным.-performBlock: и-performBlockAndWait: не работайте с типом параллелизма ограничения по той причине, что ни блоки, ни блокировка не необходимы, когда Вы создаете несколько контекстов в способе.
NSManagedObjectContext знает, как сохранить и объединиться разумно, и поэтому основной контекст потока привязан к основному потоку, его слияния всегда выполняются безопасно. Редактирование визуализации представления связано с основным потоком точно так же, как основная визуализация представления; единственным способом является — отдельная операция, которая находится только в UI, поэтому он подходит для использования типа параллелизма ограничения здесь. Контекст редактирования концептуально не “новая” вещь, он просто откладывает изменение на позже, все еще позволяя Вам отменить изменения полностью.
Таким образом, это действительно сводится к Вашему персональному предпочтению: скрытая очередь с performBlock или без параллелизма ограничения. Что касается меня, то я стараюсь предпочитать скрытые очереди из-за безопасности, что я получаю от их использования.
p.s. Для многих может показаться что статья бесполезная, но надеюсь что некоторые все же вынесут что-то полезного из этой статьи. Не ругайте сильно за перевод, если есть замечания, пишите в личку, исправим :)
Как следует из названия, это контекст текущего состояния приложения или объекта. Это позволяет вновь созданным объектам понять, что вообще происходит. Обычно его вызывают, чтобы получить информацию о другой части программы.
Кроме того, Context является проводником в систему, он может предоставлять ресурсы, получать доступ к базам данных, преференсам и т.д. Ещё в Android приложениях есть Activity . Это похоже на проводник в среду, в которой выполняется ваше приложение. Объект Activity наследует объект Context . Он позволяет получить доступ к конкретным ресурсам и информации о среде приложения.
Context присутствует практически повсюду в Android приложении и является самой важной его частью, поэтому необходимо понимать, как правильно его использовать.
Неправильное использование Context может легко привести к утечкам памяти в Android приложении.
Существует много разных типов контекста, поэтому давайте разберёмся, что каждый из них представляет из себя, как и когда их правильно использовать.
Выберите внутреннее или внешнее хранилище
Все Android устройства имеют две области хранения файлов: «внутреннее» и «внешнее» хранилище. Эти названия пришли из первых дней Android, когда предлагалось, что большинство устройств предоставляют встроенную постоянную неизменную память (внутреннее хранилище), а также съемный носитель, такое как микро SD карта(внешнее хранилище). Некоторые устройства делят места постоянного хранения на «внутренние» и «внешние» разделы, т.е. даже без съемного носителя, всегда есть два хранилища, и поведение API для внешнего хранилища такое же, не зависимо от того, является ли оно съемным или нет. Следующие списки обобщают факты о каждом хранилище.
Внутреннее хранилище:
- Всегда доступно.
- Файлы, сохраненные здесь, по умолчанию, доступны лишь вашему приложению.
- Когда пользователь удаляет ваше приложение, система удаляет все файлы вашего приложения из внутренней памяти.
Внутреннее хранилище лучше, когда вы хотите быть уверены, что ни пользователь, ни другие приложения не могут получить доступ к файлам.
Внешнее хранилище:
- Не всегда доступно, так как пользователь может смонтировать внешний накопитель в качестве USB хранилища, и в некоторых случаях вынуть его из устройства.
- Чтение разрешено всем, поэтому файлы, сохраненные здесь, можно прочитать без вашего контроля.
- Когда пользователь удаляет ваше приложение, система удаляет файлы вашего приложения отсюда, только если вы сохраните их в каталоге из getExternalFilesDir() .
Внешние накопители лучшее место для файлов, которые не требуют ограничения доступа, и для файлов, которыми вы хотите поделиться с другими приложениями, или хотите позволить пользователю получить доступ к ним на компьютере.
Полезный совет: Хотя приложения устанавливаются во внутреннее хранилище по умолчанию, можно указать android:installLocation атрибут в манифесте, чтобы ваше приложение могло быть установлено на внешний накопитель. Пользователи ценят эту опцию, когда размер APK очень большой, и у них есть внешнее пространство для хранения, превышающее внутреннюю память. Для получения дополнительной информации, см. Путь установки приложения.
Запросите свободное пространство
Если вы знаете заранее, сколько данных вы будете сохранять, вы можете выяснить, есть ли в наличие достаточно места, не вызывая IOException с помощью вызова getFreeSpace() или getTotalSpace() . Эти методы предоставляют информацию о текущем доступном пространстве и общем пространстве раздела, соответственно. Эта информация также полезна, чтобы избежать заполнения раздела накопителя выше определенного порога.
Тем не менее, система не гарантирует, что вы можете записать столько байт, сколько обозначено getFreeSpace() . Если возвращаемое число на несколько МБ больше, чем размер данных, которые вы хотите сохранить, или если файловая система заполнена меньше чем на 90%, то, наверное, можно продолжить. В противном случае, вероятно, не стоит записывать в хранилище.
Примечание: Вы не обязаны проверять количество свободного места, прежде чем вы сохраняете файл. Вместо этого, вы можете попробовать записать файл, и поймать IOException если оно произойдет. Возможно, вам придется сделать так, если вы не знаете точно, сколько места нужно. Например, если вы измените кодировку файла перед сохранением путем преобразования PNG изображения в JPEG, вы не будете знать размер файла заранее.
Удаление файла
Вы всегда должны удалить файлы, которые вам больше не нужны. Самый простой способ удалить файл это вызвать delete() .
Если файл сохранен во внутреннем хранилище, вы также можете попросить Context найти и удалить файл, вызвав deleteFile() :
Примечание: Когда пользователь удаляет ваше приложение, Android система удаляет следующее:
- Все файлы, сохраненные во внутреннем хранилище
- Все файлы, сохраненные на внешнем накопителе, используя getExternalFilesDir() .
Тем не менее, вы должны вручную удалить все файлы из кэша, созданные с помощью getCacheDir() на регулярной основе, а также регулярно удалять другие файлы, которые вам больше не нужны.
Получите разрешения для внешних накопителей
Для записи на внешний накопитель, вы должны запросить WRITE_EXTERNAL_STORAGE разрешение в вашем файле манифеста:
Внимание: В настоящее время, все приложения имеют возможность читать с внешних накопителей без специального разрешения. Тем не менее, это изменится в будущем релизе. Если ваше приложение должно прочитать внешний накопитель (но не писать в него), то вам нужно будет объявить READ_EXTERNAL_STORAGE разрешение. Чтобы убедиться, что ваше приложение продолжало работать, как и ожидалось, вы должны объявить это разрешение сейчас, прежде чем изменения вступят в силу.
Однако, если ваше приложение использует WRITE_EXTERNAL_STORAGE разрешение, то также неявно имеет разрешение на чтение внешнего хранилища.
Вам не нужно никаких разрешений для сохранения файлов во внутреннее хранилище. Ваше приложение всегда имеет разрешение на чтение и запись файлов в каталоге внутреннего хранилища.
Правило большого пальца
В большинстве случаев используйте контекст, доступный непосредственно из компонента, в котором вы работаете в данный момент. Вы можете безопасно хранить ссылку на него, если она не выходит за пределы жизненного цикла этого компонента. Как только вам нужно сохранить ссылку на контекст в объекте, который живет за пределами вашей Activity или другого компонента, даже временно, используйте ссылку на контекст приложения.
Это перевод серии статей от Mark Murphy из CommonsWare, широко известного на stackoverflow, а так же автора книг “The Busy Coder’s Guide to Android Development”, “Android’s Architecture Components”. Некоторые термины оставлены не переведенными специально.
Существует много путаницы в отношении модели хранилища Android. Путаницы стало значительно больше с изменениями Android 4.4 в Storage Model, и с тех пор ситуация не улучшилась. Есть бесчисленное множество вопросов на Stack Overflow и тому подобных ресурсах, где люди явно не совсем разбираются в различных моделях хранилищ Android.
В зависимости от модели вашего устройства пользователи в конечном итоге придут в «Настройки» --> «Хранилище на этом устройстве» (Settings --> Storage on their device) или в эквивалентном месте, и могут видеть экран, который описывает «Внутреннее хранилище».
Пользователь думает, что вся встроенная флешка — это «внутреннее хранилище» (Internal Storage). К счастью, Google начал менять этот термин с Android 8.0, перейдя к «general storage» вместо «internal storage».
Тем не менее, пользователи могут по-прежнему видеть «внутреннее хранилище» в таких местах, как окно проводника в Windows, когда их устройство подключено через USB.
Увы, то, что видят пользователи это не то же самое, что Android SDK считает «внутренним хранилищем», что приводит к некоторой путанице. Если вы читали документацию на Android по внутреннему хранилищу, то это описание … как минимум туманно (прим. текст изменился со времени написания статьи):
Вы можете сохранять файлы непосредственно во внутренней памяти устройства. По умолчанию файлы, сохраненные во внутреннем хранилище, являются приватными для вашего приложения, и другие приложения не могут получить к ним доступ (также как и пользователь). Когда пользователь удаляет приложение, эти файлы удаляются.
По правде говоря, Android SDK определяет «внутреннее хранилище» как особый каталог, уникальный для вашего приложения, где ваше приложение может размещать файлы. Как было предложено в документации, эти файлы по умолчанию предназначены для чтения и записи для вашего приложения и запрещены для любого другого приложения (исключение: пользователи, работающие с файловыми менеджерами с привилегиями суперпользователя на rooted устройствах могут получить доступ ко всему).
В Context есть несколько базовых методов, которые предоставляют вам доступ к внутреннему хранилищу, в том числе:
- getCacheDir()
- getDir()
- getDatabasePath()
- getFilesDir()
- openFileInput()
- openFileOutput()
Другие методы будут опираться на них, такие как openOrCreateDatabase() . Другие классы также будут полагаться на них, такие как SQLiteOpenHelper и SharedPreferences .
Внутри будут некоторые каталоги, автоматически созданные Android, поскольку вы используете некоторые из методов Context. Например, getFilesDir() возвращает объект File , указывающий на каталог files/ во внутреннем хранилище вашего приложения.
NEVER HARDCODE PATHS.
Время от времени я вижу, что разработчики делают что-то вроде этого:
File f=new File("/data/data/their.app.package.name/files/foo.txt");
Это не преступление, это хуже, это — ошибка.
Правильный ход, да и писать меньше:
File f=new File(getFilesDir(), "foo.txt");
Что еще более важно, внутреннее хранилище не всегда находится в одном месте. Примечательно, что у нас есть понятие отдельных профилей пользователей (separate user profiles), начиная с Android 4.2 для планшетов и Android 5.0 для телефонов. Каждый пользователь получает свое собственное «внутреннее хранилище». Хотя вышеупомянутый каталог по-прежнему используется для основного пользователя, не гарантируется, что он же будет использоваться для вторичных учетных записей.
Device File Explorer tool в Android Studio 3.0+ может просматривать все внутренние хранилища на эмуляторе, а также внутреннее хранилище отлаживаемых приложений на продакшн устройствах.
В командной строке вы можете использовать adb с опцией run-as .
Например, чтобы загрузить базу данных из внутреннего хранилища основного пользователя на вашу девелоперскую машину, вы можете использовать:
adb shell 'run-as your.application.package.name cp /data/data/your.application.package.name/databases/dbname.db /sdcard'
Обратите внимание, что:
- Вам нужно будет изменить пункт назначения туда, где на вашем устройстве монтируется внешнее хранилище (показано здесь как /sdcard/ , которое не будет одинаковым на всех устройствах)
- Возможно, вам придется использовать cat вместо cp на старых устройствах
- После того, как файл будет находиться на внешнем хранилище, вы сможете использовать adb pull , чтобы загрузить его на свой компьютер, или получить доступ к нему другими обычными способами (например, путем монтирования устройства в качестве диска).
На старых устройствах Android 1.x и 2.x внутреннее хранилище обычно находилось в выделенном разделе файловой системы, и этот раздел обычно был довольно крошечным. HTC Dream (a.k.a., T-Mobile G1), оригинальное Android-устройство, обладал огромными 70 МБ встроенной памяти для использования всеми приложениями (это не опечатка, в то время мы измеряли память в мегабайтах).
К тому времени, когда вышли 2.3 устройства, внутреннее хранилище могло быть размером 1 ГБ.
Android 3.0 изменил модель хранилища, так как внутреннее хранилище стало больше объемом. У устройств, которые рекламируют как имеющее 4 ГБ, 8 ГБ, 16 ГБ и т.д. пространства для хранения, обычно имелось все это (минус существующее содержимое) доступное для внутреннего хранилища. Мы рассмотрим, что изменилось в Android 3.0 и его влияние на модель хранилища в следующих постах про внешнее хранилище.
Для Android 1.x и 2.x внутреннее хранилище было действительно только для небольших файлов, и вам нужно было использовать внешнее хранилище для всего остального. Android 3.0+ означает, что для большинства устройств и большинства пользователей внутреннее хранилище отлично подходит для файлов, которые не предназначены для обычного использования другими приложениями или доступны пользователю независимо от вашего приложения. Однако некоторые опытные пользователи обнаруживают, что даже on-board flash недостаточна для того, что они хотят хранить, поэтому они переходят на съемные хранилища… которые представляют собой банку с червями (прим. имеются в виду ἕλμινς) — источник многих непредсказуемых и сложных проблем.
Контекст Activity
Этот контекст доступен в Activity и привязан к её жизненному циклу. Контекст Activity следует использовать, когда вы передаете контекст в рамках Activity или вам нужен контекст, жизненный цикл которого привязан к текущему контексту.
Сохраните файл на внешнем накопителе
Поскольку внешнее хранилище может быть недоступно — например, когда пользователь монтируется хранилище к ПК, или вынул SD карту, которая обеспечивает внешнее хранилище — вы всегда должны убедиться, что раздел доступен перед доступом к нему. Вы можете запросить состояния внешнего хранилища, вызвав getExternalStorageState() . Если возвращенное состояние равно MEDIA_MOUNTED , то вы можете читать и писать файлы. Например, следующие методы полезны для определения доступности хранилища:
Хотя внешнее запоминающее устройство может изменяться пользователем и другими приложениями, есть две категории файлов, которые вы могли бы сохранить здесь:
Общедоступные файлы Файлы, которые должны быть в свободном доступе для других приложений и для пользователя. Когда пользователь удаляет ваше приложение, эти файлы должны оставаться доступными для пользователя.Например, фотографии, сделанные с помощью приложения, или другие загруженные файлы. Личные файлы Файлы, которые по праву принадлежат вашему приложению, и их следует удалить, когда пользователь удаляет ваше приложение. Хотя эти файлы являются технически доступными пользователю, и другим приложениям, т.к. они находятся на внешнем накопителе, это файлы, которые реально не представляют собой ценности для пользователя вне вашего приложения. Когда пользователь удаляет ваше приложение, система удаляет все файлы с внешнего приватного каталога вашего приложения.Например, дополнительные ресурсы загруженные вашим приложения или временные медиа-файлы.
Если вы хотите сохранить общедоступные файлы на внешнем устройстве хранения, используйте getExternalStoragePublicDirectory() метод для получения File представляющего соответствующий каталог на внешнем накопителе. Метод принимает аргумент, указывающий тип файла, который вы хотите сохранить, чтобы они могли быть логически организованы с другими общедоступными файлами, такими как DIRECTORY_MUSIC или DIRECTORY_PICTURES . Например:
Если вы хотите сохранять файлы, которые являются приватными для вашего приложения, вы можете запросить соответствующий каталог, вызвав getExternalFilesDir() и передать ему имя, указывающее тип необходимого каталога. Каждый каталог, созданный таким способом, добавляется в родительский каталог, который инкапсулирует все файлы внешнего хранилища вашего приложения, которые система удаляет, когда пользователь удаляет ваше приложение.
Например, вот метод, который можно использовать для создания каталога отдельного фотоальбома:
Если ни одно из предопределенных имен подкаталогов не удовлетворяет вашим файлам, вы можете вместо этого вызвать getExternalFilesDir() и передать null . При это возвращается корневой каталог для приватных каталогов вашего приложения на внешнем накопителе.
Помните, что getExternalFilesDir() создает каталог внутри каталога, который удаляется, когда пользователь удаляет ваше приложение. Если сохраненные файлы должны оставаться доступными после того как пользователь удаляет ваше приложение — например, когда ваше приложение представляет собой камеру, и пользователь захочет сохранить фотографии — то следует использовать getExternalStoragePublicDirectory() .
Независимо от того, используете ли вы getExternalStoragePublicDirectory() для файлов, которые являются общими или getExternalFilesDir() для файлов, которые являются приватными для вашего приложения, важно, что вы используете имена каталогов, предоставляемые константами API, такими как DIRECTORY_PICTURES . Эти имена каталогов гарантируют, что файлы будут интерпретироваться системой должным образом. Например, файлы, сохраненные в DIRECTORY_RINGTONES классифицируются по системе медиа сканера как мелодии звонка вместо музыки.
Как запретить пользователям rooted устройств доступ к моим файлам во внутреннем хранилище?
Просто не помещайте файлы во внутреннее хранилище. Пользователи rooted устройств могут получить доступ к тому, что им нужно на устройстве, поэтому единственный способ предотвратить их доступ к вашим данным — не иметь их на устройстве.
В целом, относительно мало людей с rooted устройствами — я оцениваю их на уровне менее 1%. ИМХО, вы преуспеете больше, сосредоточив свою инженерную работу на написании лучшего приложения, вместо того, чтобы тратить время на защиту от рутованных устройств.
Получить контекст внутри кода можно одним из следующих методов:
- getBaseContext(получить ссылку на базовый контекст)
- getApplicationContext(получить ссылку на объект приложения)
- getContext (внутри активности или сервиса получить ссылку на этот объект)
- this(то же, что и getContext)
- MainActivity.this (внутри вложенного класса или метода получить ссылку на объект MainActivity)
- getActivity(внутри фрагмента получить ссылку на объект родительской активности)
Как было сказано выше, контекст является базовым классом для классов Application, Activity и Service, а значит его методы входят в их состав. Именно поэтому для передачи контекста в качестве параметра можно использовать как ссылку на сам контекст (getBaseContext), так и ссылки на наследуемые классы (getApplicationContext, getContext, this, MainActivity.this, getActivity).
Но тут важно понимать, что время жизни этих ссылок будет разное. Ссылка на переданный объект будет работать, пока будет жить этот объект. Поэтому в качестве контекста важно передать такую ссылку, которая будет рабочей на всём протяжении работы вызываемого метода.
То есть, мы видим, что хотя контекст одинаков у разных объектов, сами эти объекты могут жить разное время.
Контекст можно представить, как набор функций для работы на уровне приложения, вошедший в состав таких крупных классов, как Application, разных видов Activity, Service.
Получить контекст внутри кода можно одним из следующих методов:
- getBaseContext(получить ссылку на базовый контекст)
- getApplicationContext(получить ссылку на объект приложения)
- и(внутри активности или сервиса получить ссылку на этот объект)
- this(то же, что и getContext)
- MainActivity.this (внутри вложенного класса или метода получить ссылку на объект MainActivity)
- getActivity(внутри фрагмента получить ссылку на объект родительской активности)
Как было сказано выше, контекст является базовым классом для классов Application, Activity и Service, а значит его методы входят в их состав. Именно поэтому для передачи контекста в качестве параметра можно использовать как ссылку на сам контекст (getBaseContext), так и ссылки на наследуемые классы (getApplicationContext, getContext, this, MainActivity.this, getActivity).
Но тут важно понимать, что время жизни этих ссылок будет разное. Ссылка на переданный объект будет работать, пока будет жить этот объект. Поэтому в качестве контекста важно передать такую ссылку, которая будет рабочей на всём протяжении работы вызываемого метода.
Будет видно даже после завершения приложения:
Toast.makeText(getBaseContext(), «Text «, Toast.LENGTH_SHORT).show();
То есть, мы видим, что хотя контекст одинаков у разных объектов, сами эти объекты могут жить разное время.
Контекст можно представить, как набор функций для работы на уровне приложения, вошедший в состав таких крупных классов, как Application, разных видов Activity, Service.
Binding Adapters, создание пользовательских атрибутов На прошлом уроке мы изучали обработку пользовательских событий ввода
Читайте также: