Site icon AppTractor

Написание символьного процессора с помощью Kotlin Symbol Processing (Часть 1)

Kotlin Symbol Processing (KSP) позволяет добавить возможность генерации кода в приложение. KSP использует аннотации для создания легких плагинов компилятора. С его помощью можно генерировать шаблонный код или добавлять в ваш код мощные функциональные возможности.

Эта тема также требует базовых знаний об аннотациях. Если вы не знакомы с этой темой, прочитайте учебник Annotations: Supercharge Your Development.

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

Вы также узнаете другие подробности:

Начало работы

Скачайте учебный проект в руководстве на сайте Kodeco, а затем откройте стартовый проект в Android Studio.

Структура проекта


В нем вы увидите три модуля:

Эти модули имеют заранее настроенные зависимости. Общая структура зависимостей показана ниже.

Как модуль :app, так и модуль :processor включают в себя модуль :annotation. Кроме того, модуль :app также зависит от модуля :processor.

Соберите и запустите проект.

На рисунке выше показан ShuffleFragment. При нажатии на кнопку выбирается случайный покемон.


Это DetailFragment, который показывает подробную информацию о покемоне. Случайный покемон передается в DetailFragment через Bundle. Наша цель — заменить функцию createDetailFragment в DetailFragment на сгенерированную.

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

Изучим способы генерации кода

Генерировать код в исходниках Kotlin можно тремя способами:

KAPT

Kotlin Annotation Processing Tool — или kapt — это решение для генерации кода, которое заставляет annotationProcessor в Java работать с файлами на Java и Kotlin. Хотя на него легко перейти, он опирается на извлечение из исходных файлов Kotlin сущностей Java, которые процессор может понять. Это делает его более медленным для файлов Kotlin.

Плагин компилятора Kotlin

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

Хорошим примером является плагин Parcelize, который генерирует реализации Parcelable для классов данных. Такой подход имеет ряд недостатков, в частности, API компилятора часто меняются, что затрудняет их обслуживание. Кроме того, эти API компилятора не очень хорошо документированы, поэтому работать с ними становится сложнее.

KSP

KSP пытается преодолеть разрыв между написанием плагинов компилятора и удобством сопровождения. Считайте, что это слой, защищающий ваш генератор кода от изменений API компилятора. Это также означает, что некоторая функциональность подключаемых модулей компилятора будет недоступна. KSP является Kotlin First, что означает, что он распознает синтаксис Kotlin. Это делает его более быстрым, поскольку он не полагается на извлечение сущностей Java.

Теперь, когда вы знаете больше о KSP, давайте начнем с обновления проекта с помощью аннотаций.

Знакомство с аннотациями

Аннотации — это точки входа в исходный код. Большинство инструментов генерации кода опираются на них. KSP работает на том же фундаменте.

Определение собственной аннотации

Начнем с определения класса аннотации. Он служит для поиска фрагментов, которым нужна фабрика. Перейдите в модуль :annotation вашего проекта и добавьте новый файл FragmentFactory.kt.

Затем добавьте к нему декларацию аннотации:

package com.yourcompany.fragmentfactory.annotation

import kotlin.annotation.AnnotationRetention.SOURCE
import kotlin.reflect.KClass
@Target(AnnotationTarget.CLASS) //1
@Retention(SOURCE) //2
annotation class FragmentFactory(val type: KClass<*>, val parcelKey: String)//3

Давайте рассмотрим ее пошагово:

  1. Это объявление указывает на то, что ваша аннотация должна использоваться для классов. Поскольку вас интересуют только Фрагменты, это будет правильным выбором. Другие варианты — для классов PROPERTY и FUNCTION.
  2. Значение SOURCE для возврата означает, что вы хотите, чтобы FragmentFactory была доступна только во время компиляции, а не внутри вашего APK.
  3. Параметр type указывает тип класса объекта, который необходимо обработать. Параметр parcelKey — это ключ, который будет использоваться для хранения сериализованных данных в Bundle.

Аннотирование фрагмента

Откройте DetailFragment и добавьте приведенное ниже содержимое прямо над объявлением фрагмента.

