Не могу загрузить приложение на playmarket!
Вот то что оно мне пишет: Вы загрузили APK-файл или набор Android App Bundle с объектом activity, псевдонимом объекта, сервисом или широковещательным приемником с фильтром intent, но без набора свойств android:exported. Этот файл нельзя установить на устройство с версией Android 12 или выше. Чтобы узнать больше, перейдите на developer.android.com/about/versions/12/behavior-changes-12#exported. Если понижать target version до 30, тогда ругается, что необходимо поднять до 31. Что делать?
Отслеживать
17.9k 11 11 золотых знаков 25 25 серебряных знаков 56 56 бронзовых знаков
задан 21 ноя 2022 в 10:13
Андрей Мостовенко Андрей Мостовенко
105 9 9 бронзовых знаков
1 ответ 1
Сортировка: Сброс на вариант по умолчанию
но без набора свойств android:exported.
Если у вас есть «широковещательный приемник «, вам стоило бы указать android:exported=true. Это есть в ссылке, которая даже указана в вашем вопросе.
Отслеживать
ответ дан 21 ноя 2022 в 10:42
Daniels Šatcs Daniels Šatcs
1,611 10 10 серебряных знаков 15 15 бронзовых знаков
Уточнение: начиная с Android 12 атрибут android:exported нужно явно указывать для всех компонентов. Раньше были условия, при которых значение true выставлялось автоматически, теперь дефолтное значение всегда false (компонент не виден другим приложениям не зависимо от фильтров).
21 ноя 2022 в 10:59
Я уже разобрался спасибо! Все верно! Но приемника там нет, а все равно просит. А щас разрешения кроме к интернету в приле нет а просит политику добавить. ))
21 ноя 2022 в 10:59
Там говорится не конкретно про приёмник, а про все компоненты (с объектом activity, псевдонимом объекта, сервисом или широковещательным приемником). То есть даже MainActivity (или какая у вас стартовая) должна иметь значение exported=true . Откройте манифест — студия все проблемы подсветит и даже предложит фиксы
Сервисы Android: начало работы

Многие приложения Android имеют функции, выполнение которых требует много времени, например загрузка какого-либо контента или обновление удаленной базы данных. Если разработчики не организуют работу подобных функций должным образом, они постепенно загромоздят основной поток, создав утечки памяти и неточности в работе. Это может сильно раздражать ваших пользователей.
К счастью есть Android Services, которые позволяет сделать работу с подобным функционалом. Android Services — это особый тип компонента приложения, который предназначен для обработки повторяющихся и/или длительно выполняемых процессов.
- В этом туториале вы узнаете:
- Что такое Android Services и как их объявить в файле Manifest.
- Как использовать foreground сервис для отображении уведомления пользователю.
- Что такое фоновая обработка сложных процессов, не связанных с пользовательским интерфейсом.
- Как использовать bound service.
Давайте начнем
В этом туториале вы будете использовать приложение Memo. Memo — это игра, в которой пользователю предлагается сопоставить карты за как можно меньшее время. Кроме того, пользователь может прослушивать разные саундтреки во время игры .
В игре четыре уровня сложности: Beginner (Начальный), Intermediate (Средний), Advanced (Продвинутый ) и Expert (Эксперт). Чем выше уровень сложности, тем больше карт нужно сопоставить. После того, как пользователь выполняет задание на уровень эксперт, он проходит игру. Не хотите попробовать? :]
В настоящее время приложение позволяет пользователю только запускать игру и управлять саундтреками. Запустив игру, вы сможете выйти из нее, просмотреть игровые карточки и засечь время, прошедшее с момента запуска игры.
Откройте проект в Android Studio и ознакомьтесь с файлами. Когда вы закончите, запустите проект на устройстве / эмуляторе, чтобы посмотреть, как он выглядит.

Вы можете поиграть в игру, нажав на кнопку PLAY BEGINNER LEVEL. Сейчас вы уже можете сопоставлять карточки, однако отсутствует таймер, отображающий количество времени, затраченное на решение задачи. Это первое, что мы реализуем в этом туториале.

Однако сначала давайте более подробно рассмотрим, что такое Android Services и как их использовать.
Представляем Android Services
Android Services — это компонент, который помогает выполнять длительные процессы, такие как обновление базы данных или сервера, запуск обратного отсчета или воспроизведение звука. По умолчанию Android Services запускаются в том же потоке, что и основные процессы приложения. Такой вид службы также называют local service.
Ресурсоемкие задачи данный сервис будет выполнять в фоновом режиме. Это позволит пользователю взаимодействовать с приложением. Например, если пользователь захочет позвонить или просмотреть уведомления при обновлении сервера, он сможет сделать это, не прерывая и не отменяя обновления.
Для этого нужно создать фоновый поток и вручную указать, какие задачи должны выполняться именно в этом потоке, поскольку сервисы не поддерживают реализацию потока напрямую.
Поскольку Android Services не имеет пользовательского интерфейса, он никак не зависит от последовательности выполнения других процессов. Это означает, что вы можете запускать его, даже когда пользователь вообще никак не взаимодействует с приложением.
Кроме того, другие компоненты приложения могут взаимодействовать с сервисами или даже осуществлять межпроцессное взаимодействие (InterProcess Communication (IPC)). Если вы будете дальше следовать этому туториалу, то узнаете больше о IPC.
В следующих разделах этого туториала мы применим каждый из перечисленных ниже типов Android Services и добавим в наше приложение Memo много новых функций:
- Foreground service: Добавим сервис TimerService, который содержит логику для отслеживания времени.
- Bound service: Используем MusicService, который позволит нам воспроизводить, ставить на паузу и перемешивать саундтреки.
Объявление сервиса в Manifest
Перед использованием сервиса его необходимо объявить в AndroidManifest.xml. Сервис — это компонент Android, точно так же, как и Activity. Для удобства наш проект уже содержит классы сервисов, но вам все еще необходимо правильно их конфигурировать.
Перейдите к AndroidManifest.xml и добавьте этот код внутрь application :
В этом кода вы можете увидеть следующее:
- Android:name: Имя класса сервиса.
- Android:enabled: Если установлено значение true, система может создавать экземпляры сервисов.
- Android:exported: Если установлено значение False, то системе сообщается, что ни одно другое приложение не может запускать и использовать эти сервисы.
Значение android:name выделиться красным цветом, но пока просто не обращайте на это внимание. Мы починим это чуть позже.
А пока начнем работу по реализации нового функционала.
Реализация интерфейса сервиса
Каждый кастомный класс сервиса должен быть подклассом Service и должен реализовывать и переопределять несколько базовых методов колбэка, связанных с жизненным циклом сервиса.
Вот наиболее важные из них:
- onStartCommand(): вызывается системой, когда другой компонент хочет запустить сервис, использую startService() . Как только система вызывает onStartCommand() , сервис может работать в фоновом режиме столько времени, сколько потребуется для завершения процесса. Вам нужно не забыть остановить службу вручную, когда работа будет завершена, вызвав stopSelf() или topService() . Эта не нужно, если вы хотите просто выполнить привязку к сервису, не запуская его.
- onBind(): Вам всегда нужно применять этот метод колбэка. Система использует этот колбэк при вызове bindService() . Используйте его для привязки другого компонента к сервису. Он открывает возможность коммуникации, таким образом вы предоставляете интерфейс для взаимодействия клиентов возвращая IBinder . Если вы не хотите использовать привязку, просто верните null .
- onCreate(): Используйте этот метод для настройки сервиса перед его запуском. Система вызывает его, только если сервис еще не запущен.
- onDestroy(): Система вызывает его, чтобы уничтожить сервис, когда он больше не нужен. Это последний вызов, который получает сервис. После этого сервис перестает использовать какие-либо ресурсы.
Создание сервиса Foreground
Сервис Foreground выполняет задачи, которые необходимо видеть пользователю. Он выводит уведомления в строку состояния, чтобы сообщить пользователю о том, что приложение выполняет операцию и потребляет ресурсы. Уведомление должно иметь приоритет PRIORITY_LOW или выше и быть видимым, даже если пользователь не взаимодействует с приложением.
Пользователь не может закрыть уведомление. Оно исчезает, когда служба завершает свою работу или покидает передний план.
Чтобы увидеть работу данного сервиса в действии давайте реализуем TimerService . Откройте TimerService.kt и добавьте следующее, чтобы сделать его подклассом Service :
class TimerService : Service(), CoroutineScope
В этой строчке мы применяем интерфейс Service и CoroutineScope .
Применение методов сервиса.
Присмотритесь и обратите внимание, что Android Studio выдает ошибку из-за того, что не может найти onBind() .

