Разбор Android 13

Всем приложениям, опубликованным в Google Play, до 31 августа 2023 года надо поддержать Android 13 в своих приложениях, т.е. поднять targetSdk до значения 33. В этом году сроки для обновления новых и уже опубликованных приложений объединили в один дедлайн. Я потратил несколько дней, чтобы разобрать все самые важные изменения и полезные фичи Android 13, чтобы упростить вам процесс миграции.

Если вам интересно следить за самыми последними новостями Android разработки и получать подборку интересных статей по этой тематике, тогда вам стоит подписаться на Телеграм-канал Android Broadcast и мой YouTube канал «Android Broadcast»

Улучшенная поддержка планшетов и больших экранов

Google еще c выходом Android 12L показала важность адаптации приложений под планшеты, складные устройства и большие экраны. В Android 13 были добавлены все наработки по улучшению UI/UX для планшетов и больших экранов из Android 12L, а также добавлены новые. Теперь системный UI лучше выглядит на больших экранах; улучшили работу с многозадачностью, добавив Task Bar, чем-то похожи на панель с приложениям в macOS и Windows; появился полноценный разделенный экран и др. улучшения.

Чтобы все это работало хорошо, начиная с Android 12L, поддержка режима многооконности для всех приложений была включена по умолчанию. Если ваше приложение этого не поддерживает, то придется это явно указать в AndroidManifest. От разработчиков требуется проверить, как ваше приложения будет работать в режиме «Split Screen». Как это сделать, вы найдете в официальной документации.

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

Использование Task Bar

Использование Task Bar

Информация о Task Bar для приложения сообщается через Inset. При использовании навигации жестами Task Bar может показываться или скрываться динамически. Если ваше приложение отрисовывает UI на основе Inset-ов окна, учтите, что тогда размер отступов может меняться пока приложение показывается на экране. Чтобы все корректно работало и дальше, вам надо будет подписаться на обновления отступов в окне и реагировать на это в UI вашего приложения.

ViewCompat.setOnApplyWindowInsetsListener(view) { view, windowInsets ->
    val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemGestures())
    // Применяем изменение Inset к UI вашего окна
}

Режим Совместимости

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

Пример работы режима совместимости

Пример работы режима совместимости

В режиме Совместимости производители устройства будут задавать какое-то стандартное соотношение сторон, делать закругления окна приложения и задавать прозрачность status bar. Разработчикам надо проверить корректность работы и вида приложения в режиме Совместимости.

Улучшена поддержка стилусов

Стилус — популярный способ работы на больших экранах, но пользователь может случайно касаться экрана рукой при его использовании. До Android 13 движения пальцами заканчивались отправкой MotionEvent с ACTION_CANCEL, но начиная с новой версии ОС поведение меняется. Если экрана касались только рукой, тогда завершающий MotionEvent будет содержать ACTION_CANCEL и FLAG_CANCELED, но если есть другие указатели на экране — будет задано ACTION_POINTER_UP и FLAG_CANCELED.

// До Android 13
MotionEvent(action = ACTION_CANCEL, flags = FLAG_CANCELED)

// Android 13+. Убрали все указатели с экрана
MotionEvent(action = ACTION_CANCEL, flags = FLAG_CANCELED)

// Android 13+. Убрали палец с экрана но стилус остался
MotionEvent(action = ACTION_POINTER_UP, flags = FLAG_CANCELED)

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

Помимо этого, выпустили много новых API в Jetpack, например, появилась библиотека Jetpack WindowManager, которая позволяет встраивать Activity в UI вашего приложения (называется Activity Embedding). Нечто подобное было давно в Android до прихода Fragment, а вот сейчас вернули снова, но ужу в новом варианте

Пример работы приложения с Activity Embedding

Пример работы приложения с Activity Embedding

Google явно прилагает много усилий для лучшей работы Android ОС на планшетах и больших экранах, особенно что не за горами выход фирменного планшета компании — Pixel Tablet.

Pixel Tablet

Pixel Tablet

Разрешение для показа уведомлений

В Android 13 и новее для показа уведомлений придется запросить новое разрешение POST_NOTIFICATIONS. Это поведение идентично тому, как уже давно было на iOS. Рассказывать, как запрашивать разрешения, я вам не буду, функционал с нами уже с 2015 года, но вот есть несколько рекомендаций, когда стоит показывать запрос на показ уведомлений, например, когда пользователь выполняет действие, для которого важен показ уведомлений.