import com.yourcompany.fragmentfactory.DetailFragment.Companion.KEY_POKEMON
import com.yourcompany.fragmentfactory.annotation.FragmentFactory
@FragmentFactory(Pokemon::class,KEY_POKEMON)

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

Обращение к Символьным процессорам

KSP обращается к процессорам символов на этапе компиляции. Вся логика фильтрации токенов KSP и генерации кода происходит в них.

Добавление процессора

Начнем с перехода к пакету com.yourcompany.fragmentfactory.processor в модуле :processor проекта. Создайте в нем файл FragmentFactoryProcessor.kt.

После этого добавьте в него декларации процессоров.

package com.yourcompany.fragmentfactory.processor

import com.google.devtools.ksp.processing.CodeGenerator
import com.google.devtools.ksp.processing.KSPLogger
import com.google.devtools.ksp.processing.Resolver
import com.google.devtools.ksp.processing.SymbolProcessor
import com.google.devtools.ksp.symbol.KSAnnotated

class FragmentFactoryProcessor(
  private val logger: KSPLogger,
  codeGenerator: CodeGenerator
) : SymbolProcessor {

  override fun process(resolver: Resolver): List<KSAnnotated> {
    logger.info("FragmentFactoryProcessor was invoked.")
    return emptyList()
  }
}

Ваш символьный процессор будет расширять класс SymbolProcessor и реализовывать метод process. Обработка символов в Kotlin происходит в несколько проходов. В каждом метод process() может возвращать список символов, которые недоступны или будут обработаны в будущих раундах. Это называется отложенной обработкой и позволяет нескольким процессорам хорошо взаимодействовать друг с другом, когда один из них зависит от результатов работы другого.

Определение провайдера

В KSP Provider — это просто фабрика вашего processor. Как правило, здесь вы возвращаете экземпляр своего процессора. Добавьте новый файл FragmentFactoryProcessorProvider.kt в существующий пакет com.yourcompany.fragmentfactory.processor. Он также будет находиться в модуле :processor.

Затем добавьте в него следующий код:

package com.yourcompany.fragmentfactory.processor

import com.google.devtools.ksp.processing.SymbolProcessor
import com.google.devtools.ksp.processing.SymbolProcessorEnvironment
import com.google.devtools.ksp.processing.SymbolProcessorProvider

class FragmentFactoryProcessorProvider : SymbolProcessorProvider {

  override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor {
    return FragmentFactoryProcessor(
      logger = environment.logger,
      codeGenerator = environment.codeGenerator
    )
  }
}

Функция create вызывается всякий раз, когда KSP необходимо создать экземпляр вашего SymbolProcessor. Это дает доступ к environment, который предоставляет дефолтный логер. CodeGenerator предоставляет методы для создания и управления файлами. Более того, только созданные из него файлы доступны KSP для инкрементной обработки и компиляции.

Регистрация провайдера

Последним шагом в настройке процессора является регистрация его провайдера. Это делается путем определения его квалифицированной ссылки в специальном файле в каталоге src/main/resources/META-INF/services. Перейдите в эту папку, как показано ниже.

Создайте файл с именем com.google.devtools.ksp.processing.SymbolProcessorProvider с содержимым, приведенным ниже:

com.yourcompany.fragmentfactory.processor.FragmentFactoryProcessorProvider

KSP будет использовать эту ссылку для поиска вашего провайдера. Это похоже на то, как Android определяет путь к вашей Активити, читая файл AndroidManifest.xml.

Далее перейдите в файл app/build.gradle и добавьте следующую строку в раздел зависимостей:

ksp(project(":processor"))

Это позволит процессору обрабатывать исходные файлы модуля :app.

Ура, вы только что сконфигурировали свой первый SymbolProcessor. Теперь откройте вкладку Terminal в Android Studio и выполните следующую команду:

./gradlew clean installDebug --info

Вы должны увидеть результат сборки, аналогичный приведенному ниже:

Вы также увидите каталог ksp в папке build вашего модуля :app.

Продолжение: Написание символьного процессора с помощью Kotlin Symbol Processing (Часть 2)

Источник

Exit mobile version