Улучшите производительность приложения с помощью базовых профилей

1. Прежде чем начать

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

Что вам понадобится

Что ты будешь делать

  • Настройте проект для использования генераторов базовых профилей.
  • Создавайте базовые профили для оптимизации запуска приложений и производительности прокрутки.
  • Проверьте прирост производительности с помощью библиотеки Jetpack Macrobenchmark.

Что вы узнаете

  • Базовые профили и способы повышения производительности приложения.
  • Как создать базовые профили.
  • Повышение производительности базовых профилей.

2. Приступаем к настройке

Для начала клонируйте репозиторий Github из командной строки, используя следующую команду:

$ git clone https://github.com/android/codelab-android-performance.git

Альтернативно вы можете скачать два zip-файла:

Открыть проект в Android Studio

  1. В окне «Добро пожаловать в Android Studio» выберите 61d0a4432ef6d396.png Откройте существующий проект .
  2. Выберите папку [Download Location]/codelab-android-performance/baseline-profiles . Убедитесь, что вы выбрали каталог baseline-profiles .
  3. Когда Android Studio импортирует проект, убедитесь, что вы можете запустить модуль app для создания примера приложения, с которым вы будете работать позже.

Пример приложения

В этой лаборатории кода вы работаете с примером приложения JetSnack . Это виртуальное приложение для заказа закусок, использующее Jetpack Compose.

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

23633b02ac7ce1bc.png

3. Что такое базовые профили

Базовые профили повышают скорость выполнения кода примерно на 30 % с момента первого запуска за счет исключения этапов интерпретации и JIT- компиляции для включенных путей кода. Отправляя базовый профиль в приложение или библиотеку, среда выполнения Android (ART) может оптимизировать включенные пути кода посредством предварительной компиляции (AOT), обеспечивая повышение производительности для каждого нового пользователя и при каждом обновлении приложения. Эта оптимизация на основе профилей (PGO) позволяет приложениям оптимизировать запуск, уменьшить количество зависаний при взаимодействии и повысить общую производительность выполнения для конечных пользователей с первого запуска.

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

Базовые профили помогают проводить оптимизацию помимо запуска приложения, обеспечивая общие взаимодействия с пользователем, которые улучшают время работы приложения с первого запуска. Управляемая компиляция AOT не зависит от пользовательских устройств и может выполняться один раз для каждого выпуска на компьютере разработки, а не на мобильном устройстве. При выпуске выпусков с базовым профилем оптимизация приложений становится доступной гораздо быстрее, чем при использовании только облачных профилей .

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

4. Настройте модуль генератора базового профиля.

Вы можете создавать базовые профили с помощью класса инструментального тестирования, для которого в ваш проект требуется добавить новый модуль Gradle. Самый простой способ добавить его в свой проект — использовать мастер модулей Android Studio, который входит в состав Android Studio Hedgehog или более поздней версии.

Откройте окно мастера создания нового модуля, щелкнув правой кнопкой мыши проект или модуль на панели «Проект» и выбрав «Создать» > «Модуль» .

232b04efef485e9c.png

В открывшемся окне выберите «Генератор базового профиля» на панели «Шаблоны».

b191fe07969e8c26.png

Помимо обычных параметров, таких как имя модуля, имя пакета, язык или язык конфигурации сборки, есть два входных параметра, которые необычны для нового модуля: Target application и Use Gradle Managed Device .

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

Флажок «Использовать управляемое устройство Gradle» устанавливает модуль для запуска генераторов базового профиля на автоматически управляемых эмуляторах Android. Подробнее об управляемых устройствах Gradle можно прочитать в разделе Масштабирование тестов с помощью управляемых устройств Gradle . Если вы снимите этот флажок, генераторы будут использовать любое подключенное устройство.

После того как вы определите все сведения о новом модуле, нажмите «Готово» , чтобы продолжить создание модуля.

Изменения, внесенные мастером модуля

Мастер модулей вносит в ваш проект несколько изменений.