Чтобы исправить это добавьте следующее в TimerService :
// 1 override fun onBind(intent: Intent): IBinder? = null override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int < super.onStartCommand(intent, flags, startId) // 2 intent?.extras?.run < when (getSerializable(SERVICE_COMMAND) as TimerState) < TimerState.START ->startTimer() TimerState.PAUSE -> pauseTimerService() TimerState.STOP -> endTimerService() else -> return START_NOT_STICKY > > // 3 return START_NOT_STICKY > override fun onDestroy() < super.onDestroy() // 4 handler.removeCallbacks(runnable) job.cancel() >
Давайте разберем этот код немного подробнее:
- Так как мы используем сервис переднего плана, нам не нужно выполнять привязку и мы возвращаем null вместо IBinder . Также заметьте, что мы не используем onCreate() , так как нам не нужна никакая специфическая конфигурация для этого сервиса.
- Здесь мы просматриваем дополнительные возможности Intent , чтобы получить значение, которое содержит ключ SERVICE_COMMAND . Это значение указывает на то, какое действие должен выполнить сервис.
- Если система завершает работу сервиса из-за нехватки памяти, START_NOT_STICKY говорит системе о том, что не стоит создавать службу с неопределенным Intent . Альтернативные константы: START_STICKY и START_REDELIVER_INTENT . Если вас интересует функционал этих констант, ознакомьтесь с официальной документацией по константам сервисов.
- Временно удалите все колбэки Handler , чтобы не засорять память. Кроме того, стоит очистить ресурсы, отменив все Job таймера, поскольку на следующем шаге сервис будет уже уничтожен.
Добавление разрешений
Все приложения, созданные для Android 9 (уровень API 28) или выше, должны запрашивать разрешение на использование службы переднего плана. Система автоматически разрешит доступ к сервису. В противном случае приложение генерирует исключение SecurityException.
В Project view перейдите к app ▸ manifest ▸ AndroidManifest.xml и добавьте следующий код над тегом application :
Это позволит нашему приложению Memo использовать сервис переднего плана.
Далее давайте создадим уведомление, которое будет говорить о работе этого сервиса.
Создание канала уведомлений
Любой сервис переднего плана должен уведомлять пользователя о своем запуске. Вам также необходимо определить диспетчер уведомлений, который уведомляет пользователя о происходящих событиях и содержит информацию об одном или нескольких уведомлениях.
Откройте NotificationHelper.kt и добавьте этот код в начало класса:
private val notificationManager by lazy
Здесь вам не нужно инициализировать notificationManager , получив системную службу NOTIFICATION_SERVICE и преобразовав ее в NotificationManager .
В Android 8 и более поздних версиях уведомление должно быть частью канала уведомлений. Чтобы создать его, добавьте этот код в NotificationHelper.kt:
@RequiresApi(Build.VERSION_CODES.O) private fun createChannel() = // 1 NotificationChannel( CHANNEL_ID, CHANNEL_NAME, NotificationManager.IMPORTANCE_DEFAULT ).apply < // 2 description = CHANNEL_DESCRIPTION setSound(null, null) >
В этом кусочке кода мы:
- Создаем канал уведомлений с CHANNEL_ID в качестве идентификатора, CHANNEL_NAME в качестве имени и дефолтным приоритетом по умолчанию.
- Устанавливаем значение CHANNEL_DESCRIPTION для описания канала уведомлений, а для звука - значение null , что означает, что при срабатывании уведомления в канале звук не будет воспроизводиться.
Следующим шагом будет работа с отображением уведомлений.
Создание конструктора уведомлений
Чтобы пользователь знал о запущенном сервисе, создайте уведомление, которое пользователь сможет увидеть в строке состояния. Вверху NotificationHelper.kt добавьте:
// 1 private val notificationBuilder: NotificationCompat.Builder by lazy < NotificationCompat.Builder(context, CHANNEL_ID) // 2 .setContentTitle(context.getString(R.string.app_name)) .setSound(null) .setContentIntent(contentIntent) .setSmallIcon(R.drawable.ic_launcher_foreground) .setPriority(NotificationCompat.PRIORITY_HIGH) // 3 .setAutoCancel(true) >
Если вы получаете ошибку, сообщающую, contentIntent не определено, не волнуйтесь. Мы позаботимся об этом на следующих шагах.
В этом коде мы реализовали следующее:
- Мы использовали NotificationCompat.Builder для того, чтобы создать конструктор уведомлений, отображаемых в строке состояния.
- Конструктор должен содержать информацию о некоторых параметрах уведомления. Однако вам не нужно определять эти параметры при объявлении конструктора. Если не будет хватать какой-либо информации, мы всегда сможем указать эту информацию в самом уведомлении.
- Этот тип уведомления должен быть настроен как автоматически отменяемый. Это означает, что когда пользователь нажмет на уведомление, оно автоматически закроется.
Заметка
Многие разработчики допускают очень глупую ошибку. Они забывают присвоить уведомлению небольшой значок (иконку). Это приводит к тому, что уведомление получается невидимым.
Создание самого уведомления
Пришло время создать само уведомление. Добавьте в NotificationHelper.kt следующее:
fun getNotification(): Notification < // 1 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) < notificationManager.createNotificationChannel(createChannel()) >// 2 return notificationBuilder.build() >
Вот что происходит в этом куске кода:
- Если версия Android равна 8 или выше, система создает канал уведомления и возвращает Notification . К сожалению, библиотека поддержки для версий до Android 8 не предоставляет API каналов уведомлений. Подробнее об этой проблеме читайте в официальной документации на тему каналов уведомлений.
- Возвращается Notification после вызова build() в конструкторе.
Уведомление может обновляться динамически. В этом случае мы будем обновлять текст, если пользователь закроет приложение во время работы службы.
Ниже getNotification() добавьте следующее:
fun updateNotification(notificationText: String? = null) < // 1 notificationText?.let < notificationBuilder.setContentText(it) >// 2 notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build()) >
Вот, что здесь происходит:
- Мы обновляем текст в уведомлении, отображаемом в строке состояния, через notificationBuilder .
- Затем говорим диспетчер уведомлений какое уведомление нужно обновить. Для этого мы используем уникальный NOTIFICATION_ID .
Наконец, давайте определим, что происходит, когда пользователь нажимает на уведомление. Добавьте это в начало NotificationHelper.kt:
private val contentIntent by lazy
Здесь вы выполняете ленивую инициализацию PendingIntent , которая запускает MainActivity , когда пользователь нажимает на уведомление.
Прекрасная работа! Уже очень многое было реализовано. Теперь пора запустить Foreground Service.
Запуск и остановка службы
Чтобы запустить Foreground Service, используйте startForeground() . Для этого метода требуются два параметра: уникальный положительный целочисленный идентификатор уведомления и Notificaton .
Откройте TimerService.kt и добавьте следующее в метод startTimer() :
startForeground(NotificationHelper.NOTIFICATION_ID, helper.getNotification())
Этот метод заставляет службу работать на переднем плане и отправляет уведомление с идентификатором NOTIFICATION_ID в строку состояния. Вы заметите, что helper выделен красным, потому что вы еще не определили переменную helper .

