Android 14 уже здесь, поэтому я взялся за документацию, разбор экспертов и другие доступные ресурсы, чтобы разобрать все важные изменения, которые коснутся большинства прикладных разработчиков. Разберем новые ограничения на работу в фоне, изменения в Foreground Service, новые ограничения на работу Intent и BroadcastReceiver. В этом релизе много ограничений, но есть и новые фичи.
Если вам интересно следить за самыми последними новостями Android-разработки и получать подборку интересных статей по этой тематике, тогда вам стоит подписаться на Телеграм-канал Android Broadcast и мой YouTube канал "Android Broadcast"
Predictive Back Gesture в бой
Уже в Android 13 нас предупредили о том, что в следующей версии Android нас ждет обновление жестов назад и предсказуемая навигация с анимацией в виде превью экрана, куда мы будем переходить. Анимация показа пока все также не работает и ее нужно отдельно включать в настройках разработчика.
Также добавили возможность создавать собственные анимации перехода внутри приложения. Для этого в OnBackPressedCallback добавили метод handleOnBackProgressed(), который вызывается с прогрессом жеста “Назад”, а также методы handleOnBackPressed() и handleOnBackCancelled(), вызываемые при окончании анимации жеста “Назад” и его отмене соответственно. На экране вы можете видеть пример реализации собственной анимации с помощью библиотеки Jetpack AppCompat 1.8.0
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
...
val box = findViewById<View>(R.id.box)
val screenWidth = Resources.getSystem().displayMetrics.widthPixels
val maxXShift = (screenWidth / 20)
val callback = object : OnBackPressedCallback(enabled = true) {
override fun handleOnBackProgressed(backEvent: BackEvent) {
when (backEvent.swipeEdge) {
BackEvent.EDGE_LEFT ->
box.translationX = backEvent.progress * maxXShift
BackEvent.EDGE_RIGHT ->
box.translationX = -(backEvent.progress * maxXShift)
}
box.scaleX = 1F - (0.1F * backEvent.progress)
box.scaleY = 1F - (0.1F * backEvent.progress)
}
override fun handleOnBackPressed() {
// Жест "Назад" завершен
}
override fun handleOnBackCancelled() {
// Жест назад отменён
// сбрасываем объекты аниманиции в начальное состояние
}
}
this.onBackPressedDispatcher.addCallback(callback)
}
}
Также вместо метода overidePendingTransition(), который теперь помечен как deprecated, надо вызывать новый метод overrideActivityTransition(). Существующий метод не работает корректно с predictive back, так как имеет выше приоритет при выполнении анимации перехода.
// Новое API
overrideActivityTransition(
enterAnim = R.anim.open_trans,
exitAnim = R.anim.exit_trans,
backgroundColor = R.color.bgr_color
)
// deprecated
overridePendingTransition(R.anim.open_trans, R.anim.exit_trans)
Ограничение на установку старых приложений
Одним из первых анонсированных изменений в Android 14 станет невозможность установки приложений с targetSdk <= 23 (Android 6.0). Не путайте с minSdk. Про их различие у меня есть ролик на канале.
Изменение призвано остановить распространение приложений, которые не переходят на новые версии targetSdk и распространяются за пределами Google Play, чтобы пользоваться старыми уязвимостями Android. Таким образом, приложения могут получать все разрешения при установке, обходя механизм Runtime Permission.
При попытке установки такого приложения пользователь увидит ошибку, а в Logcat появится лог с подробностями
INSTALL_FAILED_DEPRECATED_SDK_VERSION: App package must target at least SDK version 23, but found 7
Установка приложений с любым targetSdk будет доступна через adb с указанием специального флага для игнорирования ограничения
adb install --bypass-low-target-sdk-block FILENAME.apk
На мой взгляд, это правильное ограничение для борьбы со старым софтом, а тем энтузиастам, кто это хочет сделать, оставили возможность через adb. Ставлю на то, что с каждым релизом Android будет подниматься минимальная разрешенная версия для установки. Возможно, это даже отвяжут от релизов и будут поднимать для всех версий Android.
Интернационализация
Теперь пользователи смогут менять региональные настройки независимо от выбранной в системе локали: единицы измерения температуры, первый день недели и системы исчисления. Разработчикам надо учитывать эту информацию при отображении информации в приложениях.
Grammatical Inflection API
Русский язык отличается от английского тем, что у нас есть понятие рода и, соответственно, меняются глаголы, предлоги и другие части языка. В новой версии Android появляется Grammatical Inflection API. Теперь приложению можно указать, какого пола пользователь через системный сервис GrammaticalInflectionManager, что приведет к пересозданию Activity, так как является частью конфигурации.
// Указываем пол пользователя для грамматики
val gIM: GrammaticalInflectionManager = сontext.getSystemService()
gIM.setRequestedApplicationGrammaticalGender(
Configuration.GRAMMATICAL_GENDER_FEMININE)
В ресурсах теперь можно создавать отдельные строки для разных полов
Квалификатор пола | Значение строк | Пример |
---|---|---|
Женский | feminine | res/values-ru-feminine/strings.xml |
Мужской | masculine | res/values-ru-masculine/strings.xml |
Нейтральный | neuter | res/values-ru-neuter/strings.xml |
Без информации | - | res/values-ru/strings.xml |
Нелинейное увеличение размера текста
Размер текста в Android рекомендуется задавать в sp, специальной единице измерения, которая учитывает увеличение размера текста заданного пользователям в системных настройках. Минусом является то, что весь текст увеличивался, и если маленький текст становился читаемым, то вот большие названия становились нечитаемыми из-за обрезания.
В Android 14 вводится новая система масштабирования текста в соответствии с Web Content Accessibility Guidelines (WCAG). Теперь будет применяться нелинейная шкала масштабирования. Это значит, что большой текст не будет увеличиваться также, как это происходит с мелким. Помимо этого, увеличили максимальный масштаб текста. На Pixel 7 Pro с Android 13 максимальным значением было 130%, в Android 14 - 200%.
Чтобы корректно перевести пиксели в sp и обратно, вам надо использовать TypedValue.applyDimension() и TypedValue.deriveDimension(). Эти API учитывают особенности нелинейного масштабирования текста, в отличие от простого умножения размера текста в SP на коэффициент масштабирования, который можно получить в Configuration.fontScale и DisplayMetrics.scaledDensity.
// Конвертируем 10 SP в PX
TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 10F, displayMetrics)
// Конвертируем 50 PX в SP
TypedValue.deriveDimension(TypedValue.COMPLEX_UNIT_SP, 50F, displayMetrics)
Share Sheet
В Android 14 обновили дизайн и возможности Share Sheet - системного диалога, который вы видите, когда делитесь текстом, картинкой или другим контентом.
На Pixel 7 Pro в разделе Direct Share вместо 4 элементов на Android 13 стало размещаться 5 в свежей версии ОС. Также изменили систему ранжирования того, что вы видите в этой секции. Используйте ShortcutManagerCompat.pushDynamicShortcut() , когда отправляете сообщение пользователю, а при создании ShortcutInfo вызывайте addCapabilityBinding() со значением “actions.intent.SEND_MESSAGE”
. Очень странное решение, сделанное таким образом, еще и без добавления констант.
ShortcutManagerCompat.pushDynamicShortcut(context,
ShortcutInfoCompat.Builder(context, id)
// Настраиваем Shortcut
.addCapabilityBinding("actions.intent.SEND_MESSAGE")
.build()
)
Теперь в стандартном системном UI появится возможность добавить дополнительные действия в видео объекта ChooserAction, содержащий иконку, название и PendingIntent, который будет отправлен при выборе действия. Ограничений на количество собственных действий нет.
Также есть еще одно специальное действие, которое предназначено для правки отправляемого контента, тоже в виду ChooserActions.
Все созданные дополнительные действия надо добавить в Intent через специальные новые EXTRA. Как это сделать, вы можете увидеть ниже.
val intent: Intent = // Создаем Inent и кладем контент для шаринга
val modifyAction: ChooserAction =
ChooserAction.Builder(modifyIcon, "Modify Share", modifyActionIntent).build()
// Задаем действие для правки контент
intent.putExtra(EXTRA_CHOOSER_MODIFY_SHARE_ACTION, modifyAction)
// Дополнительный действия, которые можно сделать с контентом
val customActions: Array<ChooserAction> = arrayOf(
ChooserAсtion.Builder(copyIcon, "Copy", copyIntent).build(),
ChooserAсtion.Builder(albumIcon, "Create Album", createAlbumIntent).build(),
ChooserAсtion.Builder(createLinkIcon, "Create link", createLinkIntent).build()
)
// Добавляем допонительные действия, которые можно сделать с контентом
inent.putExtra(Intent.EXTRA_CHOOSER_CUSTOM_ACTIONS, customActions)
// Создаем Intent для Share Sheet
val chooserIntent = Intent.createChooser(customActions, "Share 1 image")
// Запускаем Share Sheet
context.startActivity(chooserIntent)
Хотелось бы чтобы из коробки добавили какие-то стандартные действия, как это было в Android 13, но на момент выхода этой статьи я не смог найти никаких действий.
SCHEDULE_EXACT_ALARM запрещены по умолчанию
Для приложений с targetSdk 33+
В Android 12 появилось новое разрешение SCHEDULE_EXACT_ALARM для использования API AlarmManager, связанного с точным временем срабатывания будильников. В Android 13 появилось новое разрешение USE_EXACT_ALARMS. В Android 14 нового не добавили, но меняют работу SCHEDULE_EXACT_ALARM. Теперь оно не будет выдаваться всем приложениям с targetSdk 33 и выше. При обновлении устройства и восстановлении бэкапа разрешение будет выдано, а вот для новых установок - нет. В исключения попадают приложения, подписанные системным сертификатом и имеющие специальные привилегии, а также те приложения, для которых отключены оптимизации энергопотребления.
Тип Foreground Service становится обязательным
В Android 10 для всех Foreground Service появилась возможность объявить тип сервиса, которое указывает цель его запуска. В Android 14 становится обязательным указывать тип для всех Service, которые могут запускаться как Foreground, а в современном Android - это практически все случаи. Чтобы покрыть все варианты использования Foreground Service, добавили новые типы сервисов и каждый из них имеет специальное разрешение для объявления.
<!-- AndroidManifest.xml -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Рзарешение для запуска Foreground Service -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<!-- Рзарешение для запуска Foreground Service с типом dataSync -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
<application>
<service
android:name=".SyncService"
android:foregroundServiceType="dataSync"
/>
</application>
</manifest>
Нововведение позволит четко понимать, попадают ли операции, выполняемые в Service, под разрешенные категории. Система сможет лучше понимать, что делает приложение и не является ли это чем-то подозрительным. Google настоятельно рекомендует использовать WorkManager и другие специальные API, и в случае если они вам не подходят, реализовывать ForegroundService.
Google Play также займется проверкой Foreground Service на этапе загрузки сборок. Любое приложение с поддержкой Android 14 (targetSdk>= 34) должно подтвердить, что Service с объявленным типом задач нужен для работы приложения. В противном случае вам не опубликоваться в магазине
Для запуска Foreground Service, помимо добавления разрешения на запуск Foreground Service, надо будет добавлять разрешение на запуск сервиса определенного типа. Все эти разрешения имеют тип “normal”, т.е. разрешения выдаются автоматом при установке приложения и запрашивать их отдельно не придется. Помимо разрешения есть и другие требования, например, для Service по работе с камерой надо, чтобы в AndroidManifest было также разрешение CAMERA.
Тип | Разрешения | Цель |
---|---|---|
camera | FOREGROUND_SERVICE_CAMERA | Доступ к камере из фона, например, видеочаты |
connectedDevice | FOREGROUND_SERVICE_CONNECTED_DEVICE | Коммуникация с устройствами по Bluetooth, NFC и другим каналам |
dataSync | FOREGROUND_SERVICE_DATA_SYNC | Операции с процессингом данными на устройстве и сервером |
health | FOREGROUND_SERVICE_HEALTH | Фитнес приложения |
location | FOREGROUND_SERVICE_LOCATION | Навигация или предоставления доступа к местоположению |
mediaPlayback | FOREGROUND_SERVICE_MEDIA_PLAYBACK | Проигрывание аудио и видео в фоне |
mediaProjection | FOREGROUND_SERVICE_MEDIA_PROJECTION | Воспроизведение контента на внешнем дисплее (медиа, шаринг экрана) |
microphone | FOREGROUND_SERVICE_MICROPHONE | Запись аудио с микрофона в фоне |
phoneCall | FOREGROUND_SERVICE_PHONE_CALL | Приложения для звонков с использованием ConnectionService API |
remoteMessaging | FOREGROUND_SERVICE_REMOTE_MESSAGING | Передача сообщений между устройствами |
shortService | - | Высокоприоритетная короткая (до 3 минут) задача, которую необходимо закончить как можно быстрее |
systemExempted | FOREGROUND_SERVICE_SYSTEM_EXEMPTED | Специальный тип для системных приложений и приложений с разрешением для работы с exact alarm API в AlarmManager |
specialUse | FOREGROUND_SERVICE_SPECIAL_USE | Специальный тип, предназначенный для задач, которые не попали в остальные категории |
Все типы связаны с каким-то конкретным сценарием использования за исключением трех:
systemExempted - специальный тип, предназначенный для системных приложений и будильников, которые определяются по наличию разрешений SCHEDULE_EXACT_ALARM и USE_EXACT_ALARM.
Тип specialUse применяется в тех случаях, когда все другие типы Foreground Service вам не подошли. Его объявление, помимо типа и разрешения, требует указать причину использования этого типа в специальном свойстве в AndroidManifest. Если у вас есть идеи, какую операцию делать из всех стандартных типов, напишите о ней в комментариях.
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
<application>
<service
android:name=".SpecialService"
android:foregroundServiceType="specialUse">
<property
android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
android:value="Reason to use special service type" />
</service>
</application>
</manifest>
Тип
shortService
предназначается для выполнения операций, которые не могут быть прерваны или отложены. Данный тип не требует дополнительных разрешений, но имеет несколько особенных свойств: ограничен во времени выполнения (не больше 3 минут), не могут запускать другие Foreground Service, не перезапускаются при остановке процесса приложения системой и др.
Время запуска Service отсчитывается с момента вызова startForeground() и ожидается, что будет закончено вызовом stopSelf() или stopForeground(). По истечению таймаута выполнения будет вызван новый callback метод в Service - onTimeot(), который предупреждает, что у вас осталась последняя возможность остановить Service. В противном случае происходит ANR, даже если приложение выполняет другие операции в Foreground Service. Отключение оптимизаций энергопотребления для приложения не влияет на ограничения по времени выполнения.
Возможно, продление жизни Short Foreground Service в случае, если приложение в текущий момент своей жизни удовлетворяет запуску Foreground Service, то повторный запуск Short Services приведет к увеличению времени выполнения Service на 3 минуты.
Ограничения для неявных Intent
Для приложения с поддержкой Android 14 (targetSdk 34+)
В Android 14 продолжаются форсирования лучших практик для предотвращения использования зловредами возможностей доступа к внутренним компонентам приложений. Теперь неявные intent будут доставляться только экспортированным компонентам приложения, для неэкспортированных - надо делать явные Intent.
<activity
android:name=".AppActivity"
android:exported="false">
<intent-filter>
<action android:name="dev.androidbroadcast.action.APP_ACTION" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
// Выброситься исключение в случае targetSdk 34+
context.startActivity(Intent("dev.androidbroadcast.action.APP_ACTION"))
// Делаем явный Intent, указанием пакета приложения
val explicitIntent =
Intent("dev.androidbroadcast.action.APP_ACTION")
// Указываем пакет приложения где находится компонент
// В случае вашего приложения - пакет вашего приложения
.setPackage(context.packageName)
context.startActivity(explicitIntent)
Вторым изменением в работе механизма Intent является то, что любой мутабельный PendingIntent с неявным Intent приведет к выбрасыванию исключения.
Изменения работы BroadcastReceiver
Помимо этого изменяются поведения Intent, которые доставляются в BroadcastRecevier, зарегистрированные из кода для всех приложений независимо от targetSdk. Все Intent, которые они могут получить, НЕ будут доставляться, когда приложение находится в закешированном состоянии, т.е. свернуто и пользователь какое-то время им не пользовался. Доставка произойдет, когда приложение станет снова активным. Помимо этого, при отправке нескольких одинаковых Intent может быть доставлен только один из них. BroadcastReceiver-ы, зарегистрированные в AndroidManifest, будут работать, как и прежде.
Также стала строже регистрация BroadcastReceiver из кода. Теперь надо указывать, экспортируемые они или нет по аналогии, как это делается для всех компонентов в AndroidManifest. При регистрации надо будет указать флаг RECEIVER_EXPORTED или RECEIVER_NOT_EXPORTED. Изменение работает только для приложений с поддержкой Android 14.
class DataReceiver: BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
// Обарабатываем данные
}
}
val intentFilter = IntentFilter() // настраиваем IntentFilter
context.registerReceiver(DataReceiver(), intentFilter, Context.RECEIVER_NOT_EXPORTED)
Обновления JobScheduler
JobScheduler обзавёлся возможностью запускать задачи для долгих передач данных между устройством и сервером. Такой тип задач получил название user-initiated data transfer job. Запуск такого типа Job может быть осуществлен, только когда приложение видно пользователю или в случаях, когда приложение может запустить Activity из фона.
Для запуска user-initieated data transfer job необходимо в AndroidManifest указать разрешение RUN_USER_INITIATED_JOBS и добавить Service, который расширяет класс JobService. При создании JobInfo надо вызвать методы setUserInitiated() и выставить значение в true. Обязательным условием запуска является показ системного уведомления, что нужно сделать при вызове onStartJob()
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.RUN_USER_INITIATED_JOBS" />
<application>
<service
android:name="dev.androidbroadcast.CustomTransferService"
android:exported="false"
android:permission="android.permission.BIND_JOB_SERVICE" />
</application>
</manifest>
val networkRequest = NetworkRequest.Builder()
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED)
// Дополнительные требования к сетевому запросу
.build()
val jobInfo = JobInfo.Builder(1, ComponentName(context, CustomTransferService::class.java))
.setUserInitiated(true)
.setRequiredNetwork(networkRequest)
.setEstimatedNetworkBytes(ONE_GIGABYTE, ZERO_BYTES)
// ...
.build()
val jobScheduler: JobScheduler = checkNotNull(context.getSystemService())
jobScheduler.schedule(jobInfo)
class CustomTransferService : JobService() {
override fun onStopJob(job: JobParameters) {
if (job.jobId == JOB_ID) {
// Показываем уведомление
setNotification(job, NOTIF_ID, newNotification(), JobService.JOB_END_NOTIFICATION_POLICY_REMOVE)
val success = doDataTransfer()
jobFinished(job, wantsReschedule = !success)
return false
}
error("Job isnn't handled")
}
}
Для успешного старта понадобится выполнение всех условий запуска и наличие в системе ресурсов на её выполнение. Важным отличием user-initiated data transfer job является то, что на неё не распространяются квоты App Standby Buckets на запуск Job. При определенных условиях система всё также может остановить выполнение задачи, если посчитает это необходимым. Перед этим будет вызван метод onStopJob(), в котором вам рекомендуется сохранить текущий прогресс выполнения сетевой операции, чтобы после перезапуска не выполнять работу с начала, а доделать необходимую часть.
class CustomTransferService : JobService() {
override fun onStopJob(job: JobParameters): Boolean {
if (job.jobId == JOB_ID) {
// Сохраняем прогресс передачи данных
return true
}
}
}
Из API кажется, что пометка User Initiated может быть сделана для любого типа Job, но в документации по методу говорится, что в Android 14 он должен быть вызван только в комбинации с setRequiredNetwork() или setRequiredNetworkType(). Возможно, в будущих версиях Android мы увидим больше типов задач, которые могут быть инициированы пользователем и явно помечены.
На момент выхода Android 14 в Jetpack WorkManager, рекомендуемый Google к использованию вместо JobScheduler, пока явно не добавлял в API. Возможно, это случится в будущих релизах, либо изменение будет использоваться под капотом уже существующего API WorkManager.
Частичный доступ к фото и видео
По аналогии с iOS в Android делают частичный доступ к фото и видео. Теперь при запросе медиа из приложения пользователь будет видеть диалог, в котором будет предлагаться дать доступ ко всей медиа или только к отдельным фото/видео. Изменение будет работать для всех приложений, независимо от их targetSdk.
В Android 13 уже появились отдельные разрешения для доступа к фото и видео, а теперь появится еще одно дополнительное READ_MEDIA_VISUAL_USER_SELECTED, которое позволяет повторно запросить выбор к отдельным фото/видео. Новое разрешение должно использоваться в дополнение к уже существующим READ_MEDIA_IMAGES и READ_MEDIA_VIDEO, чтобы поддержать новое поведение. Его объявление означает, что вы поддерживаете из кода его повторный запрос.
<manifest xmlns:android="http://schemas.android.com/apk/res/android" />
<!-- Разрешение на доступ к фото и видео из Android 13 !-->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<!-- Доп. разрешение на повторный выбор медиа для доступа из Android 14 !-->
<uses-permission android:name="android.permission.READ_MEDIA_VISUAL_USER_SELECTED" />
</manifest>
Доступ к отдельным медиа будет выдаваться временно, а вот как надолго - в документации не описано. В случае если новое разрешение не объявлено, то отзываться частичный доступ будет сразу же при переходе приложения в фон или пользователь убивает приложение. Нечто подобное тому, как работает одноразовое разрешение. В связи с этим хранить состояние получения разрешения READ_MEDIA_VISUAL_USER_SELECTED нельзя и нужно проверять его каждый раз.
При обновление устройства на Android 14, если ваше приложение имело полный доступ к фото/видео пользователя, получив соответствующие разрешения, то у вас также сохранится прежний уровень доступа к этим данным.
В документации пишут, что добавление нового разрешения и механизма не затронет работы вашего приложения, но его придется дорабатывать с учетом того, что пользователь захочет добавить новые фотографии в выборку, а какие-то приложения и вовсе предназначены для работы со всей медиатекой. Как пользователь, для себя я вижу плюс, потому что всякие мессенджеры и соцсети больше не смогут получить доступ ко всему, что у меня хранится в галерее.
Если вашему приложение нужен доступ к фото/видео при работе из фона, то настоятельно рекомендуется поддержать новое разрешение для дальнейшей корректной работы, иначе у вас регулярно может происходить отзыв этих разрешений на Android 14.
Улучшения для магазинов приложений
В Android 14 упрощает жизнь сторонним магазинам добавлением новых API в PackageInstaller. Начнем с ограничения на установку приложений. Сейчас она может произойти в любом момент при вызове соответствующего API. В Android U добавили Install Constraints API, которая позволяет задать условия, при которых можно выполнить установку
Приложение не видно пользователю
С приложением не происходит взаимодействий
Приложение видно, но не на переднем плане
Устройство не используется
Нет текущего телефонного звонка
PackageInstaller.InstallConstraints.Builder()
// Приложение не видно пользователю
.setAppNotForegroundRequired()
// С приложением не происходит взаимодействий
.setAppNotInteractingRequired()
// Приложение видно, но не на переднем плане
.setAppNotTopVisibleRequired()
// Устройство не используется
.setDeviceIdleRequired()
// Нет текущего телефонного звонка
.setNotInCallRequired()
.build()
Затем можно проверить, удовлетворяют ли требованиям указанные приложения с помощью метода checkInstallConstraints()
val packageInstaller: PackageInstaller = //...
packageInstaller.checkInstallConstraints(
packageNames = listOf("dev.androidbroadcast"),
contraints,
Executors.newSingleThreadExecutor()
) { result: PackageInstaller.InstallConstraintsResult ->
if (result.areAllConstraintsSatisfied()) {
// устанавливаем APK файлы
}
}
Также можно вызывать блокирующий метод waitForInstallConstraints(), который будет ожидать, пока условия для заданных приложений выполнятся.
packageInstaller.waitForInstallConstraints(
listOf("dev.androidbroadcast"),
contraints,
intentSender,
timeout = Duration.ofHours(1).toMillis()
)
Отложить обновление на момент, когда условия будут выполнены, можно с помощью метода commitSessionAfterInstallConstraintsAreMet()
packageInstaller.commitSessionAfterInstallConstraintsAreMet(
sessionId,
intentSender,
contraints,
timeout = Duration.ofHours(1).toMillis()
)
Появилась возможность перед установкой нескольких APK через PackageSession API получить разрешение пользователя на это. Нужно вызвать новый метод requestUserPreapproval(). Это позволяет удобно работать с App Bundle без необходимости многократного получения разрешения на установку отдельных APK. Таким образом, обновления из магазина нескольких приложений потребуют одного одобрения пользователя.
val sessionId: Int = packageInstaller.createSession(
PackageInstaller.SessionParams(SessionParams.MODE_INHERIT_EXISTING)
)
val pendingIntent: PendingIntent = // ...
val session: Session = packageInstaller.openSession(sessionId)
// Вызываем системный диалог с запросом разрешения у пользователя
// intentSender определяет, куда будет отправляться результат
session.requestUserPreapproval(
PreapprovalDetails.Builder()
.setIcon(iconBitmap)
.setLabel(message)
.setLocale(locale)
.build(),
pendingIntent.intentSender
)
// Дожидаемся разрешения пользователя и после стартует выполнение сессии
session.commit(intentSender)
Для защиты наката приложений из другого магазина в API PackageInstaller появился метод setRequestUpdateOwnership(), который потребует от пользователя подтвердить, что он действительно хочет сменить приложение-установщик. Такая ситуация обычно происходит, когда вы установили приложения из одного приложения, например, Google Play, а пытаетесь обновить его из другого приложения-магазина. Из того что мне удалось выяснить, это предназначено для критичных пакетов: Google Play Services, Health Connect, Chrome WebView.
При попытке обновления приложения из другого установщика пользователю будет показываться диалог с предупреждением, что обычно приложение должно обновляться через другой источник. Пользователю будет необходимо подтвердить обновление.
Определение, когда пользователь делает скриншот
Для обнаружения скриншотов в Android 14 появляется специальное API в Activity, которое вызывает Callback после того, как скриншот был сделан. API может обнаружить только скриншоты, которые были сделаны пользователем на устройстве с помощью комбинации специальных клавиш. Если экран будет захвачен во время теста с помощью специальных команд или по ADB, то callback не сработает. Для работы API надо будет добавить в AndroidManifest разрешение DETECT_SCREEN_CAPTURE и затем регистрируем callback в onStart(), и не забываем убрать в onStop() либо можно воспользоваться Jetpack Lifecycle.
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.DETECT_SCREEN_CAPTURE" />
</manifest>
class MainActivity : Activity() {
private val mainExecutor = MainEcxector()
private val screenshotCallback = ScreenCaptureCallback {
// Был сделан скриншот
}
override fun onStart() {
super.onStart()
registerScreenCaptureCallback(mainExecutor, screenshotCallback)
}
override fun onStop() {
super.onStop()
unregisterScreenCaptureCallback(screenshotCallback)
}
}
Отдельное разрешение для показа полноэкранных системных уведомлений
В Android 11 появилась возможность показывать полноэкранные уведомления, которые будут видны на экране блокировки и при работе полноэкранных приложений. Такой формат показа предназначался для показа таких очень важных уведомлений, как выходящие звонки и будильники. Начиная с Android 14, получить существующее разрешение USE_FULL_SCREEN_INTENT смогут только те приложения, из категории для которых оно предназначалось. Следить за этим будет Google Play при модерации билдов. После обновления устройства на Android 14 все приложения, которые уже использует это разрешение, смогут делать это и дальше, но вот при загрузке новой сборки в Google Play придётся его убрать, если вы не подходите под политику.
Также надо не забывать, что пользователь всегда сможет отозвать разрешение, поэтому перед показом полноэкранного уведомления важно проверить доступность этой возможности с помощью вызова NotificationManager.canUseFullScreenIntent
. Чтобы попросить пользователя выдать вам это разрешение, можно использовать новый Intent с ACTION_MANAGE_APP_USE_FULL_SCREEN_INTENT
, который откроет экран, где пользователь сможет выдать разрешение.
val intent = Intent(Settings.ACTION_MANAGE_APP_USE_FULL_SCREEN_INTENT)
try {
context.startActivity(intent)
} catch (e: ActivityNotFoundException) {
// Activity не может быть запущено
}
Data Safety из Google Play в Android системе
В 2023 в Google Play стало обязательным заполнение данных об обеспечении безопасности данных, с которыми работает приложение. Теперь эти данные будут видны не только на странице приложения в магазине, но и показываться в системе. В Android 14 при запросе разрешения на доступ к местоположению в системном диалоге можно будет увидеть, для чего приложениям нужен достук к вашей локации и с кем этими данными делится сервис. Эта секция будет показываться только в том случае, если приложение шарит локацию со сторонними сервисами.
Если приложения изменят шаринг вашего местоположения с другими сервисами, вы будете получить раз в месяц уведомление об этих изменениях. При его открытии пользователь увидит новый раздел “Обновления шаринга местоположения” с показом списка всех приложений, у которых менялись соответствующие правила.
Изменение призвано сделать прозрачным шаринг данных для пользователя, а также приложения теперь не смогут изменить правила незаметно от пользователя. Будет хорошо получать такие уведомления касательно шаринга не только местоположения, но и всех других типов данных.
Foreground Service Samsung
Небольшое, но важное изменение - Google начала активно работать с вендорами, чтобы стандартизировать поведение работы приложения в фоне на оболочках разных производителей. Samsung уже заявила, что в One UI 6.0 на основе Android 14 будет гарантирована работа Foreground Services в соответствии с документацией и новой политикой. Пока остаётся только надеяться, что это не пустые обещания и ситуация будет меняться в лучшую сторону.
Прочее
В Android 14 произошло много изменений, но часть их них мелких и заденет немногих, поэтому пройдемся по ним быстро:
При самостоятельной отправке приложениям PendingIntent теперь придется явно указывать, что его фоновая Activity может быть запущена. Для этого при создании PendingIntent надо передать Bundle ActivityOptions и вызвать метод setPendingIntentBackgroundActivityStartMode() с параметром MODE_BACKGROUND_ACTIVITY_START_ALLOWED
val options = ActivityOptions.makeBasic()
.setPendingIntentBackgroundActivityStartMode(
ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
)
val pendingIntent =
PendingIntent.getActivity(context, REQUEST_CODE, intent, options)
pendingIntent.send()
При байдинге Service из активного приложения придется явно указывать, что фоновому приложению можно запускать Activity добавлением флага BIND_ALLOW_ACTIVITY_STARTS при вызове метода bindService()
bindService(intent, serviceConnection, Context.BIND_ALLOW_ACTIVITY_STARTS)
Для Path API добавили возможность получить итератор и пройтись по его сегментам.
// Создаём path
val path = Path().apply {
moveTo(1.0f, 1.0f)
lineTo(2.0f, 2.0f)
close()
}
val pathIterator: PathIterator = path.pathIterator
for (segment in pathIterator) {
println("segment: ${segment.verb}, ${segment.points}")
}
Также появилась возможность интерполяции. Это будет полезно в анимировании между двумя Path.
val interpolatedResult = Path()
if (path.isInterpolatable(otherPath)) {
path.interpolate(otherPath, t = .5f, interpolatedResult)
}
Все возможности API из Android 14 доступны на версиях Android, начиная с 5.0, в библиотеки Jetpack Path
Android уведомления, связанные с Foreground Service или с пометкой ongoing, нельзя убрать. Изменения произошли с появлением Task Manager. В Android 14 практически все уведомления можно будет убрать. Ongoing уведомления не будут убираться с экрана блокировки или по нажатию кнопки “Удалить все”, а также уведомления для звонков и часть уведомлений от Android Enterprise.
Стали строже правила для запуска задач в фоне. Через пару секунд после перехода приложения в состояние cached любая фоновая работа запрещена, за исключением API, предназначенных для этого: WorkManager, JobScheduler или Foreground Service. Фоновая работа приложения становится недоступной на порядок быстрее, чем это происходит в Android 13.
В Android 14 продолжают добавлять возможность из последнего OpenJDK. В этот раз появились обновления из библиотеки и фичей языка Java 17: многострочные литералы, pattern matching в instanceof, sealed классы и др. Возможности будут перенесены на Android 12 и 13 благодаря модульной системе обновлений. Если кто из вас ждал этих возможностей, пишите в комментариях. Кажется, всех устраивает JDK 8 и Kotlin вокруг
Исправлена уязвимость с обходом Zip файлов
Все приложения с поддержкой Android 14, использующие динамическую загрузку кода, должны будут это делать только с файлами доступными для чтения
val jar = File("DYNAMICALLY_LOADED_FILE.jar")
// Делаем файл доступным только для чтения
jar.setReadOnly()
// Загружаем код
val cl = PathClassLoader(jar, parentClassLoader)
Все приложения на Android 14 теперь смогут останавливать фоновые процессы в рамках этого же приложения. При попытке остановки процесса другого приложения не произойдет ничего, а в LogCat появится сообщение
CredentialManager и HealthConnect стали системными сервисами в Android, но всё также рекомендуется использовать эти API через соответствующие Jetpack библиотеки
Стало возможно помечать View, чтобы ограничить их видимость только для Accessibility Service, которые нужны для помощи пользователям с ограниченными возможностями
view.setAccessibilityDataSensitive(
View.ACCESSIBILITY_DATA_SENSITIVE_YES
)
Для устройств, которые выходят на Android 14, будет обязательна аппаратная поддержка кодека AV1 и Identity Credential, которая позволит хранить водительские права на устройствах.
Для приложений появилась новая роль - NOTES. Предназначено для приложений, которые используются для создания заметок и по умолчанию будут срабатывать при отправке Intent с действием CREATE_NOTE
Публичной частью Android SDK стали аннотации @CriticalNative и @FastNative для отметки native методов для ускорения работы JNI в ART
Полностью удалили поддержку Android Beam из Android SDK
Появилась поддержка передачи аудио по USB без потерь. Аудиофилы будут рады. Google работает с партнерами, чтобы эта возможность появилась на устройствах.
Появилось аппаратное ускорение буфера рендеринга в Canvas
Все устройства на Android 14 с чипами на ARM v9 должны будут выходить только с поддержкой 64-битных приложений
Cross-device SDK стало частью Android SDK
Появилась поддержка 10-битных HDR изображений, позволяющая сохранить больше информации о цветах и контрастности. Формат называется Ultra HDR. Google камера и Google Photos будут его поддерживать
Добавлены новые Mainline модули: Health Connect, модуль для управления фича флагами, Cronet, Device Lock Controller, Remote Key Provisioner и Root Certificate
Улучшена поддержка рукописного ввода. Фича будет использоваться на планшетах
Поддержка спутниковых звонков
Появился системный сервис VirtualDeviceManager для создания и управления виртуальными устройствами. Не путать с эмуляторами в Android Studio
Заключение
Android с каждым годом все меньше и меньше добавляет новых возможностей, а идет работа над улучшением Android SDK и более строгими правилами для работы со стандартными компонентами Android-системы. Много новинок API уже давно стали развиваться в рамках Jetpack, т.к. они не привязаны к версии Android и обновляются независимо. Сам же Android SDK именно стал мостом между ОС и Jetpack, а разработчики все больше и больше используют последнее, библиотеки Kotlin и другие сторонние решения.
Делитесь в комментариях своим мнением касательно новой версии Android, что вам понравилось, а что нет. Может даже вы расскажите про то, что я упустил.