Он добавляет модуль Gradle с именем baselineprofile или именем, выбранным вами в мастере.

Этот модуль использует плагин com.android.test , который сообщает Gradle не включать его в ваше приложение, поэтому он может содержать только тестовый код или тесты. Он также применяет плагин androidx.baselineprofile , который позволяет автоматизировать создание базовых профилей.

Мастер также вносит изменения в выбранный вами целевой модуль приложения. В частности, он применяет плагин androidx.baselineprofile , добавляет зависимость androidx.profileinstaller и добавляет зависимость baselineProfile к вновь созданному модулю build.gradle(.kts) :

plugins {
  id("androidx.baselineprofile")
}

dependencies {
  // ...
  implementation("androidx.profileinstaller:profileinstaller:1.3.0")
  "baselineProfile"(project(mapOf("path" to ":baselineprofile")))
}

Добавление зависимости androidx.profileinstaller позволяет вам делать следующее:

  • Локально проверьте прирост производительности созданных базовых профилей.
  • Используйте базовые профили на Android 7 (уровень API 24) и Android 8 (уровень API 26), которые не поддерживают облачные профили.
  • Используйте базовые профили на устройствах, на которых нет сервисов Google Play.

Зависимость baselineProfile(project(":baselineprofile")) позволяет Gradle узнать, из какого модуля ему нужно взять сгенерированные базовые профили.

Теперь, когда у вас есть набор проектов, напишите класс генератора базовых профилей.

5. Напишите генератор базового профиля

Обычно вы создаете базовые профили для типичных действий пользователя вашего приложения.

Мастер модулей создает базовый тестовый класс BaselineProfileGenerator , который способен генерировать базовый профиль для запуска вашего приложения и выглядит следующим образом:

@RunWith(AndroidJUnit4::class)
@LargeTest
class BaselineProfileGenerator {

    @get:Rule
    val rule = BaselineProfileRule()

    @Test
    fun generate() {
        rule.collect("com.example.baselineprofiles_codelab") {
            // This block defines the app's critical user journey. This is where you
            // optimize for app startup. You can also navigate and scroll
            // through your most important UI.

            // Start default activity for your app.
            pressHome()
            startActivityAndWait()

            // TODO Write more interactions to optimize advanced journeys of your app.
            // For example:
            // 1. Wait until the content is asynchronously loaded.
            // 2. Scroll the feed content.
            // 3. Navigate to detail screen.

            // Check UiAutomator documentation for more information about how to interact with the app.
            // https://d.android.com/training/testing/other-components/ui-automator
        }
    }
}

Этот класс использует правило тестирования BaselineProfileRule и содержит один тестовый метод для создания профиля. Точкой входа для создания профиля является функция collect() . Для этого требуется всего два параметра:

  • packageName : пакет вашего приложения.
  • profileBlock : последний лямбда-параметр.

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

По умолчанию созданный класс-генератор содержит взаимодействия для запуска вашего Activity по умолчанию и ожидает, пока первый кадр вашего приложения не будет отображен с помощью метода startActivityAndWait() .

Расширьте генератор с помощью индивидуальных путешествий.

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

В нашем примере приложения вы можете идентифицировать эти поездки, выполнив следующие действия:

  1. Запустите приложение. Это уже частично покрыто сгенерированным классом.
  2. Подождите, пока содержимое не загрузится асинхронно.
  3. Прокрутите список закусок.
  4. Перейдите к деталям закусок.

Измените генератор, чтобы он содержал функции, описанные в следующем фрагменте:

// ...
rule.collect("com.example.baselineprofiles_codelab") {
    // This block defines the app's critical user journey. This is where you
    // optimize for app startup. You can also navigate and scroll
    // through your most important UI.

    // Start default activity for your app.
    pressHome()
    startActivityAndWait()

    // TODO Write more interactions to optimize advanced journeys of your app.
    // For example:
    // 1. Wait until the content is asynchronously loaded.
    waitForAsyncContent()
    // 2. Scroll the feed content.
    scrollSnackListJourney()
    // 3. Navigate to detail screen.
    goToSnackDetailJourney()

    // Check UiAutomator documentation for more information about how to interact with the app.
    // https://d.android.com/training/testing/other-components/ui-automator
}
// ...

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