Изменение будет работать для всех приложений, независимо от targetSdk, но есть несколько отличий:

  • Для приложений с targetSdk = 33 и выше, разработчик сам отвечает за проверку разрешения и его запрос

  • Для приложений с targetSdk ниже 33, которые были установлены на устройстве при обновление на Android 13, разрешение на показ уведомлений выдадут автоматически, но оно будет сброшено при удалении приложения. Важно, чтобы у вашего приложения уже должен быть создан хотя бы один канал уведомлений и пользователь не отключил все уведомления в вашем приложении.

  • Все новые устанавливаемые приложения с targetSdk ниже 33 будут показывать диалог с запросом на показ уведомлений при первом запуске любой Activity приложения и создании первого канала уведомлений. Обычно это делают в Application.onCreate ()

Нововведение касается показа всех уведомлений, включая Foreground Service, но есть исключения. Уведомления, связанные с медиа сессией, будут показываться вне зависимости от состояния разрешения, а также приложения, которые самостоятельно управляют звонками, смогут показывать уведомления, которые используют Notification.CallStyle.

Стандартный Photo Picker

В Android 13 добавили стандартный компонент, который предоставляет возможность обзора и поиска фотографий пользователя как на устройстве, так и в облаке, например, Google Photos. Новый компонент позволит предоставлять пользователю доступ к своим фото, а приложения не будут иметь доступ ко всем файлам пользователя, а только к фото и видео, которые он сам решит предоставить.

Важной особенностью Photo Picker является то, что он будет доступен на Android 11, благодаря модульной системе компонентов. Важно, устройство должно получить эти новинки через систему обновлений Google Pllay Services.

Приме работы Photo Picker

Приме работы Photo Picker

Jetpack Activity 1.6.1 уже содержит Activity Result API контракты PickVisualMedia и будет выбирать наилучший вариант, доступный на устройстве. В случае если Photo Picker недоступен на устройстве, то будет вызван Intent с ACTION_OPEN_DOCUMENT.

// Регистрируем Activity Result для получения фото
// Откроется Photo Picker (если доступен) или Document Provider
val pickMedia = registerForActivityResult(PickVisualMedia()) { uri ->
    // Обрабатываем результат
}

// Запуск для выбора фото и видео
pickMedia.launch(PickVisualMediaRequest(PickVisualMedia.ImageAndVideo))

// Запуск для выбора только фото
pickMedia.launch(PickVisualMediaRequest(PickVisualMedia.ImageOnly))

// Запуск для выбора только видео
pickMedia.launch(PickVisualMediaRequest(PickVisualMedia.VideoOnly))

// Выбираем меди только указанного MIME типа
pickMedia.launch(
		PickVisualMediaRequest(PickVisualMedia.SingleMimeType("image/gif"))
)

Чтобы проверить доступность Photo Picker, можно вызвать метод isPhotoPickerAvailable () либо воспользоваться Android SDK Extensions.

