О проблеме
В разработке под платформу Android с самых первых его версий существует сложность в управлении жизненным циклом activity. Activity - это один из основных компонентов Android-приложения. Он представляет один, независимый от других, экран. Во время работы приложения операционная система может выгружать activity из оперативной памяти в следующих ситуациях ситуациях:
- При повороте экрана
- Если activity находится в фоне и для работы активных приложений ресурсов недостаточно
Объект activity является jvm классом, который пересоздается при наступлении вышеописанных условий, следовательно все поля этого объекта теряют свои актуальные значения.
Стандартное решение
            Для решения данной проблемы разработчики добавили в базовый класс activity дополнительный callback метод
            onSaveInstanceState. Чтобы лучше понять принцип работы этого механизма рассмотрим диаграммы,
            иллюстрирующие жизненный цикл activity (рис. 1):
        
 
        
Рисунок 1 – Жизненный цикл activity
            В случае уничтожения объекта системой после вызова onStop и перед вызовом
            onDestroy вызывается метод onSaveInstanceState, который позволяет сохранить
            необходимые данные. Далее при пересоздании в метод onCreate передается объект bundle в который
            ранее данные были сохранены. Однако, сохранение информации в bundle приводит к необходимости написания
            однотипного кода, что чревато ошибками на этапе сопровождния.
        
 
        