Ждите асинхронного контента

Многие приложения имеют своего рода асинхронную загрузку при запуске приложения, также известную как полностью отображаемое состояние , которое сообщает системе, когда контент загружается и отображается, и пользователь может с ним взаимодействовать. Дождитесь состояния в генераторе ( waitForAsyncContent ) с помощью следующих взаимодействий:

  1. Найдите список кормовых закусок.
  2. Подождите, пока некоторые элементы из списка не появятся на экране.
fun MacrobenchmarkScope.waitForAsyncContent() {
   device.wait(Until.hasObject(By.res("snack_list")), 5_000)
   val contentList = device.findObject(By.res("snack_list"))
   // Wait until a snack collection item within the list is rendered.
   contentList.wait(Until.hasObject(By.res("snack_collection")), 5_000)
}

Путешествие по прокручиваемому списку

Для перемещения по списку закусок ( scrollSnackListJourney ) вы можете следить за следующими взаимодействиями:

  1. Найдите элемент пользовательского интерфейса списка закусок.
  2. Установите поля жестов, чтобы не запускать системную навигацию.
  3. Прокрутите список и подождите, пока пользовательский интерфейс не успокоится.
fun MacrobenchmarkScope.scrollSnackListJourney() {
   val snackList = device.findObject(By.res("snack_list"))
   // Set gesture margin to avoid triggering gesture navigation.
   snackList.setGestureMargin(device.displayWidth / 5)
   snackList.fling(Direction.DOWN)
   device.waitForIdle()
}

Перейти к подробному описанию путешествия

Последнее путешествие ( goToSnackDetailJourney ) реализует эти взаимодействия:

  1. Найдите список закусок и все закуски, с которыми вы можете работать.
  2. Выберите элемент из списка.
  3. Нажмите на элемент и подождите, пока загрузится экран подробностей. Вы можете воспользоваться тем фактом, что список закусок больше не будет отображаться на экране.
fun MacrobenchmarkScope.goToSnackDetailJourney() {
    val snackList = device.findObject(By.res("snack_list"))
    val snacks = snackList.findObjects(By.res("snack_item"))
    // Select snack from the list based on running iteration.
    val index = (iteration ?: 0) % snacks.size
    snacks[index].click()
    // Wait until the screen is gone = the detail is shown.
    device.wait(Until.gone(By.res("snack_list")), 5_000)
}

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

6. Подготовьте устройство для запуска генератора.

Для создания базовых профилей мы рекомендуем использовать эмулятор, например управляемое устройство Gradle, или устройство под управлением Android 13 (API 33) или более поздней версии.

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

Чтобы определить управляемое устройство Gradle, добавьте его определение в файл модуля :baselineprofile build.gradle.kts , как показано в следующем фрагменте:

android {
  // ...

  testOptions.managedDevices.devices {
    create<ManagedVirtualDevice>("pixel6Api31") {
        device = "Pixel 6"
        apiLevel = 31
        systemImageSource = "aosp"
    }
  } 
}

В данном случае мы используем Android 11 (уровень API 31), и образ системы aosp имеет root-доступ.

Затем настройте плагин Gradle базового профиля для использования определенного управляемого устройства Gradle. Для этого добавьте имя устройства в свойство managedDevices и отключите useConnectedDevices , как показано в следующем фрагменте:

android {
  // ...
}

baselineProfile {
   managedDevices += "pixel6Api31"
   useConnectedDevices = false
}

dependencies {
  // ...
}

Затем создайте базовый профиль.

7. Создайте базовый профиль.