// Проверка доступности Photo Picker
fun isPhotoPickerAvailable(): Boolean {
    when {
        Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU -> true
        Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> 
						SdkExtensions.getExtensionVersion(Build.VERSION_CODES.R) >= 2
        else -> false
    }

Выбор языков для приложений

Выбор языка для приложения в Android 13

Выбор языка для приложения в Android 13

В Android 13 появилась возможность выбрать язык приложения отдельного от установленного в системе. В системных настройках у приложения вы сможете найти пункт «Язык приложения». Это еще одна из возможностей, заимствованная из iOS

Эта опция появится только для приложений, которые поддержали эту фичу. Для этого в AndroidManifest вашего приложения вы должны добавить атрибут android:localeConfig, который будет ссылаться на XML ресурс с перечислением поддерживаемых вашим приложением локалей



   
   
   
   
   
   


    ...
    
    

Также будет полезно добавить и поддерживаемые локали в Gradle, чтобы ненужные ресурсы не попадали в финальную сборку. Подробнее про этот прием я рассказывал в видео про уменьшение размера APK

android {
  ...
  defaultConfig {
    resourceConfigurations += ["en", "en-rGB", "fr", "ja", "b+zh+Hans+MO", "b+zh+Hant+MO"]
  }
}

Было бы конечно удобнее все сделать из коробки и чтобы разработчикам не надо было включать возможность. Ведь определить поддерживаемые языки можно по локалям, для которых есть строки в ресурсах приложения. Это не так. Многие приложения не заботятся о том, чтобы удалить ресурсы строк из неподдерживаемых локалей, а также библиотеки могут туда что-то положить. Поэтому без информации от разработчика, какие локали поддерживаются приложением, корректной работы фичи не получается.

Чтобы корректно получать текущую локаль, добавили новый системный сервис — LocaleManager, который позволяет получить текущие системные локали, локали для вашего или другого приложения, а также поменять локали приложения из кода.

// LocaleManager доступен на Android 13+ (API Level 33+)
val localeManager: LocaleManager = context.getSystemService()

// Список локалей для текущего приложения
val appLocales: LocaleList = localeManager.getApplicationLocales()

// Спиоск локалей выбранных в системе
val systemLocales: LocaleList = localeManager.getSystemLocales()

Возможно, вы уже реализовали собственный выбор языка в приложении, но рекомендуется миграция на стандартный UI. Для Android 12 и ниже вам придется реализовать UI самостоятельно, но зато в AppCompat 1.6.0 в классе AppCompatDelegate появились методы для получения и задание значения локалей, которые работают с аналогом системного API, либо могут запоминать выбранные значения на Android 12 и ниже. Для этого вам надо в манифесте добавить специальный сервис

// Код работает на всех версиях Andoid, поддеживаемы Jetpack

// Получаем актуальные локали приложения
val appLocales: LocaleListCompat = AppCompatDelegate.getApplicationLocales()

// Задаем новые локали
val newLocales = LocaleListCompat.create("ru", "en")
AppCompatDelegate.setApplicationLocales(newLocales)

  ...
  

    
  
  ...

Также важно учитывать настройки языка приложения при работе с API, которым необходимо передавать локаль: Accept-Language Header при открытии Web контента или при распознавании речи с помощью Speech Recognizer API.

Predictive Back Gesture

d74f564f2510cde8f513ac95ed071f80.gif

В Android 13 представили новую систему навигации назад под названием Predictive Back Gesture, которая будет показывать превью экрана, на который будет сделан переход при жесте назад. Пользователь сможет решить, нужно ли ему перейти или остаться на текущем экране.

В Android 13 добавится лишь API и поддержка, а переход на новую систему навигации будет осуществлен в Android 14. Протестировать систему можно и на Android 13 через настройки разработчика, а в манифесте вашего приложения вы можете выставить, что поддерживаете Predictive Back Gesture



Теперь разработчикам вместо переопределения Activity.onBackPressed () и обработке события нажатия KeyEvent.KEYCODE_BACK на Android 13 и выше надо будет использовать новое платформенное API — OnBackInvokedCallback. Если вы уже успели мигрировать на OnBackPressedCallback из Jetpack, то в AppCompat 1.6.0 уже под капотом сделали миграцию на новое платформенное API и вам ничего менять не придется

class MyActivity : AppCompatActivity() {

	@Override
	fun onCreate() {
	    if (BuildCompat.isAtLeastT()) {
	        onBackInvokedDispatcher.registerOnBackInvokedCallback(
	            OnBackInvokedDispatcher.PRIORITY_DEFAULT // или PRIORITY_OVERLAY
	        ) {
	            // Обрабатываем нажатие назад
	        }
	    }
	}
}
// AndroidManifest.xml

...

Foreground Service Task Manager

Foreground Task Manager в Android 13

Foreground Task Manager в Android 13

Теперь из системной панели уведомлений пользователь сможет остановить любой запущенный Foreground Service, независимо от targetSdk приложения. Это меню назвали Task Manager и он показывает список всех приложений с запущенными Foreground Service, рядом с каждым приложением в списке есть кнопка «Остановить»

Остановка приложения таким образом нечто среднее между удалением его из меню «Недавнее» и принудительной остановкой из раздела информации о приложении, а именно:

  • приложение удаляется из памяти

  • очищается back stack activity

  • останавливается воспроизведение медиа

  • уведомление, связанное с Foreground Service, будет убрано

  • приложение остается в истории

  • Запланированный Job будут выполняться

  • Все запланированные вызовы через AlarmManager сработают

FGS Task Manager

Удаление из Recent

Force Stop

Незамедлительное удаление приложения из памяти

Остановка воспроизведения медиа

Остановка Foreground Service

Удаление Activity Back Stack

Удаление приложения из истории

Запланированные Job отменены

Alarm отменяются

Некоторые приложения не будут отображаться в Task Manager, а для других не будет показываться кнопка «Остановить», например, для приложения звонилки или приложения, связанного с EMM.

Также благодаря этому изменению уведомления, связанные с Foreground Service, можно убрать из панели уведомлений. Если вы этого не хотите, то при создании объекта Notification, пометьте его как ongoing.

Notification.Builder(context)
		 // Задаем уведомление как текущее, которое не может быть удалено
         .setOngoing(true)
		 // ...
         .build();

Прокачали использование батарейки

Система App Standby Bucket появилась еще в Android 9 и развивается. В Android 13 сделали строже правила, когда приложения попадет в Restricted App Standy Bucket, независимо от targetSdk:

  • Пользователь не взаимодействовал с приложением 8 дней и больше (на Android 12 — это было 45 дней). Считаются только дни когда устройство не выключено

  • Приложение выполняет слишком много байндингов к Services или слишком часто отправляется broadcast-ы в течение 24 часов

  • Приложение потребляет слишком много батарейки в течение 24 часов

Что значит попасть в restricted bucket:

  • Выполнение задач в JobScheduler и WorkManager будет возможность один раз в день в течение 10 минут, во время которых будут также выполняться задачи других приложений

  • Уменьшается возможное количество запускаемых expedited jobs

  • Приложение сможет выполнять только один alarm в течение дня, независимо, задан в точное время или нет

Если пользователь начнет взаимодействовать с приложением, тогда система переместит приложение в другую корзинку.

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

Уведомление о слишком активном использовании батареи

Если приложение будет потреблять слишком много энергии в пределах 24-часового окна, то система покажет уведомление «приложение слишком сильно расходует энергию». Android 13 рассчитывает расход батарейки в фоне на основе работы Foreground и Background Service (если вы все еще сможете такие использовать), JobScheduler/WorkManager и BroadcastReceiver-ы.

За время использования Android 13 я не нашел приложения, которые приведут к показу такого уведомления. Видно его реально увидеть только для совсем зловредных приложений.

Превью контента в буфере обмена

Пример системного уведомления при изменение контента в буфере обмена

Пример системного уведомления при изменение контента в буфере обмена

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

Если вы что-то из кода добавляете в буфер обмена, вы должны учитывать, что все содержимое будет показано на экране. Если вы этого не хотите, тогда помечайте содержимое как чувствительный с помощью специального флага EXTRA_IS_SENSITIVE

// Android 13+ (API Level 33+)
clipData.apply {
    description.extras = PersistableBundle().apply {
        putBoolean(ClipDescription.EXTRA_IS_SENSITIVE, true)
    }
}

Новое разрешение для Wi-Fi устройств поблизости

Если вам приходилось работать с устройствами в Wi-Fi сети (nearby), то разработчикам надо было запросить разрешение ACCESS_FINE_LOCATION на доступ к геолокации. В Android 13 появилось отдельное разрешение NEARBY_WIFI_DEVICES, которое позволит работать с устройствами в Wi-Fi сети.


    
    

    
    

Новое разрешение попадает в группу NEARBY_WIFI_DEVICES, в которую также попадают и разрешения на доступ к Bluetooth и Ultra-wideband. Это значит, что при запросе или отзыве любого из них, будет выдаваться доступ или отзываться для всех разрешений в группе.

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

Новые иконки приложений

9da85627f7e91f1e1206632bc2ddaeca.png

В Android 13 продолжают развитие Material You, добавляя тематические иконки приложений. Такие иконки смогут перекрашиваться в цвета из системной темы.

В дополнении к уже всем существующим иконкам (обычная, круглая и адаптивная) приложениям надо будет добавить монохромную картинку, хорошо, что это сделали простым добавлением тэга monochrome в адаптивной иконке

92894da728b3de3e0b1f2dbb84b33a58.png

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

Очень странная фича, так как разработчики точно не будут спешить с ее реализацией в приложениях, если конечно Google Play не станет давить на разработчиков. А они только форсируют targetSdk и функции касательно безопасности

OpenJDK 11

В Android 13 стали переносить возможности из OpenJDK 11 LTS, как API из JDK, так и фичи языка. Они также будут перенесены через Google Play Services на Android 12, благодаря системе модульных компонентов.

Что именно теперь станет доступно:

  • Ключевое слова var для объявления локальных переменных и параметров лямбд

  • Новое API в классе String

  • Обновление Collection Stream API

  • Обновление Java Util Concurrent

  • и множество других API

C одной стороны — это хорошо, но для прикладных разработчиков толку не так много. Кто еще пишет приложения на Java? Уже все разработчики давно перешли на Kotlin. Тем, кто разрабатывает форки Android или сам Android, точно станет лучше.

android {
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_11
        targetCompatibility = JavaVersion.VERSION_11
    }
    kotlinOptions {
        jvmTarget = "11"
    }
}