Чтобы исправить это создайте экземпляр класса NotificationHelper в верхней части TimerService :
private val helper by lazy
Чтобы попытаться запустить сервис из другого компонента, откройте MainActivity.kt, найдите sendCommandToForegroundService() и добавьте:
ContextCompat.startForegroundService(this, getServiceIntent(timerState))
Здесь мы запускаем сервис из Activity. Вы передаете его Context и Intent для запуска сервиса.
Все, что начинается, должно заканчиваться, в том числе и сервисы. Откройте TimerService.kt и добавьте в stopService() следующее:
// 1 stopForeground(true) // 2 stopSelf()
В этом блоке кода:
- Этот вызов сообщает системе, что она должна убрать этот сервис из foreground. Переданный логический аргумент соответствует удалению уведомления, если для него установлено значение true .
- Поскольку сервис может запускаться сам, он также должен и завершаться сам. stopSelf() позволяет это реализовать.
Обновление уведомления при изменении таймера
TimerService содержит сопрограмму, которая тикает каждую секунду во время выполнения уровня. Вы будете использовать значение таймера, чтобы обновить представление уведомлений.
Найдите BroadUpdate () внутри TimerService.kt и добавьте этот код в if :
// 1 sendBroadcast( Intent(TIMER_ACTION) .putExtra(NOTIFICATION_TEXT, elapsedTime) ) // 2 helper.updateNotification( getString(R.string.time_is_running, elapsedTime.secondsToTime()) )
Вот что делает этот блок кода:
- Здесь вы отправляете трансляцию с истекшим временем в MainActivity . С его помощью MainActivity может обновлять время в TextView .
- Этот вспомогательный метод обновляет уведомление в строке состояния, которое мы сделали ранее.
На этом этапе мы уже сделали все, что видит пользователь во время игры. Но что произойдет, если пользователь закроет приложение во время работы таймера?
Чтобы это работало корректно, добавьте следующую строку в broadcastUpdate() в else if :
helper.updateNotification(getString(R.string.get_back))
Эта строчка обновляет текст уведомления, зазывающего пользователя обратно в игру.
Отличная работа! Запустите проект, чтобы проверить таймер. Запустите игру на каком-нибудь уровне сложности и обратите внимание, как внизу меняется таймер. Потяните вниз строку состояния, чтобы увидеть, как таймер изменяется в уведомлении. Попробуйте выйти из приложения, чтобы проверить обновление текста сообщения в уведомлении.
Использование фоновой обработки для сложных задач
Иногда пользователю не нужно знать о том, что происходит какое-то действие, например, если вы сохраняете данные об активности пользователя на сервере в целях доработки своего приложения, вам не нужно, чтобы пользователь видел уведомление. Поместите такие непрерывные задачи в фоновую очередь.
Для этого вы можете использовать:
- Пользовательский Background Service - по умолчанию сервис запускается в потоке пользовательского интерфейса. Чтобы избежать блокировки, создайте Сервис с обработкой процесса в фоновом потоке.
- IntentService - подкласс сервиса, который последовательно выполняет запросы с помощью рабочего потока. Начиная с Android 8, использовать его не рекомендуется. Кроме того, IntentService устарел с Android 11.
- JobIntentService - замена IntentService. Вместо сервиса он использует JobScheduler для выполнения заданий. В более ранних версиях, чем Android 8, он будет действовать так же, как IntentService. Для получения дополнительной информации прочтите официальную документацию JobIntentService.
- Фоновая работа с WorkManager - это общая концепция фоновой работы в Android. Используйте его, когда вы захотите выполнять периодические процессы в будущем или когда у вас есть некоторые ограничения в работе приложения.
Объяснение ограничений для фонового выполнения
Android 8 и более новые версии имеют ограничения при использовании Android Services в фоновом потоке:
- Система уничтожит сервис через некоторое время, если приложение запускает его в фоновом режиме. Когда приложение переходит в фоновый режим, у него есть всего лишь несколько минут, в течение которого оно может взаимодействовать с сервисами. После этого система останавливает работу всех запущенных служб.
- Система выдаст исключение, если сервис был запущен, когда приложение находилось в фоновом режиме и за пределами временного окна.
Дополнительные сведения см. В документации по ограничениям фонового выполнения.
Заметка
Из-за всех этих ограничений фоновые сервисы используются крайне редко. Как упоминалось выше, а также рекомендовано официальной документацией Android, лучшим решением для фоновой работы является использование Work Manager. Подробнее об этом читайте в разделе «Планирование задач с помощью Android WorkManager».
А теперь пора узнать о последнем виде сервисов.
Создание Bound Service
Bound Service - это все еще применение Service . Это компонент, с которым связываются другие компоненты, взаимодействуют с ним и осуществляют межпроцессное взаимодействие (IPC). Связанный компонент может быть Activity, другим сервисом, приемником трансляции или поставщиком контента.
Жизненный цикл связанного сервиса зависит от другого компонента приложения. Когда компонент отключается, привязанный сервис уничтожает себя. Следовательно, он не может работать в фоновом режиме вечно.
Однако ему не обязательно нужен связанный компонент - он также может запускаться и останавливаться. В этом случае есть возможность работать бесконечно.
Чтобы попробовать это, давайте добавим в Memo возможность воспроизводить звук.
Преобразование MusicService в реальный Service
Для реализации функции воспроизведения звука, давайте воспользуемся MusicService . Во-первых, измените его, чтобы получить доступ к основным методам сервиса.
Откройте MusicService.kt и расширьте его Service :
class MusicService : Service()
Как указано в его типе, другой компонент привяжется к этому сервису. Вы уже узнали об обязательном методе привязки к сервису, поэтому добавьте его в MusicService :
override fun onBind(intent: Intent?): IBinder = binder
Разница между последним применением onBind() и этой строчкой, очевидно, заключается в возвращаемом значении. Поскольку здесь вы привязываетесь к сервису, вам необходимо указать значение IBinder.
IBinder - это программный интерфейс Binder, который клиент использует для взаимодействия с сервисом. Его методы позволяют отправлять вызов объекту IBinder и получать вызов, поступающий в объект Binder. Чтобы узнать больше, ознакомьтесь с документацией IBinder.
Сейчас Android Studio выдает ошибку, потому что мы не определили переменную binder . Чтобы исправить это, добавьте в начало класса следующее:
private val binder by lazy
Здесь мы выполняем отложенную инициализацию binder , который является разновидностью MusicBinder . Внедрение MusicBinder - ваш следующий шаг.
Определение Binder
К этому сервису нельзя получить доступ за пределами этого приложения, и у него нет собственного процесса. Следовательно, нам необходимо создать интерфейс, который будет иметь доступ к контексту сервиса. Сделаем это, расширив класс Binder . Таким образом, все компоненты могут использовать все общедоступные методы из Binder или Service .
Теперь, чтобы определить интерфейс, добавьте эти строки внизу класса:
inner class MusicBinder : Binder()
MusicBinder вложен в другой класс и может получить доступ ко всем его методам. Он также может использовать все методы Binder . Внутри него вы создаете метод для получения контекста сервиса.
Определение методов сервисов для управления аудио
Этот сервис позволяет пользователю взаимодействовать со звуковыми иконками для воспроизведения, приостановки, остановки и перемешивания музыки. Для простоты, некоторые методы управления звуком уже определены. Однако сам музыкальный проигрыватель все еще не определен.
Чтобы создать музыкальный проигрыватель, добавьте этот код в initializeMediaPlayer() внутри MusicService.kt:
musicMediaPlayer = MediaPlayer.create(this, randomSongs.first()).apply
Здесь вы используете MediaPlayer для запуска непрерывно повторяющегося звука первой песни в randomSongs .
Еще одна интересная особенность такого сервиса заключается в том, что он может предоставить имя воспроизводимого трека. Внутри MusicService добавьте:
fun getNameOfSong(): String = resources.getResourceEntryName(randomSongs.first()) .replaceFirstChar < if (it.isLowerCase()) it.titlecase(Locale.ENGLISH) else it.toString() >.replace("_", " ")
В этом методе мы читаем название дорожки из ресурсов и изменяем String , чтобы она была более читаемой для пользователя.
Все сделано! Теперь воспользуемся этими методами из другого компонента.
Создание колбэка для подключения к службе
Чтобы использовать MusicService внутри других классов, нам нужно создать его экземпляр. Откройте MainActivity.kt и добавьте в начало класса следующее:
private var musicService: MusicService? = null
Здесь мы объявляем переменную, которая содержит экземпляр сервиса. В данный момент сервис является null . Мы назначим новое значение, когда активити подключится к службе с помощью интерфейса Binder . Чтобы уловить изменения состояния соединения, создайте колбэк соединения.
Добавьте код под инициализацией musicService :
// 1 private val boundServiceConnection = object : ServiceConnection < // 2 override fun onServiceConnected(className: ComponentName, service: IBinder) < val binder: MusicService.MusicBinder = service as MusicService.MusicBinder musicService = binder.getService() mainViewModel.isMusicServiceBound = true >// 3 override fun onServiceDisconnected(arg0: ComponentName) < musicService?.runAction(MusicState.STOP) musicService = null mainViewModel.isMusicServiceBound = false >>
Этот блок кода не сложно понять:
- Это колбэк для состояния подключения сервиса.
- Когда активити подключается к сервису, система использует экземпляр MusicBinder и getService() чтобы передать ссылку в musicService .
- Когда сервис отключается, воспроизведение звука прекратится, если ссылка на сервис еще не приняла значение null , при этом ссылка на сервис будет удалена.
Также устанавливается флаг isMusicServiceBound , который проверяет состояние привязки к сервису в обоих методах в соответствии с заданными вами параметрами. Это важно, чтобы избежать исключений DeadObjectException, которые удаленные методы генерируют при разрыве соединения.
Привязка к Сервису
Следующим шагом мы создадим привязку сервиса при запуске MainActivity . Найдите onStart() и добавьте:
if (!mainViewModel.isMusicServiceBound) bindToMusicService()
Здесь вы проверяете, имеет ли сервис уже привязку. Если нет, вы вызываете метод привязки.
Метод привязки еще не существует, поэтому добавьте его код ниже onDestroy() :
private fun bindToMusicService() < // 1 Intent(this, MusicService::class.java).also < // 2 bindService(it, boundServiceConnection, Context.BIND_AUTO_CREATE) >>
В этом куске кода мы:
- Заявляем о намерении запустить MusicService
- Предоставляем сервису информацию о нашем Intent вместе с колбэком соединения и флагом, который автоматически создает сервис, если привязка существует.
Чтобы избежать утечек памяти или ошибок, вам нужно добавить код для отмены привязки сервиса. Добавьте в unbindMusicService() следующее:
unbindService(boundServiceConnection)
Таким образом, вы говорите сервису выполнить onServiceDisconnected() в колбеке boundServiceConnection .
Использование методов сервисов
Как только сервис отключится, нам нужно остановить воспроизведение музыки. Добавьте следующий код над только что добавленной строкой unbindService() :
musicService?.runAction(MusicState.STOP)
Здесь мы используем экземпляр сервиса, чтобы остановить воспроизведение звука. Помните, что мы не могли вызывать методы из TimerService . Затем нам нужно было предоставить флаги через Intent . Однако здесь у нас есть соединение - привязка - к сервису, поэтому вы можете вызывать его методы и получать ответ.
Чтобы запустить звук, используем sendCommandToBoundService() . Теперь создадим общий вызов к изменению действия, чтобы уменьшить количество строк кода. Добавьте эту строку в sendCommandToBoundService() :
musicService?.runAction(state)
Таким образом, вы задаете сервису действие, которое он выполняет в соответствии с параметром sendCommandToBoundService() .
Осталось сделать еще кое- что! Как только саундтрек начнет воспроизводиться, сервис может предоставить информацию о названии песни. Перед тем, как это применить это в нашем приложении, нужно исправить момент с получением результата.
Внутри getNameOfSong() , замените строку, возвращающую текст «Unknown», на:
musicService?.getNameOfSong() ?: getString(R.string.unknown)
Здесь мы вызываем метод сервиса, который проверяет, какая звуковая дорожка в настоящее время воспроизводится, и возвращает необязательный результат String . Если результат равен null , вместо информации, полученной от сервиса, мы используем текст, взятый из ресурсов.
Запустите проект, затем нажмите значок «Play», чтобы запустить воспроизведение. Нажмите GET SONG NAME, чтобы увидеть название текущей песни во всплывающем сообщении. Наконец, вы можете наслаждаться музыкой во время игры!
Межпроцессные взаимодействия
Каждое приложение Android работает в собственном процессе с уникальным идентификатором процесса. Когда вы запускаете приложение, система Android генерирует процесс и выполняет основное действие в основном потоке. Плюс этого в том, что приложение живет в изолированной среде, и другие приложения не могут его прервать.
Исходя из этого, Android позволяет запускать компоненты в другом процессе, который не используется для запуска приложения. Для этого вам нужно использовать тег процесса внутри AndroidManifest.xml. У процесса может быть случайное имя, например myNewProcess.
Многопроцессорность повышает безопасность вашего приложения и улучшает управление памятью. Однако, если вы его используете, вам нужно найти способ взаимодействия между различными процессами.
Вам нужно включить только один программный интерфейс - IBinder, но Android предоставляет три способа его определения:
- Расширение подшивки
- Использование мессенджера
- Использование AIDL
Первый способ мы уже рассмотрели в этом туториале, но не стесняйтесь изучить и другие.
Что дальше?
Загрузите конечный проект, если что-то у вас не получилось.
Вы узнали много нового о сервисах Android и о том, как их по-разному использовать. Сервисы полезны для оптимизации приложений и улучшения взаимодействия с пользователем.
Android 12 внесет некоторые изменения в уведомления переднего плана. Ознакомиться с ними можно в официальной документации по Foreground Services.
Об открытости данных в Android-приложениях