Когда устройство будет готово, вы сможете создать базовый профиль. Плагин Baseline Profile Gradle создает задачи Gradle для автоматизации всего процесса запуска тестового класса генератора и применения сгенерированных базовых профилей в вашем приложении.

Мастер нового модуля создал конфигурацию запуска, чтобы иметь возможность быстро запускать задачу Gradle со всеми необходимыми параметрами для запуска без необходимости переключения между терминалом и Android Studio.

Чтобы запустить его, найдите конфигурацию запуска Generate Baseline Profile и нажмите кнопку «Выполнить». 599be5a3531f863b.png .

6911ecf1307a213f.png

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

После успешного завершения работы генератора плагин Gradle автоматически помещает сгенерированный baseline-prof.txt в ваше целевое приложение (модуль :app ) в папку src/release/generated/baselineProfile/ .

fa0f52de5d2ce5e8.png

(Необязательно) Запустите генератор из командной строки.

Альтернативно вы можете запустить генератор из командной строки. Вы можете использовать задачу, созданную управляемым устройством Gradle :app:generateBaselineProfile . Эта команда запускает все тесты в проекте, определенном зависимостью baselineProfile(project(:baselineProfile)) . Поскольку модуль также содержит тесты для последующей проверки прироста производительности, эти тесты завершаются неудачей с предупреждением о недопустимости запуска тестов на эмуляторе.

android
   .testInstrumentationRunnerArguments
   .androidx.benchmark.enabledRules=BaselineProfile

Для этого вы можете отфильтровать все генераторы базовых профилей с помощью следующего аргумента инструментария, и все тесты будут пропущены:

Вся команда выглядит следующим образом:

./gradlew :app:generateBaselineProfile -Pandroid.testInstrumentationRunnerArguments.androidx.benchmark.enabledRules=BaselineProfile

Распространяйте свое приложение с помощью базовых профилей.

После того как базовый профиль будет создан и скопирован в исходный код вашего приложения, создайте производственную версию вашего приложения, как обычно. Вам не нужно делать ничего дополнительного, чтобы распространить базовые профили среди пользователей. Они выбираются плагином Android Gradle во время сборки и включаются в ваш AAB или APK. Далее загрузите сборку в Google Play.

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

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

8. (Необязательно) Настройте создание базовых профилей.

Плагин Gradle Baseline Profiles включает параметры для настройки создания профилей в соответствии с вашими конкретными потребностями. Вы можете изменить поведение с помощью блока конфигурации baselineProfile { } в сценариях сборки.

Блок конфигурации в модуле :baselineprofile влияет на то, как запускать генераторы с возможностью добавления managedDevices и решать, использовать ли useConnectedDevices или Gradle Managed.

Блок конфигурации в целевом модуле :app решает, где сохранять профили и как они генерируются. Вы можете изменить следующие параметры:

  • automaticGenerationDuringBuild : если этот параметр включен, вы можете генерировать базовый профиль при сборке сборки производственной версии. Это полезно при разработке CI перед отправкой приложения.
  • saveInSrc : указывает, сохраняются ли сгенерированные базовые профили в папке src/ . Альтернативно вы можете получить доступ к файлу из папки сборки :baselineprofile .
  • baselineProfileOutputDir : определяет, где хранить сгенерированные базовые профили.
  • mergeIntoMain : по умолчанию базовые профили генерируются для каждого варианта сборки (вариант продукта и тип сборки). Если вы хотите объединить все профили в src/main , вы можете сделать это, включив этот флаг.
  • filter : вы можете отфильтровать, какие классы или методы включить или исключить из сгенерированных базовых профилей. Это может быть полезно для разработчиков библиотек, которым нужен только код из библиотеки.

9. Проверьте улучшение производительности при запуске.

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

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

Класс выглядит следующим образом:

@RunWith(AndroidJUnit4::class)
@LargeTest
class StartupBenchmarks {

    @get:Rule
    val rule = MacrobenchmarkRule()

    @Test
    fun startupCompilationNone() =
        benchmark(CompilationMode.None())