Новое разрешение для использование AlarmManager

В Android 12 представили специальное разрешение для установки точных будильников через AlarmManager — SCHEDULE_EXACT_ALARM, которое выдается автоматом приложениям и может быть отозвано через системные настройки. В Android 13 появляется новое разрешение на замену USE_EXACT_ALARM. Отозвать такое разрешение пользователь уже не сможет, но чтобы опубликовать с ним приложение в Google Play, вам придется пройти специальное ревью и доказать, что ваше приложение не сможет работать без него. Например, будильник, таймер или календарь. Политика вступает в действие с 31 июля 2023.


    
    

    
    
    

На Android 13 и новее старое разрешение SCHEDULE_EXACT_ALARM больше не будет выдаваться автоматом. Вы всё также можете использовать его для тех случаев, когда опубликоваться с USE_EXACT_ALARM у вас не получиться, но вам надо будет явно просить пользователя выдавать его через системные настройки, а также не забывайте, что его могут отозвать в любой момент.

JobScheduler

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

Есть возможность выбрать один из 5 приоритетов

  • PRIORITY_MIN Для задач, результат которых пользователь не ожидает или вовсе не знает про них, например, загрузка аналитики. Может быть отложено, чтобы обеспечить достаточную квоту для задач более высокого приоритета.

  • PRIORITY_LOW Для задач, представляющие минимальную пользу для пользователя, например, предварительная загрузка данных, которые пользователь явно не запрашивал. Такую работы все еще можно отложить, чтобы гарантировать достаточную квоту для задач с приоритетом выше.

  • PRIORITY_DEFAULT Приоритет по умолчанию для всех задач. Максимальное время выполнение — 10 минут и стандартные правила по управлению задачей, как было раньше.

  • PRIORITY_HIGH: Для задач, которые должны выполняться, чтобы пользователь не подумал, что что-то идет не так. Такие задачи имеют максимальное время выполнения — 4 минуты при условии, что все ограничения выполнены и система находится в нагрузке. которая позволяет её выполнить.

  • PRIORITY_MAX: Для задач, которые должны выполняться в первую очередь, например, обработка текстового сообщения для отображения уведомления. Только Expedited Jobs могут использовать такой приоритет.