Рисунок 2 – Логгирование методов жизненного цикла activity (анимация: 16 кадров, циклов повторения – замкнутый, размер 377 Кб)
Кодогенерация
Так как код сохранения/восстановления состояния достаточно однотипен, его можно сгенерировать с помощью annotation processor. Annotation processor - jvm модуль, подключаемый к основному проекту. В его задчи входит обработка пользовательских аннотаций и генерация кода на их основе. Основным классом, через который осуществляется обработка является AbstractProcessor. Для процессинга аннотаций нужно унаследоваться от него и переопределить ряд методов.
Так как в настоящее время наиболее актуальным и перспективным языком разработки под Android является kotlin, то и процессор будет обрабатывать kotlin-код. Все примеры кода будут приведены также на котлине. Однако, на данный момент существует API только для обработки java-кода, в связи с этим для обработки кода на котлине необходимо вручную считывать и анализировать метаданные. Метаданные kotlin расширений хранятся в аннотации @Metadata в бинарном формате ProtoBuf. Существует библиотека, выполняющая парсинг сырых байт в ProtoBuf объекты. Это в значительной мере облегчает обработку, но, тем не менее, все еще требуется дополнительная обработка. Для этого были написаны следующие вспомогательные классы и методы:
    fun NameResolver.getJvmName(i: Int) = getQualifiedClassName(i).replace("/", ".")
    fun Element.toKotlinClass(): KotlinClass {
        val metadata = kotlinMetadata ?: throw RuntimeException("$simpleName does not have metadata")
        val classMetadata = metadata as KotlinClassMetadata
        val resolver = classMetadata.data.nameResolver
        val proto = classMetadata.data.classProto
        return KotlinClass(metadata, asTypeElement)
    }
    fun Element.toKotlinClassOrNull(): KotlinClass? {
        val metadata = kotlinMetadata ?: return null
        val classMetadata = if (metadata is KotlinClassMetadata) metadata else return null
        val resolver = classMetadata.data.nameResolver
        val proto = classMetadata.data.classProto
        val typeElement = if (this is TypeElement) this else null
        return KotlinClass(metadata, this.asTypeElement)
    }
    class KotlinClass(
        private val metadata: KotlinClassMetadata,
        val jvmTypeElement: TypeElement
    ) {
        private val resolver = metadata.data.nameResolver
        val name: String = resolver.getJvmName(metadata.data.classProto.fqName)
        val pkg: String = name.substring(0, name.lastIndexOf("."))
        val simpleName: String = name.substring(name.lastIndexOf(".") + 1, name.length)
        val properties: List<KotlinProperty> = metadata.data.classProto.propertyList
            .map { KotlinProperty(resolver, it) }
        val functions: List<KotlinFunction> = metadata.data.classProto.functionList
            .map { KotlinFunction(resolver, it) }
        val isDataClass: Boolean = metadata.data.classProto.isDataClass
        fun toTypeName(): TypeName = ClassName(pkg, simpleName)
    }
    class KotlinProperty(
        private val resolver: NameResolver,
        private val property: ProtoBuf.Property
    ) {
        val name: String = resolver.getJvmName(property.name)
        val returnType: KotlinType? =
            if (property.hasReturnType()) KotlinType(resolver, property.returnType) else null
        val receiverType: KotlinType? =
            if (property.hasReceiverType()) KotlinType(resolver, property.receiverType) else null
        val hasAnnotations: Boolean = property.hasAnnotations
        val getterHasAnnotations: Boolean = property.getterHasAnnotations
        val setterHasAnnotations: Boolean = property.setterHasAnnotations
    }
    class KotlinFunction(
        private val resolver: NameResolver,
        private val function: ProtoBuf.Function
    ) {
        val name = resolver.getJvmName(function.name)
        val returnType: KotlinType? =
            if (function.hasReturnType()) KotlinType(resolver, function.returnType) else null
        val receiverType: KotlinType? =
            if (function.hasReceiverType()) KotlinType(resolver, function.receiverType) else null
        val valueParameters: List<KotlinValueParameter> =
            function.valueParameterList.map { KotlinValueParameter(resolver, it) }
        val hasAnnotations: Boolean = function.hasAnnotations
    }
    class KotlinValueParameter(
        private val resolver: NameResolver,
        private val parameter: ProtoBuf.ValueParameter
    ) {
        val name = resolver.getJvmName(parameter.name)
        val type: KotlinType? = parameter.type.takeIf { parameter.hasType() }
            ?.let { KotlinType(resolver, it) }
    }
    class KotlinType(
        private val resolver: NameResolver,
        private val type: ProtoBuf.Type
    ) {
        val name: String = resolver.getJvmName(type.className)
        val arguments: List<KotlinTypeArgument> = type.argumentList.map { KotlinTypeArgument(resolver, it) }
        val nullable: Boolean = type.nullable
        val pkg: String = name.substring(0, name.lastIndexOf("."))
        val simpleName: String = name.substring(name.lastIndexOf(".") + 1, name.length)
        fun toTypeName(): TypeName = if (arguments.isNotEmpty()) {
            ClassName(pkg, simpleName)
                .parameterizedBy(*arguments.map { it.type.toTypeName() }.toTypedArray())
                .nullable(nullable)
        } else {
            ClassName(pkg, simpleName).nullable(nullable)
        }
        fun isSimpleType(): Boolean = name in SIMPLE_TYPES.map { it.fullName }
        fun isInt(): Boolean = name == kt.Int.fullName
        fun isLong(): Boolean = name == kt.Long.fullName
        fun isString(): Boolean = name == kt.String.fullName
        fun isFloat(): Boolean = name == kt.Float.fullName
        fun isDouble(): Boolean = name == kt.Double.fullName
        fun isBoolean(): Boolean = name == kt.Boolean.fullName
        companion object {
            val SIMPLE_TYPES = listOf(
                kt.Boolean,
                kt.Float,
                kt.Double,
                kt.Int,
                kt.Long,
                kt.String
            )
        }
    }
    class KotlinTypeArgument(
        private val resolver: NameResolver,
        private val typeArgument: ProtoBuf.Type.Argument
    ) {
        val type: KotlinType = KotlinType(resolver, typeArgument.type)
    }
        
        Также для некоторых возможностей требуется объект RoundEnvironment, поэтому часть вспомогательных расширений написана в дочернем классе KotlinAbstractProcessor
    protected fun KotlinType.toTypeElement(): TypeElement = elementUtils.getTypeElement(name.toJvmName())
    protected fun KotlinClass.getExecutableElement(function: KotlinFunction): ExecutableElement =
        elementUtils.getTypeElement(name.toJvmName()).enclosedElements
            .first { it.simpleName.toString() == function.name }
            .asExecutableElement
    protected fun KotlinType.toKotlinClass(): KotlinClass = toTypeElement()
        .toKotlinClass()
    protected fun KotlinType.toKotlinClassOrNull(): KotlinClass? = toTypeElement()
        .toKotlinClassOrNull()
    protected fun KotlinType?.implements(name: String) = this!!
        .toTypeElement()
        .implements(name)
    protected fun KotlinType.canSerialize(): Boolean = when {
        this.isSimpleType() -> true
        this.isSerializable() -> true
        this.implements(java.util.List::class.java.canonicalName) -> arguments[0].type.canSerialize()
        this.implements(java.util.Set::class.java.canonicalName) -> arguments[0].type.canSerialize()
        else -> false
    }
    protected fun KotlinType.isSerializable(): Boolean =
        this.toTypeElement().getAnnotation(kotlinx.serialization.Serializable::class.java) != null
    protected fun KotlinType.implementsObservable(): Boolean =
        this.implements(caf.observable.IObservable.fullName)
    protected fun KotlinType.isSerializableList(): Boolean = when {
        this.implements(java.util.List::class.java.canonicalName) -> arguments[0].type.isSimpleType()
                || arguments[0].type.isSerializable()
                || arguments[0].type.isSerializableList()
        else -> false
    }
        
        С помощью данных расширений можно приступить к решению задачи кодогенерации
    @AutoService(Processor::class)
    class AnnotationProcessor : BaseAnnotationProcessor() {
        override fun getSupportedAnnotationTypes(): MutableSet<String> {
            return mutableSetOf(
                Presenter::class.java.name
            )
        }
        override fun getSupportedSourceVersion(): SourceVersion = SourceVersion.latest()
        override fun process(annotations: Set<TypeElement>, roundEnv: RoundEnvironment): Boolean = try {
            roundEnv.annotatedClasses<Presenter>()
                .mapNotNull { it.toKotlinClassOrNull() }
                .forEach { kotlinClass ->
                    processPresenter(kotlinClass)
                }
            false
        } catch (t: Throwable) {
            error(t)
            true
        }
        private fun processPresenter(kotlinClass: KotlinClass) {
            FileSpec.builder(kotlinClass.pkg, "Generated${kotlinClass.simpleName}")
                .addType(kotlinClass.getPersistableWrapper())
                .addFunction(kotlinClass.getPersistableWrapperFactoryFunc())
                .build()
                .writeTo(this.generatedDir!!)
        }
        private fun KotlinClass.getPersistableWrapper(): TypeSpec {
            val className = simpleName
            val classPackage = pkg
            val persistableWrapperName = ClassName(classPackage, "Persistable${className}Wrapper")
            return TypeSpec.classBuilder(persistableWrapperName)
                .primaryConstructor(
                    FunSpec.constructorBuilder()
                        .addParameter("presenter", ClassName(classPackage, className))
                        .build()
                )
                .addProperty(
                    PropertySpec.builder("presenter", ClassName(classPackage, className))
                        .addModifiers(KModifier.PRIVATE)
                        .initializer("presenter")
                        .build()
                )
                .addSuperinterface(caf.Persistable.className)
                .addFunction(getSaveFunc())
                .addFunction(getRestoreFunc())
                .build()
        }
        private fun KotlinClass.getPersistableWrapperFactoryFunc(): FunSpec = FunSpec.builder("persistable")
            .receiver(toTypeName())
            .returns(caf.Persistable.className)
            .addStatement("return %T(this)", ClassName(pkg, "Persistable${simpleName}Wrapper"))
            .build()
        private fun KotlinClass.getSaveFunc(): FunSpec {
            val annotatedGetterNames = getAnnotatedGetterNames<Persist>()
            val block = buildCodeBlock {
                wrap("val jsonRes = %M {", ktx.serialization.json.json.memberName) {
                    properties
                        .filter { property -> property.name.toGetterName() in annotatedGetterNames }
                        .onEach { property ->
                            if (property.returnType == null) {
                                return@onEach
                            }
                            val isLateInit = property.returnType.name == caf.observable.LateinitObservable.fullName
                            conditionalWrapIf(isLateInit, "presenter.${property.name}.isInitialized") {
                                add("%S to ", property.name)
                                generateSerializer(
                                    "presenter.${property.name}.value",
                                    property.returnType.arguments[0].type
                                )
                            }
                        }
                }
                addStatement("return jsonRes.toString()")
            }
            return FunSpec.builder("onSaveInstanceState")
                .addModifiers(KModifier.OVERRIDE)
                .returns(kt.String.className)
                .addCode(block)
                .build()
        }
        private fun KotlinClass.getRestoreFunc(): FunSpec {
            val annotatedGetterNames = getAnnotatedGetterNames<Persist>()
            val block = buildCodeBlock {
                wrap(
                    "%T.${currentSerializationType.memberName}.parseJson(strState).jsonObject.apply",
                    ktx.serialization.json.Json.className
                ) {
                    properties
                        .filter { property -> property.name.toGetterName() in annotatedGetterNames }
                        .onEach { property ->
                            if (property.returnType == null) {
                                return@onEach
                            }
                            val isLateInit = property.returnType.name == caf.observable.LateinitObservable.fullName
                            conditionalWrapIf(isLateInit, "containsKey(%S)", property.name) {
                                val propertyTypeArg = property.returnType.arguments[0].type
                                val propertyGetter = when {
                                    propertyTypeArg.isSimpleType() -> "getPrimitive"
                                    propertyTypeArg.isSerializable() -> if (propertyTypeArg.nullable) "getObjectOrNull" else "getObject"
                                    propertyTypeArg.isSerializableList() -> if (propertyTypeArg.nullable) "getArrayOrNull" else "getArray"
                                    else -> {
                                        error("property type ${propertyTypeArg.name} is not supported")
                                        throw fatal()
                                    }
                                }
                                wrap("presenter.${property.name}.value = $propertyGetter(%S).let", property.name) {
                                    generateDeserializer("it", propertyTypeArg)
                                }
                            }
                        }
                }
            }
            return FunSpec.builder("onRestoreInstanceState")
                .addModifiers(KModifier.OVERRIDE)
                .addParameter("strState", kt.String.className)
                .addCode(block)
                .build()
        }
        protected fun CodeBlock.Builder.generateSerializer(propertyGetter: String, type: KotlinType) {
            when {
                type.isSimpleType() -> {
                    conditionalWrapIfElse(type.nullable, "$propertyGetter != null", propertyGetter, next = {
                        if (type.isInt() || type.isLong() || type.isFloat() || type.isDouble()) {
                            addStatement("($propertyGetter!! as Number)")
                        } else {
                            addStatement("$propertyGetter!!")
                        }
                    }, nextElse = {
                        addStatement("null")
                    })
                }
                type.isSerializable() -> {
                    conditionalWrapIfElse(type.nullable, "$propertyGetter != null", next = {
                        addStatement(
                            "%T.${currentSerializationType.memberName}.toJson(%T.serializer(), $propertyGetter!!)",
                            ktx.serialization.json.Json.className,
                            type.toTypeName().nullable(false)
                        )
                    }, nextElse = {
                        addStatement("%T", ktx.serialization.json.JsonNull.className)
                    })
                }
                type.isSerializableList() -> {
                    conditionalWrapIfElse(type.nullable, "$propertyGetter != null", next = {
                        wrap("%M", ktx.serialization.json.jsonArray.memberName) {
                            wrapLambda("$propertyGetter!!.forEach") {
                                add("+ ")
                                generateSerializer("it", type.arguments[0].type)
                            }
                        }
                    }, nextElse = {
                        addStatement("%T", ktx.serialization.json.JsonNull.className)
                    })
                }
                else -> {
                    error("Unable to generate serializer for type ${type.name}")
                    throw fatal()
                }
            }
        }
        protected fun CodeBlock.Builder.generateDeserializer(propertyGetter: String, type: KotlinType) {
            when {
                type.isInt() -> if (type.nullable) {
                    addStatement("$propertyGetter.primitive.intOrNull")
                } else {
                    addStatement("$propertyGetter.primitive.int")
                }
                type.isFloat() -> if (type.nullable) {
                    addStatement("$propertyGetter.primitive.floatOrNull")
                } else {
                    addStatement("$propertyGetter.primitive.float")
                }
                type.isLong() -> if (type.nullable) {
                    addStatement("$propertyGetter.primitive.longOrNull")
                } else {
                    addStatement("$propertyGetter.primitive.long")
                }
                type.isDouble() -> if (type.nullable) {
                    addStatement("$propertyGetter.primitive.doubleOrNull")
                } else {
                    addStatement("$propertyGetter.primitive.double")
                }
                type.isString() -> if (type.nullable) {
                    addStatement("$propertyGetter.primitive.contentOrNull")
                } else {
                    addStatement("$propertyGetter.primitive.content")
                }
                type.isBoolean() -> if (type.nullable) {
                    addStatement("$propertyGetter.primitive.booleanOrNull")
                } else {
                    addStatement("$propertyGetter.primitive.boolean")
                }
                type.isSerializable() -> {
                    conditionalWrapIfElse(
                        type.nullable,
                        "$propertyGetter != null && !$propertyGetter!!.isNull",
                        next = {
                            addStatement(
                                "%T.${currentSerializationType.memberName}.fromJson(%T.serializer(), $propertyGetter)",
                                ktx.serialization.json.Json.className,
                                type.toTypeName().nullable(false)
                            )
                        },
                        nextElse = {
                            addStatement("null")
                        })
                }
                type.isSerializableList() -> {
                    conditionalWrapIfElse(
                        type.nullable,
                        "$propertyGetter != null && !$propertyGetter!!.isNull",
                        next = {
                            wrap("it.jsonArray.map") {
                                generateDeserializer("it", type.arguments[0].type)
                            }
                        },
                        nextElse = {
                            addStatement("null")
                        })
                }
            }
        }
        private inline fun <reified T : Annotation> KotlinClass.getAnnotatedGetterNames() =
            jvmTypeElement
                .enclosedElements
                .filter { it.getAnnotation(T::class.java) != null }
                .mapNotNull {
                    val getterName = it.simpleName.split("$")[0].toGetterName()
                    val getterElement = jvmTypeElement.enclosedElements
                        .firstOrNull { it.simpleName.toString() == getterName }
                    if (getterElement == null) {
                        error("No public getter for @Persist annotated element exists")
                        throw RuntimeException("Fatal error")
                    }
                    if (!getterElement.modifiers.contains(Modifier.PUBLIC)) {
                        error("@Persist annotation can be applied only to public getters")
                        throw RuntimeException("Fatal error")
                    }
                    if (!getterElement.type.asExecutable.returnType.asDeclared.typeElement.implements(caf.observable.IMutableObservable.fullName)) {
                        error("@Persist annotation can be applied only to properties that implements ${caf.observable.IMutableObservable.fullName}")
                        throw RuntimeException("Fatal error")
                    }
                    getterElement
                }
                .map { it.simpleName.toString() }
        private inline fun <reified T : Annotation> KotlinClass.getAnnotatedFunctionNames() =
            jvmTypeElement
                .enclosedElements
                .filter { it.getAnnotation(T::class.java) != null }
                .mapNotNull { functionElement ->
                    if (functionElement.kind != ElementKind.METHOD) {
                        error("Only functions can be annotated with @InteractorExecutor")
                        throw fatal()
                    }
                    functionElement as ExecutableElement
                    val functionReturnTypeElement = functionElement.returnType?.asDeclared?.typeElement
                    if (functionReturnTypeElement?.qualifiedName?.toString() != caf.interactor.Task.fullName) {
                        error("@InteractorExecutor functions should return ${caf.interactor.Task.fullName} objects")
                        throw RuntimeException("Fatal error")
                    }
                    functionElement
                }
                .map { it.simpleName.toString() }
    }
        
        Результат