    @Test
    fun startupCompilationBaselineProfiles() =
        benchmark(CompilationMode.Partial(BaselineProfileMode.Require))

    private fun benchmark(compilationMode: CompilationMode) {
        rule.measureRepeated(
            packageName = "com.example.baselineprofiles_codelab",
            metrics = listOf(StartupTimingMetric()),
            compilationMode = compilationMode,
            startupMode = StartupMode.COLD,
            iterations = 10,
            setupBlock = {
                pressHome()
            },
            measureBlock = {
                startActivityAndWait()

                // TODO Add interactions to wait for when your app is fully drawn.
                // The app is fully drawn when Activity.reportFullyDrawn is called.
                // For Jetpack Compose, you can use ReportDrawn, ReportDrawnWhen and ReportDrawnAfter
                // from the AndroidX Activity library.

                // Check the UiAutomator documentation for more information on how to
                // interact with the app.
                // https://d.android.com/training/testing/other-components/ui-automator
            }
        )
    }
}

Он использует MacrobenchmarkRule , который позволяет выполнять тесты вашего приложения и собирать показатели производительности. Точкой входа для написания теста является функция measureRepeated из правила.

Для этого требуется несколько параметров:

  • packageName: какое приложение измерять.
  • metrics : какой тип информации вы хотите измерить во время тестирования.
  • iterations : сколько раз повторяется тест.
  • startupMode : как вы хотите, чтобы ваше приложение запускалось при запуске теста.
  • setupBlock : какие взаимодействия с вашим приложением должны произойти перед измерением.
  • measureBlock : взаимодействие с вашим приложением, которое вы хотите измерить во время тестирования.

Тестовый класс также содержит два теста: startupCompilationeNone() и startupCompilationBaselineProfiles() , которые вызывают функцию benchmark() с разными compilationMode .

Режим компиляции

Параметр CompilationMode определяет, как приложение предварительно компилируется в машинный код. Он имеет следующие параметры:

  • DEFAULT : частично предварительно компилирует приложение с использованием базовых профилей, если они доступны. Используется, если не применяется параметр compilationMode .
  • None() : сбрасывает состояние компиляции приложения и не выполняет предварительную компиляцию приложения. JIT- компиляция по-прежнему включена во время выполнения приложения.
  • Partial() : предварительно компилирует приложение с помощью базовых профилей или прогрева, или того и другого.
  • Full() : предварительно компилирует весь код приложения. Это единственный вариант на Android 6 (API 23) и более ранних версиях.

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

Измените тест, чтобы дождаться контента

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