Немного информации о том, какие данные в вашем приложении могут быть доступны для других программ и какие меры можно предпринять, чтобы это предотвратить.
Компоненты
Архитектуру Android-приложения можно представить в виде набора компонент (activities, services и пр.) и связей между ними. Android API предоставляет несколько стандартных способов общения между компонентами приложения: старт активити/сервис через контекст, отправка сообщений-интентов, использование ServiceConnection или ContentProvider’а и другое. Обо всём этом можно почитать в любом туториале о передаче данных в Android. Однако, есть один нюанс, о котором, как правило, умалчивается. Речь идет о доступности ваших компонент и данных, передаваемых между ними, для других приложений.
Activity
Для запуска одной из своих активити вы, наверняка, используете специально подготовленный intent:
Intent myIntent = new Intent(CurrentActivity.this, NextActivity.class); startActivity(myIntent);
Используя Context и Class, которые вы передали в конструктор интента, Android определяет package приложения и сам класс нужной активити, после чего запускает её.
Вы также можете попробовать запустить чужую активити, зная ее package name и class name:
Intent intent = new Intent(); /* Одна из активити стандартного приложения Контакты*/ intent.setClassName("com.android.contacts", "com.android.contacts.activities.PeopleActivity"); startActivity(intent);
Результат — запущенная активити.
Значит ли это, что все активити могут быть запущены сторонним приложением? Нет. Если заменить в предыдущем примере classname на
intent.setClassName("com.android.contacts", "com.android.contacts.preference.ContactsPreferenceActivity");
то мы получим java.lang.SecurityException.
На самом деле разработчик приложения сам решает, какие активити будут доступны, а какие нет, указывая для каждой активити в манифесте тэг android:exported равным true или false соответственно. Значение по умолчанию для этого тега ‒ false, т.е. все активити в AndroidManifest.xml, не имеющие этого тега, доступны только внутри приложения.
Однако, посмотрев на манифест приложения Контакты (например, тут) можно заметить, что у PeopleActivity нет тега android:exported=«true», почему же нам удалось её запустить? Ответ можно найти в официальной документации: если активити содержит какой-либо интент-фильтр, то значением по умолчанию для android:exported будет true.
Разработчику приложения не нужно беспокоиться о видимости своих activity вне приложения до тех пор, пока в какой-либо активити не появляется intent-filter. Как только вы объявляете intent-filter, спросите себя, готовы ли вы к тому, что эта активити может быть запущена кем-либо еще. Если нет — не забудьте указать .
Service
Видимость сервиса определяется также как и видимость активити, и мы бы не стали уделять сервисам отдельный пункт, если бы не одно “но”. В случае с сервисом, его видимость может завести куда дальше, чем незапланированный старт, как в случае с активити. Если в вашем сервисе предусмотрена возможность биндинга, то этой возможностью могут воспользоваться и другие и получить ссылку на ваш сервис через ServiceConnection. Давайте рассмотрим пример подключения к стандартному сервису воспроизведения музыки.
Примечание: следующий пример актуален только на достаточно старых версиях плеера. В свежей версии разработчики разобрались с флагом exported и установили его в false 🙂
Intent intent = new Intent(); /* Сервис проигрывания аудио - часть стандартного плеера */ intent.setClassName("com.android.music", "com.android.music.MediaPlaybackService"); ServiceConnection conn = new MediaPlayerServiceConnection(); /* Подключение к сервису */ bindService(intent , conn, 1); class MediaPlayerServiceConnection implements ServiceConnection < public void onServiceConnected(ComponentName name, IBinder boundService) < Log.i("service connection", "Connected! Name: " + name.getClassName()); >public void onServiceDisconnected(ComponentName name) < Log.i("MediaPlayerServiceConnection", "Disconnected!"); >>
После биндинга в переменной boundService будет храниться ссылка на сервис. Далее вы можете воспользоваться java reflection или Android Interface Definition Language (AIDL), чтобы иметь возможность вызывать методы сервиса напрямую, в результате чего можно, например, остановить воспроизведение, если стандартный плеер уже что-то играет.
Рекомендации в этом случае такие же: установите для всех сервисов, имеющих какой-либо интент фильтр, если вы не планируете оставить его публичным.
ContentProvider
Основное предназначение ContentProvider’а — предоставление данных другим приложениям. Именно поэтому по умолчанию значение тега для этого компонента приложения равно true. Но основное предназначение — не единственное. ContentProvider часто используется:
- при реализации подсказок при поиске
- при обмене данных через Sync Adapter
- как уровень абстракции над базой данных
BroadCast
- Создание intent’а
- Отправка intent’а через Context#sendBroadcast(. )
- Получение intent’а всеми зарегистрированными BroadcastReceiver’ами.
intent.setPackage(“com.android.example.mypackage”)
или воспользоваться LocalBroadcastManager (доступен в support library), который также не даст улететь сообщению за границы приложения (на самом деле процесса):
LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
Более того, LocalBroadcastManager позволяет регистрировать приёмники, так что вы можете быть уверены, что получаемые такими приёмниками интенты тоже являются локальными, а не прилетают извне.
Ресурсы
Если коротко, то все используемые в приложении ресурсы доступны для чтения вне приложения. Для получения некоторых из них достаточно знать только package name целевого приложения:
PackageManager pm = getPackageManager(); /* Иконка приложения Youtube */ Drawable icon = pm.getApplicationIcon("com.google.android.youtube"); /* Логотип приложения Youtube */ Drawable logo = pm.getApplicationLogo("com.google.android.youtube"); ApplicationInfo applicationInfo = pm.getApplicationInfo("com.google.android.youtube", 0); /* Название приложения Youtube */ CharSequence applicationLabel = pm.getApplicationLabel(applicationInfo);
Если вам известна информация об активити внутри приложения, вы можете проделать подобные операции для каждой из них отдельно.
Узнав имя конкретного ресурса, можно получить и его:
Resources r = getPackageManager().getResourcesForApplication("com.google.android.youtube"); /* Картинка-превью для виджета Youtube * youtube_widget_preview - имя файла * drawable - директория, в которой находится файл * com.google.android.youtube - package name */ int "drawable", "com.google.android.youtube"); Drawable drawable = r.getDrawable(id); ImageView iw = new ImageView(context); iw.setImageDrawable(drawable); rootView.addView(iw);