Теперь любой класс, состояние которого нужно сериализовать, можно описать слеюующим образом:
    @Presenter
    class SomeClass {
        @Persist
        val value1 = Observable<Int>(20)
        @Persist
        val value2 = Observable<List<String>>(listOf())
    }
        
        Следует отметить, что данный генератор разрабатывался под определенную задачу, в которой состояние хранилось в объектах класса Observable, поэтому он обрабатывает только свойства с типом этих классов. Класс, объекты которого требуют сериализации, помечаются аннотацией @Presenter, свойства, требующие сериализации отмечаются аннотацией @Persist. Для успешной кодогенерации аннотированные свойства должны отмечать следующим критериям:
- Свойство должно иметь публичный getter
- Тип свойства толжен реализовывать интерфейс IMutableObservable (его описание выходит за рамки данной статьи)
- Аргумент дженерика должен быть либо простым типом (Int, Long, Float, Double, Boolean, String), либо этот тип должен быть помечен аннотацией @Serizlizable из kotlin-serialization, либо являться списком вышеперечисленных типов
            На выходе создается класс PersistableSomeClassWrapper имеющий методы onSaveInstanceState():
            String и onRestoreInstanceState(strState: String). Первый возвращает строку, хранящую
            состояние объекта, второй - по переданной строке это состояние восстанавливает. Так же создается extension
            метод SomeClass.persistableWrapper(): PersistableSomeClassWrapper, для более удобного создания
            враппера.
        
    class PersistableSomeClassWrapper(private val presenter: SomeClass) : Persistable {
        override fun onSaveInstanceState(): String {
            val jsonRes = json {
                "value1" to (presenter.value1.value!! as Number)
                "value2" to jsonArray {
                    presenter.value2.value!!.forEach {
                        + it!!
                    }
                }
            }
            return jsonRes.toString()
        }
        override fun onRestoreInstanceState(strState: String) {
            Json.indented.parseJson(strState).jsonObject.apply {
                presenter.value1.value = getPrimitive("value1").let {
                    it.primitive.int
                }
                presenter.value2.value = getArray("value2").let {
                    it.jsonArray.map {
                        it.primitive.content
                    }
                }
            }
        }
    }
    fun SomeClass.persistable(): Persistable = PersistableSomeClassWrapper(this)
        
    