Вы можете сделать это, повторно используя функции расширения, которые вы пишете для генератора. Поскольку этот тест фиксирует время запуска (с помощью StartupTimingMetric() , мы рекомендуем вам включить сюда только ожидание асинхронного контента, а затем написать отдельный тест для других действий пользователя, определенных в генераторе.

// ...
measureBlock = {
   startActivityAndWait()

   // The app is fully drawn when Activity.reportFullyDrawn is called.
   // For Jetpack Compose, you can use ReportDrawn, ReportDrawnWhen and ReportDrawnAfter
   // from the AndroidX Activity library.
   waitForAsyncContent() // <------- Added to wait for async content.

   // Check the UiAutomator documentation for more information on how to
   // interact with the app.
   // https://d.android.com/training/testing/other-components/ui-automator
}

Запустите тесты

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

587b04d1a76d1e9d.png

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

94e0da86b6f399d5.png

После запуска теста ваше приложение перестраивается, а затем запускает тесты. Тесты запускают, останавливают и даже переустанавливают ваше приложение несколько раз в зависимости от определенных вами iterations .

После завершения тестов вы можете увидеть время в выходных данных Android Studio, как показано на следующем снимке экрана:

282f90d5f6ff5196.png

На скриншоте видно, что время запуска приложения разное для каждого CompilationMode . Медианные значения показаны в следующей таблице:

времяToInitialDisplay [мс]

времяToFullDisplay [мс]

Никто

202,2

818,8

Базовые профили

193,7

637,9

Улучшение

4%

28%

Разница между режимами компиляции для timeToFullDisplay составляет 180 мс, что на ~28 % лучше за счет простого наличия базового профиля. CompilationNone работает хуже, поскольку устройству приходится выполнять большую часть JIT-компиляции во время запуска приложения. CompilationBaselineProfiles работает лучше, поскольку частичная компиляция с помощью AOT базовых профилей компилирует код, который, скорее всего, будет использовать пользователь, и оставляет некритичный код предварительно не скомпилированным, поэтому его не нужно загружать немедленно.

10. (Необязательно) Проверьте улучшение производительности прокрутки.

Как и в предыдущем шаге, вы можете измерить и проверить производительность прокрутки. Сначала создайте тестовый класс ScrollBenchmarks с правилом тестирования и двумя методами тестирования, которые используют разные режимы компиляции:

@LargeTest
@RunWith(AndroidJUnit4::class)
class ScrollBenchmarks {

   @get:Rule
   val rule = MacrobenchmarkRule()

   @Test
   fun scrollCompilationNone() = scroll(CompilationMode.None())

   @Test
   fun scrollCompilationBaselineProfiles() = scroll(CompilationMode.Partial())

   private fun scroll(compilationMode: CompilationMode) {
       // TODO implement
   }
}

Из метода scroll используйте функцию measureRepeated с необходимыми параметрами. В качестве параметра metrics используйте FrameTimingMetric , который измеряет, сколько времени требуется для создания кадров пользовательского интерфейса:

private fun scroll(compilationMode: CompilationMode) {
   rule.measureRepeated(
       packageName = "com.example.baselineprofiles_codelab",
       metrics = listOf(FrameTimingMetric()),
       compilationMode = compilationMode,
       startupMode = StartupMode.WARM,
       iterations = 10,
       setupBlock = {
           // TODO implement
       },
       measureBlock = {
           // TODO implement
       }
   )
}

На этот раз вам нужно больше разделить взаимодействие между setupBlock и measureBlock , чтобы измерять продолжительность кадра только во время первого макета и прокрутки содержимого. Поэтому поместите функции, запускающие экран по умолчанию, в setupBlock , а уже созданные функции расширения waitForAsyncContent() и scrollSnackListJourney() в measureBlock :

private fun scroll(compilationMode: CompilationMode) {
   rule.measureRepeated(
       packageName = "com.example.baselineprofiles_codelab",
       metrics = listOf(FrameTimingMetric()),
       compilationMode = compilationMode,
       startupMode = StartupMode.WARM,
       iterations = 10,
       setupBlock = {
           pressHome()
           startActivityAndWait()
       },
       measureBlock = {
           waitForAsyncContent()
           scrollSnackListJourney()
       }
   )
}

Как только тест будет готов, вы можете запустить его, как и раньше, и получить результаты, как показано на следующем снимке экрана:

84aa99247226fc3a.png

FrameTimingMetric выводит продолжительность кадров в миллисекундах ( frameDurationCpuMs ) в 50-м, 90-м, 95-м и 99-м процентиле. В Android 12 (уровень API 31) и выше он также возвращает время, на которое ваши кадры превышают лимит ( frameOverrunMs ). Значение может быть отрицательным, что означает, что для создания кадра осталось дополнительное время.

Из результатов видно, что у CompilationBaselineProfiles продолжительность кадра в среднем короче на 2 мс, что может быть незаметно для пользователей. Однако для других процентилей результаты более очевидны. Для P99 разница составляет 43,5мс , что больше 3-х пропущенных кадров на устройстве, работающем со скоростью 90 FPS. Например, для Pixel 6 максимальное время рендеринга кадра составляет 1000 мс / 90 кадров в секунду = ~ 11 мс.

11. Поздравления

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

Дополнительные ресурсы

См. следующие дополнительные ресурсы:

Справочная документация