На момент выхода этой статьи стабильным является WorkManager 2.8.0 и возможность явного задания приоритетов туда не перенесли.

В Android 9.0 появилась возможность пометить Job как «prefetch», т.е. предзагрузка данных, которая в идеале должна как можно ближе и до следующего запуска приложения. Раньше никак не учитывалось лучшее время запуска для такой Job, а лишь ее расход данных. Начиная с Android 13, система будет пытаться прогнозировать следующий запуск приложения и оценить время на выполнение задач, чтобы запускать задачу в наиболее подходящее для этого время.

JobInfo.Builder(...)
	.setPrefetch(boolean)
	// Настраиваем Job
    .build()

Программируемые шейдеры

f76cc151146a550de328846e5cf80964.gif

В Android 13 появилась возможность использовать программируемые шейдеры для создания сложных эффектов. Появился новый класс — RuntimeShader. Например, на их основе в Android работает ripple эффект при нажатии кнопок, блюр, а также другие эффекты в UI. Для описания шейдера используется Android Graphics Shading Language (AGSL), который очень похож на GLSL, но способен работать андройдовским движком рендеринда и может фильтровать контент внутри View.

void mainImage(out vec4 Color, in vec2 Coord)
{
    vec2 Pos = (Coord-.5*iResolution.xy)/iResolution.y;
    float Size = 0.25;    
    float Dist = length(max(abs(Pos)-Size,0.));
    float Glow = 1./(Dist*25.+.5);
    
    Color = vec4(Glow*vec3(.4,1,.1),1);
}

Вы можете попробовать создать собственные шейдеры в песочнице на сайте shadertoy.com и потом перенести их в Android приложения.

Любители iOS копирования теперь могут порадоваться, ведь сделать размытие, как в яблочной ОС, теперь можно будет очень дешево по ресурсам для системы.

Пожалуйста, не копируйте дизайн iOS

© Habrahabr.ru