Этим способом мы смогли отобразить картинку-превью для виджета Youtube внутри собственного приложения.
Главная загвоздка с ресурсами в том, что здесь нет способа их скрыть. Хранение приватных данных в ресурсах — плохая идея, но если у вас есть такая необходимость, лучше хранить их в зашифрованном виде.
В завершении статьи хотелось бы сказать еще несколько слов о файловой системе в Android. Этот вопрос уже хорошо освещен, многие знают, что в Android для каждого приложения создается собственная директория /data/data/«app package name», доступ к которой имеет только само приложение (или группа приложений c одним sharedUserId). В этой директории находятся файлы настроек SharedPreferences, файлы базы данных приложения, кэш и многое другое, однако, не стоит полагаться на защищенность этого хранилища. Файлы в этой директории недоступны только до тех пор, пока:
- вы создаете файлы с флагом Context.MODE_PRIVATE
- на устройстве не получен root-доступ
- приложение не будет обработано каким-либо менеджером backup’ов.
- android
- android development
- development
- mobile development
- Блог компании True Engineering
- Разработка под Android
Манифест приложения
Манифест - это набор правил, по которым работает приложение. Файл манифеста находится в корневой папке - AndroidManifest.xml - и содержит важную информацию, без которой система не сможет запустить приложение.
Основное содержимое манифеста:
- Имя пакета для приложения. Является идентификатором приложения.
- Описание возможностей компонентов приложения: Activity, Service, Broadcast Receiver.
- Процессы, в которых будут запускаться компоненты приложения.
- Разрешения, которые необходимо запросить у пользователя, чтобы у приложения был доступ к защищенным частям API, а также чтобы другие приложения могли обращаться к вашему приложению.
- Содержит список классов Instrumentation, которые при выполнении приложения предоставляют сведения о профиле и прочую информацию. Эти объявления присутствуют в файле манифеста только во время разработки и отладки приложения и удаляются перед его публикацией.
- Объявляет минимальный уровень API Android, необходимый для работы приложения.
- Перечисляет связанные библиотеки.
Основные правила манифеста:
- Обязательно надо указать в манифесте два тега: и . Оба можно указать только один раз.
- Большинство тегов можно указывать несколько раз или не указывать совсем. Это зависит от того, что будет делать приложение.
- Тег должен следовать за тегом своей . Но многие теги одного уровня могут следовать в любом порядке.
- Все значения задаются только в виде атрибутов. Некоторые атрибуты надо прописывать обязательно, некоторые ‒ нет. Если атрибут не прописан ‒ используется значение по умолчанию.
- Большинство атрибутов должны начинаться с префикса android . Только некоторые атрибуты тега manifest могут не иметь этого префикса.
- Многие элементы соответствуют объектам Java. Например, если вы указываете имя для activity с помощью атрибута name , то в состав имени должно входить полное обозначение пакета.
- Если элементу нужно указать несколько значений, то он всегда приводится повторно, вместо перечисления нескольких значений в одном элементе.
Структура файла
Ниже представлена структура манифеста со всеми возможными элементами. А далее рассмотрим каждый элемент отдельно. Для быстрого перехода к интересующему элементу кликайте по содержанию справа.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
Пространство имен Android, оно всегда одно и то же.
Корневой элемент манифеста. Является обязательным.
Разрешения, которые должны быть запрошены при установке приложения для его нормального функционирования.
В некоторых случаях данные разрешения могут повлиять на фильтрацию вашего приложения в Google Play. Например, если вы запрашиваете разрешение на использование камеры, то Google Play логично предположит, что для работы вашего приложения необходима камера и будет фильтровать устройства, на которых ее нет. Чтобы корректно управлять данной фильтрацией, нужно использовать .
Атрибуты:
- android:name - имя разрешения. Это может быть разрешение, определенное в элементе данного приложения, разрешение, определенное в другом приложении или одно из стандартных системных разрешений.
- android:maxSdkVersion - самый высокий уровень API, на котором это разрешение должно быть предоставлено вашему приложению. Полезно использовать, когда с определенного уровня API запрашивать разрешение больше не требуется.
Разрешения, которые должны запросить другие приложения для получения доступа к вашему приложению. Можно использовать существующие, либо создать собственные.
Атрибуты:
- android:name - имя разрешения, будет использоваться в коде для ссылки на разрешение. Имена должны быть уникальными, так как если имя вашего разрешения совпадёт с именем разрешения другого приложения (которое уже установлено пользователем), то система не разрешит пользователю установить ваше приложение.
- android:label - имя разрешения, отображаемое пользователю.
- android:description - описание, которое должно объяснять пользователю для чего текущее разрешение требуется.
- android:icon - иконка разрешения.
- android:permissionGroup - определяет принадлежность к группе разрешений. Значением этого атрибута является имя группы, которое должно быть объявлено в элементе (в вашем или другом приложении).
- android:protectionLevel - уровень защиты, который говорит системе, как она должна себя вести, если текущее разрешение было кем-то запрошено. У данного атрибута может быть много значений, подробнее в документации.
Элемент позволяет приложению объявлять пространство имён разрешений, в которое оно может динамически во время выполнения добавлять новые разрешения.
Атрибуты:
- android:icon - иконка, которая будет отображаться для всех разрешений в дереве.
- android:label - имя группы (дерева разрешений).
- android:name - имя, которое будет добавляться как префикс всем разрешениям в древе. Должно быть уникальным, для этого рекомендуется, чтобы оно содержало более двух сегментов, разделенных точками.
Объявляет имя категории, в которую можно сгруппировать все логически связанные разрешения. Добавить разрешение в группу можно с помощью атрибута permissionGroup элемента . Разрешения из одной группы в пользовательском интерфейсе отображаются вместе.
Атрибуты:
- android:description - описание группы, отображаемое пользователю. Должно быть полным и понятным.
- android:icon - иконка.
- android:label - имя группы, отображаемое пользователю.
- android:name - имя группы, которое используется в коде для назначения категории атрибуту permissionGroup элемента .
Объявляет класс Instrumentation, который дает возможность отслеживать все взаимодействия, которые система выполняет с приложением. Обычно используется при отладке и тестировании приложения и удаляется из release-версии приложения.
Атрибуты:
- android:functionalTest - если true - класс Instrumentation будет работать как функциональный тест, если false - нет.
- android:handleProfiling - будет ли класс Instrumentation включать / выключать профилирование. True - будет включать / выключать, при этом позволяет нацеливаться на определенный набор операций, false - профилирование будет выполняться все время.
- android:icon - иконка, которая будет присвоена классу Instrumentation .
- android:label - заголовок для класса Instrumentation .
- android:name - полное имя класса, который реализует Instrumentation .
- android:targetPackage - приложение, с которым будет работать объект Instrumentation . Приложение идентифицируется по имени пакета, назначенному в его файле манифеста.
- android:targetProcesses - процессы, с которыми будет работать объект Instrumentation . Можно перечислить через запятую или указать "*" для взаимодействия со всеми процессами, которые заданы атрибутом android:targetPackage .
Совместимость приложения с указанной версией Android. Когда приложение будет устанавливаться, система сравнит указанный уровень API с API устройства. Также Google Play использует этот элемент, чтобы отфильтровывать устройства, которые не соответствуют указанному уровню API.
Атрибуты:
- android:minSdkVersion - минимальный уровень API, необходимый для работы приложения. Если значение не указать, то система будет думать, что ваше приложение поддерживает все уровни API. Поэтому этому атрибуту обязательно нужно задавать значение.
- android:targetSdkVersion - максимальный уровень API, под который приложение тестировалось. Этот атрибут помогает системе понять, можно ли использовать свежий функционал в приложении или оно может оказаться к нему не готовым. Помогает избежать ситуаций, когда после обновления версии API поменялись какие-либо ключевые значения от чего приложение может работать некорректно.
- android:maxSdkVersion - максимальный уровень API, который будет поддерживаться приложением. Если значение будет меньше, чем уровень API устройства, то система не позволит установить приложение. Если приложение было установлено, но позже устройство было обновлено до более высокого уровня API, чем указанный, то приложение перестанет отображаться, что фактически приравнивается к удалению приложения с устройства. Поэтому этот атрибут рекомендуется не указывать.
Требуемая для приложения аппаратная и программная конфигурация устройства. Например, можно указать, что для работы приложения требуется физическая клавиатура. Таким образом можно избежать ситуаций, когда приложение будет установлено на устройство, где оно не сможет работать. Как правильно без этого элемента можно обойтись и его даже не рекомендуют использовать, потому что приложение должно стремится к универсальности и совместимости на большинстве устройствах, а некоторые ограничения можно указать с помощью других атрибутов (менее жестких в плане ограничения устройств).
Атрибуты:
- android:reqFiveWayNav - если true , то приложению требуется устройство ввода, поддерживающее навигацию вверх, вниз, влево, вправо, а также нажатие выделенного элемента. К таким устройствам относятся трекболы и D-pad. В принципе устарело.
- android:reqHardKeyboard - если true , то нужна аппаратная клавиатура.
- android:reqKeyboardType - позволяет задать тип клавиатуры. Варианты:
- undefined - требование к наличию клавиатуры не определено.
- nokeys - приложению не требуется клавиатура.
- qwerty - требуется стандартная QWERTY-клавиатура.
- twelvekey - требуется клавиатура, состоящая из цифр и знаков звёздочки (*) и решетки (#) .
- undefined - требование к навигации не определено.
- nonav - навигационное устройство не требуется.
- dpad - требуется d-pad.
- trackball - требуется трекбол.
- wheel - требуется навигационное колесо (интересно, что это).
- undefined - требование не задано.
- notouch - сенсорный экран не требуется.
- stylus - требуется сенсорный экран, который управляется стилусом.
- finger - требуется сенсорный экран, которым можно управлять пальцами.
Объявляет одну или несколько аппаратных или программных функций, требуемых для работы приложения.
Атрибуты:
- android:name - имя аппаратной или программной функции. Имена стандартные, чувствительны к регистру.
- android:required - если true , то приложение не может функционировать без функции, заданной атрибутом name элемента .
- android:glEsVersion - версия OpenGL ES , требуемая для работы приложения.
Позволяет указать размеры экрана, которые поддерживаются приложением. Если размер экрана будет больше, чем поддерживается приложением, то включится режим совместимости, что не очень хорошо, так как этот режим может вызвать размытие интерфейса из-за неправильного масштабирования. Поэтому рекомендуется создавать свои макеты под каждый размер экрана. Кроме того, с помощью отдельного макета можно оптимизировать интерфейс, например, чтобы на экране отображалось два окна (двупанельный интерфейс). Как поддерживать разные размеры экранов описано документации здесь.
По умолчанию, для каждого атрибута установлено значение true , поэтому используйте этот элемент, чтобы указать какие экраны вы не поддерживаете.
Атрибуты:
- android:smallScreens
- android:normalScreens
- android:largeScreens
- android:xlargeScreens
- android:anyDensity
Данные атрибуты были добавлены в API 13 (Android 3.2):
- android:requiresSmallestWidthDp - позволяет указать минимальную ширину экрана (наименьшая сторона устройства), чтобы Google Play отфильтровывал неподходящие устройства.
- android:compatibleWidthLimitDp - позволяет указать максимальную наименьшую ширину экрана. Если же значение превысит заданное, то пользователю будет предложено включить режим совместимости.
- android:largestWidthLimitDp - позволяет указать максимальную наименьшую ширину экрана. Если же значение превысит заданное, то будет принудительно включен режим совместимости без возможности отключения.
Позволяет задать все возможные размеры экранов, с которыми совместимо приложение. Данный элемент может быть указан в манифесте только один раз, зато внутри него можно добавлять тег с указанием поддерживаемого размера экрана.
Этот элемент носит исключительно информационный характер. С помощью него Google Play фильтрует устройства с неподдерживаемыми экранами.
Конфигурация экрана, которая не указана в этом элементе, считается неподдерживаемой приложением.
Для тех же целей проще использовать элемент , так как в нем не нужно указывать конкретные размеры экранов, а достаточно выставить логическое значение для нужного атрибута.
Определяет формат сжатия текстур GL. Элемент является информационным, т.е. необходим Google Play для фильтрации устройств, которые не поддерживают заданные параметры.
Обязательный элемент манифеста, который содержит информацию о всех компонентах приложения (activity, service, receiver, provider), а также атрибуты, которые могут влиять на эти компоненты. В манифесте может быть только один элемент .
Дочерние элементы:
Атрибуты:
- android:allowTaskReparenting - позволяет activity перемещаться из task’а, который ее запустил, в task переднего плана, с которым activity имеет общее taskAffinity -значение. Если атрибут задан в теге , то оно применяется ко всем activity. Но можно задавать и для конкретной activity.
- android:allowBackup - позволяет указать будет ли выполняться резервное копирование приложения. По умолчанию true , если указать false , то приложение не будет обслуживаться сервисом Backup Manager - интерфейс, через который приложение запрашивает операции резервного копирования и восстановления.
- android:allowClearUserData - применимо только для системных приложений, для обычных приложений игнорируется. Позволяет приложению сбрасывать пользовательские данные.
- android:allowNativeHeapPointerTagging - вот, что я поняла, почитав про этот тег: он добавлен временно и позволяет отключить Pointer Tagging. Оставляю ссылку на документацию, может поможет.
- android:backupAgent - следует использовать, если приложение выполняет резервное копирование. Значением данного атрибута является подкласс класса BackupAgent , где указывается, что именно будет сохранено в облачном хранилище и восстановлено после реинсталляции приложения.
- android:backupInForeground - если значение true , то резервное копирование может производиться, когда приложение активно. Вообще система закрывает приложение, если идет резервное копирование, поэтому включение этого параметра может повлиять на работу приложения.
- android:banner - картинка, которая будет отображаться на главном экране Android TV. При использовании этого атрибута в элементе , картинка применится ко всем компонентам приложения. Можно задать индивидуальную картинку для каждой . Актуально только при разработке приложения для Android TV. Подробнее.
- android:debuggable - раньше этот тег использовался для того, чтобы запускать приложение в режиме отладки на реальном устройстве. На данный момент, если приложение запускает на реальном устройстве в режиме отладки, этот тег вставляется автоматически со значением true и также автоматически удаляется после завершения. В любом случае, этот тег нужно удалять из релизной версии.
- android:description - краткое описание приложения, отображаемое пользователю.
- android:directBootAware - включает режим Direct Boot, при котором приложение получает ограниченный доступ к файлам сразу после запуска системы, но до того, как пользователь разблокирует устройство. Может использоваться такими приложениями, как будильники, мессенджеры. Подробнее в статье из блога Google или в документации.
- android:enabled - если false , то система не может создавать экземпляры компонентов приложения. Не знаю какой смысл добавлять этот атрибут в элемент , но его можно использовать для отключения конкретного дочернего компонента, если он не поддерживается какой-либо версией Android. Пример.
- android:extractNativeLibs - если true (по умолчанию), то нативные библиотеки приложения будут храниться в сжатом виде в APK, а при установке извлекаться PackageManager’ом и помещаться в / data / app /. При этом размер APK будет меньше, так как библиотеки сжаты. Но приложение будет дольше устанавливаться и занимать больше места в установленном виде.
Если false (по умолчание, если версия Gradle 3.6.0 и выше), то нативные библиотеки будут хранится в APK в несжатом виде. Размер APK увеличится, но и занимаемое после установки пространство уменьшится. Подробнее. - android:fullBackupContent - значением атрибута является ссылка на xml файл, в котором содержатся правила для автоматического резервного копирования. Подробнее о том, как оформлять файл, можно почитать здесь. Этот атрибут не является обязательным, так как большинство файлов приложения и так включено в автоматическое резервное копирование. Здесь перечислены эти файлы.
- android:fullBackupOnly - данный атрибут используется, если в приложении реализован кастомный BackupAgent. В этом случае присваивается значение true - будет использовано автоматическое резервное копирование вместо key/value .
- android:gwpAsanMode - указывает следует ли использовать GWP-ASan - анализатор памяти, который позволяет находить и исправлять проблемы, вызванные небезопасной работой с памятью.
- android:hasCode - если приложение не содержит Java-кода, а полностью реализовано на основе программного интерфейса NDK API, то данного атрибуту необходимо установить значение false .
- android:hasFragileUserData - если true , то когда пользователь будет удалять приложение, ему будет предложено сохранить данные приложения.
- android:hardwareAccelerated - включает аппаратное ускорение для всего приложения. Если minSDK или targetSDK больше или равно 14, то значение по умолчанию true . Подробнее.
- android:icon - иконка для всего приложения. Если данный атрибут использовать внутри, например, activity, то у этой activity будет персональная иконка.
- android:isGame - является ли приложение игрой. Нужен системе для сортировки и группировки всех игр вместе.
- android:killAfterRestore - используется системными приложениями, пока не ясно с какой целью.
- android:largeHeap - используется, если приложению может понадобится расширение размера кучи.
- android:label - название приложения, отображаемое пользователю.
- android:logo - логотип приложения.
- android:manageSpaceActivity - когда мы просматриваем приложения через системные настройки, можно заметить, что у некоторых приложений (как правило системных) вместо кнопки “Очистить кэш” есть кнопка “Управление памятью” (или вроде того). При нажатии на эту кнопку открывается другое окно, в котором можно выбрать, какие именно данные удалить. Такую функцию можно добавить к любому приложению с помощью этого атрибута. Значением атрибута является имя подкласса activity, которую система должна запустить при нажатии на эту кнопку.
- android:name - имя подкласса Application. Зачем этот атрибут добавлять пока непонятно.
- android:networkSecurityConfig - задается имя xml -файла, который содержит конфигурацию сетевой безопасности.
- android:permission - имя разрешения, которое должно быть предоставлено для использования приложения. Применяется ко всем компонентам приложения.
- android:persistent - в документации неявно сформулировано, что данный атрибут могут использовать только системные приложения. Если приложение не является системным, то атрибут игнорируется. А предназначение атрибута следующее - позволяет запускать фоновую службу и предотвращать ее автоматическое уничтожение. Взято отсюда.
- android:process - позволяет указать процесс, в котором будет работать приложение. По умолчанию имя процесса соответствует имени пакета, заданного в теге . Можно использовать для запуска компонентов двух приложений в одном процессе, но при условии, что оба приложения подписаны одним сертификатом. Если данному атрибуту было присвоено имя, которое начинается с двоеточия, то создается приватный процесс конкретно для этого приложения. Если же имя начинается с маленькой буквы, то создается глобальный процесс, который можно использовать совместно с другими приложениями.
- android:restoreAnyVersion - если true , то BackupManager будет пытаться восстановить данные приложения из облачного хранилища, даже если текущая версия приложения не совпадает с версией, которая выполняла резервное копирование, что в свою очередь может указывать на несовместимость данных.
- android:requestLegacyExternalStorage - атрибут добавлен относительно недавно в связи с тем, что в Android 10 приложениям запретили неограниченное обращение к файлам в хранилище и, если я правильно поняла, этот тег позволяет обойти эту защиту. Тем не менее решение временно, просто дали время для внесения изменений.
- android:requiredAccountType - можно задать тип учетной записи, которая требуется для работы приложения (например, com.google).
- resizeableActivity - поддерживает ли приложение многооконный режим. Можно задать для конкретной activity. Если target API 24 и выше, то значение по умолчанию = true .
- android:restrictedAccountType - позволяет указать, что ограниченным профилям разрешен доступ к основной учетной записи. Ограниченные профили были введены для совместной работы с одной учетной записи, но с некоторыми ограничениями (без доступа к почте, игровому магазину, календарю итд.).
- android:supportsRtl - если true и targetSdkVersion - 17 и выше, то приложение будет поддерживать макеты “справа налево”.
- android:taskAffinity - указывается имя task’а. Позволяет изменять поведение activity, например, чтобы в одном приложении activity работали в разных task’ах или activity разных приложений работал в одном. Данный атрибут будет работать при следующих обстоятельствах:
- Intent, который запускает activity, содержит флаг FLAG_ACTIVITY_NEW_TASK .
- У запускаемой activity установлен атрибут allowTaskReparenting = true . Атрибут означает, что activity может перемещаться между task’ом, который ее вызвал, и task’ом, который указан в taskAfinity - в зависимости от того, какой task сейчас активен.
Объявляет activity. Каждая activity объявляется отдельным элементом . Если activity не будет добавлена в манифест, то система ее не увидит и не сможет запустить. Данный тег обязательно должен находится внутри элемента .
Дочерние элементы:
Атрибуты:
- android:allowEmbedded - указывает, что activity может быть запущена как вложенный дочерний элемент другой activity. Например, это может понадобится при внедрении в приложение Bubbles и при разработки для Android Wear.
- android:allowTaskReparenting - позволяет activity перемещаться из task’а, который ее запустил, в task переднего плана, с которым activity имеет общее taskAffinity -значение.
- android:alwaysRetainTaskState - если значение этого атрибута для корневой activity = true , то стек не будет чиститься и полностью восстановится даже после длительного времени. Атрибут есть смысл указывать только для корневой activity, так как для всех остальных заданное значение будет игнорироваться.
- android:autoRemoveFromRecents - если данный атрибут задать нескольким activity и если эти activity будут запущены пользователем, то они поместятся в один task, который будет находится на экране до тех пор, пока пользователь не закроет последнюю activity в этом task’е. При этом task сам завершит свою работу, так сказать самоликвидируется. Атрибут отменяет действие флага FLAG_ACTIVITY_RETAIN_IN_RECENTS .
- android:banner - картинка, которая будет отображаться на главном экране Android TV. При использовании этого атрибута в элементе , картинка применится ко всем компонентам приложения. Можно задать индивидуальную картинку для каждой . Актуально только при разработке приложения для Android TV.
- android:clearTaskOnLaunch - если значение этого атрибута для корневой activity = true , то back stack будет чиститься моментально, как только пользователь покинет task. Полная противоположность alwaysRetainTaskState .
- android:colorMode - отображение activity в режиме широкой цветовой гаммы (для отображения более ярких цветов). Если устройство не поддерживает такой режим, то атрибут игнорируется.
- android:configChanges - в данном атрибуте можно перечислить, какие изменения в конфигурации activity будут игнорироваться, т.е. данные изменения в конфигурации не будут вести к пересозданию activity. Вместо этого activity вызовет метод onConfigurationChanged() . Использование данного атрибуты считается плохим тоном, так как изменения в конфигурации никак не обрабатываются. Данный атрибут можно спокойно использовать, если в приложении все ресурсы (макеты, картинки итд.) для всех ориентаций экрана одинаковы.
- android:directBootAware - включает режим Direct Boot, при котором конкретная activity получает ограниченный доступ к файлам сразу после запуска системы,но до того, как пользователь разблокирует устройство. Может использоваться такими приложениями, как будильники, мессенджеры. Подробнее в статье из блога Google или в документации.
- android:documentLaunchMode - позволяет указать каким образом новый экземпляр activity будет добавляться в task. Например, на экране обзора task’ов могут быть отображены несколько документов из одного приложения. Подробнее.
- android:enabled - если false , то система не может создавать экземпляр activity. Можно использовать для отключения activity, которая не поддерживается какой-либо версией Android (хотя конкретный пример использования сложно представить).
- android:excludeFromRecents - если true , а также если атрибут задан для корневой activity, то task не будет отображаться на обзорном экране последних запущенный приложений. Подробнее.
- android:exported - если true , то activity может быть запущена компонентами других приложений, если false - activity может быть запущена только компонентами одного и того же приложения, либо приложениями с одинаковыми идентификаторами пользователя. В любом случае для запуска activity нужно знать ее точное имя класса.
- android:finishOnTaskLaunch - атрибут похож на clearTaskOnLaunch , но в отличии от него из task’а будет удалена та activity, для которой значение этого атрибута = true. Т.е. activity будет частью task’а только для текущего сеанса и если пользователь свернет task, то activity будет из него удалена.
- android:hardwareAccelerated - включает аппаратное ускорение для текущей activity. Подробнее.
- android:icon - иконка для текущей activity.
- android:immersive - включает режим погружения, в котором приложение отображается на весь экран, скрывая системные панели и панель навигации. Для того, чтобы увидеть эти панели пользователю нужно свайпнуть от верхнего или нижнего края экрана к центру.
- android:label - заголовок activity, отображаемый пользователю. Если не задан, то используется заголовок, установленный в элементе .
- android:launchMode - данный атрибут можно указать для каждой activity в манифесте. Имеет несколько значений:
- standard - при запуске activity создается новый экземпляр в стеке. Activity может размещаться в стеке несколько раз.
- singleTop - activity может располагаться в стеке несколько раз. Новая запись в стеке создается только в том случаи, если данная activity не расположена в вершине стека. Если она на данный момент является вершиной, то у нее сработает onNewIntent() метод, но она не будет пересоздана.
- singleTask - создает новый task и устанавливает activity корневой для него, но только в случае, если экземпляра данной activity нет ни в одном другом task’е. Если activity уже расположена в каком либо task’е, то откроется именно тот экземпляр и вызовется метод onNewIntent() . Она в свое время становится главной, и все верхние экземпляры удаляются, если они есть. При этом, если activity была вытащена из task’а в бэкграунде, то мы переключимся на этот task и его стек. Только один экземпляр такой activity может существовать.
- singleInstance - тоже что и singleTask , но для данной activity всегда будет создаваться отдельный task и она будет в ней корневой. Данный флаг указывает, что activity будет одним и единственным членом своего task’а.
- normal - значение по умолчанию. Task’и могут быть добавлены только с помощью метода startLockTask() .
- never - этот режим доступен только для системных приложений, для всех остальных игнорируется. В этом режиме task’и не запускаются и пользователь не может их закрепить на обзорном экране.
- if_whitelisted - если activity прошла авторизацию у Device Policy Controller, то применяется значение always , если не прошла - normal .
- always - режим доступен только для системных приложений.
- Intent, который запускает activity, содержит флаг FLAG_ACTIVITY_NEW_TASK .
- У запускаемой activity установлен атрибут allowTaskReparenting = true . Атрибут означает, что activity может перемещаться между task’ом, который ее вызвал, и task’ом, который указан в taskAfinity - в зависимости от того, какой task сейчас активен.
Данный элемент задает activity псевдоним и в манифесте должен следовать сразу после этой activity. Данный тег обязательно должен находится внутри элемента .
Когда мы разрабатываем приложение, то периодически можем менять названия классов, изменять activity, которая будет запускаться первой итд. И вроде ничего в этом страшного нет, но пользователя может смутить, например, исчезновения иконки приложения с главного экрана (потому что мы ранее изменили имя стартовой activity). Чтобы этого избежать используется данный элемент. Т.е. мы можем стартовой activity присвоить псевдоним и при запуске приложения система будет осуществлять поиск этой activity именно по псевдониму, а не по имени класса, который ее реализует. И если мы назначим стартовой другую activity, то пользователь уже не потеряет иконку на главном экране, так как псевдоним остался без изменений.
На эту тему есть интересная статья.
Дочерние элементы:
Атрибуты:
- android:enabled=["true" | "false"] - если false , то система не может создавать экземпляр activity, для которой создан данный псевдоним.
- android:exported=["true" | "false"] - если true , то activity может быть запущена под псевдонимом компонентами других приложений, если false - activity может быть запущена под псевдоним только компонентами одного и того же приложения, либо приложениями с одинаковыми идентификаторами пользователя. При этом если у элемента отсутствуют intent-фильтры, то значение по умолчанию = false (так как подразумевается, что псевдоним предназначен для использования внутри приложения), если intent-фильтры заданы, то значение по умолчанию = true .
- android:icon="drawable resource" - иконка, которая будет отображаться пользователю во время работы с activity, запущенной по данному псевдониму.
- android:label="string resource" - заголовок, который будет отображаться пользователю во время работы с activity, запущенной по данному псевдониму.
- android:name="string" - имя псевдонима. Имя указывается произвольное, должно быть уникальным, но не должно ссылаться на существующий класс.
- android:permission="string" - имя разрешения, которое должно быть предоставлено для запуска activity под данным псевдонимом или для получения от activity какой-либо информации.
- android:targetActivity="string" - полное имя класса, который реализует activity и должен быть запущен через данный псевдоним. Имя, указанное в данном атрибуте, должно соответствовать имени, указанному в атрибуте android:name элемента , за которым в манифесте следует псевдоним.
Данный элемент позволяет записать какие-либо данные по типу “ключ-значение”. Доступ к этим данным можно получить из всего приложения (если объявлены в внутри тега ), либо можно передать необходимые для работы данные конкретной activity (объявляется внутри тега ).
Количество элементов неограничено. Все их значения в итоге будут собраны в один объект Bundle, а получить эти значения можно с помощью PackageItemInfo.metaData .
Например, в приложении используются уникальные шрифты. Чтобы избежать задержек при запуске приложения, хотелось бы загружать их предварительно. Для этого создается файл в ресурсах, в котором перечисляются эти шрифты:
1 2 3 4 5 6 7 8 9
// preloaded-fonts.xml
- @font/lato
- @font/lato_bold
После чего ссылка на этот файл указывается в мета-данных манифеста: