Grabaciones en Android Studio Flamingo



Publicado por Clément Béra, ingeniero de software sénior

Records es una nueva función de Java para clases de discos inmutables introducida en Java 16 y Android 14. Para usar conjuntos de datos en Android Studio Flamingo, necesita un SDK de Android 14 (nivel de API 34) java.lang.Record la clase está ahí android.jar. Está disponible en SDK revisión 4 "Android UpsideDownCake Preview". Los registros son esencialmente clases con propiedades inmutables e implícitas. código hash, es igualY Encadenar Métodos basados ​​en los campos de datos subyacentes. En este sentido, son muy similares a las clases de datos de Kotlin. para explicar un persona Tiro con los campos nombre de cadena Y Viejo Para compilarlo en un conjunto de datos de Java, use el siguiente código:

@JvmRecord
data class Person(val name: String, val age: Int)

El archivo build.gradle también debe ampliarse para usar el SDK y el origen y el destino de Java correctos. Actualmente, se requiere la vista previa de Android UpsideDownCake, pero cuando se lance el SDK de Android 14 final, use compileSdk 34 y targetSdk 34 en lugar de la versión de vista previa.

android {
compileSdkPreview "UpsideDownCake"

defaultConfig {
targetSdkPreview "UpsideDownCake"
}

compileOptions {
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = '17'
}
}

Los conjuntos de datos no necesariamente agregan valor en comparación con las clases de datos en los programas puros de Kotlin, pero permiten que los programas de Kotlin interactúen con las bibliotecas de Java cuyas API contienen conjuntos de datos. Para los programadores de Java, esto permite que el código de Java use registros. Use el siguiente código para declarar el mismo registro en Java:

public record Person(String name, int age) {}

Aparte de las banderas y los atributos del registro, el registro de persona es aproximadamente equivalente a la siguiente clase, descrita usando la fuente de Kotlin:

class PersonEquivalent(val name: String, val age: Int) {

override fun hashCode() : Int {
return 31
* (31 * PersonEquivalent::class.hashCode()
+ name.hashCode())
+ Integer.hashCode(age)
}

override fun equals(other: Any?) : Boolean {
if (other == null || other !is PersonEquivalent) {
return false
}
return name == other.name && age == other.age
}

override fun toString() : String {
return String.format(
PersonEquivalent::class.java.simpleName + "[name=%s, age=%s]",
name,
age.toString()
)
}
}

println(Person(“John”, 42).toString())
>>> Person[name=John, age=42]

En una clase de registro es posible anular eso código hash, es igualY Encadenar Métodos que reemplazan efectivamente los métodos generados por el tiempo de ejecución de JVM. En este caso, el comportamiento de estos métodos está definido por el usuario.

Registro de desacarificación

Dado que los conjuntos de datos no son compatibles con ningún dispositivo Android hoy en día, el motor de eliminación de azúcar D8/R8 necesita eliminar los conjuntos de datos: convierte el código del conjunto de datos en un código compatible con las máquinas virtuales de Android. Cuando los conjuntos de datos se eliminan del azúcar, el conjunto de datos se convierte en una clase más o menos equivalente sin la necesidad de generar o compilar la fuente. La siguiente fuente de Kotlin muestra una aproximación del código generado. Para mantener pequeño el tamaño del código de la aplicación, los conjuntos de datos se disecan para que los métodos auxiliares se compartan entre conjuntos de datos.

class PersonDesugared(val name: String, val age: Int) {
fun getFieldsAsObjects(): Array<Any> {
return arrayOf(name, age)
}

override fun hashCode(): Int {
return SharedRecordHelper.hash(
PersonDesugared::class.java,
getFieldsAsObjects())
}

override fun equals(other: Any?): Boolean {
if (other == null || other !is PersonDesugared) {
return false
}
return getFieldsAsObjects().contentEquals(other.getFieldsAsObjects())
}

override fun toString(): String {
return SharedRecordHelper.toString(
getFieldsAsObjects(),
PersonDesugared::class.java,
"name;age")
}

class SharedRecordHelper {
companion object {
fun hash(recordClass: Class<*>, fieldValues: Array<Any>): Int {
return 31 * recordClass.hashCode() + fieldValues.contentHashCode()
}

fun toString(
fieldValues: Array<Any>,
recordClass: Class<*>,
fieldNames: String
)
: String {
val fieldNamesSplit: List<String> =
if (fieldNames.isEmpty()) emptyList() else fieldNames.split(";")
val builder: StringBuilder = StringBuilder()
builder.append(recordClass.simpleName).append("[")
for (i in fieldNamesSplit.indices) {
builder
.append(fieldNamesSplit[i])
.append("=")
.append(fieldValues[i])
if (i != fieldNamesSplit.size - 1) {
builder.append(", ")
}
}
builder.append("]")
return builder.toString()
}
}
}
}

el registro se está reduciendo

R8 asume la configuración predeterminada código hash, es igualY Encadenar Los métodos generados por Javac representan efectivamente el estado interno del registro, por lo tanto, si un campo está colapsado, los métodos deberían reflejarlo. Encadenar debe imprimir el nombre minimizado. Si se elimina un campo, por ejemplo porque tiene un valor constante en todas las instancias, los métodos deberían reflejar esto; El campo es ignorado por código hash, es igualY Métodos toString. Cuando R8 usa la estructura de registro en los métodos generados por javac, por ejemplo, cuando busca campos en el registro o examina la estructura del registro impreso, usa la reflexión. Como con cualquier uso del reflejo, debe escribir reglas de retención para informar al reductor de usar el reflejo para que pueda preservar la estructura.

Supongamos que en nuestro ejemplo Viejo es la constante 42 en toda la aplicación mientras que Apellido no es constante a lo largo de la aplicación. Entonces Encadenar devuelve diferentes resultados dependiendo de las reglas que establezca:

Person(“John”, 42).toString();

>>> Person[name=John, age=42]

>>> a[a=John]

>>> Person[b=John]

>>> a[name=John]

>>> a[a=John, b=42]

>>> Person[name=John, age=42]

Casos de uso reflexivo

Mantener Encadenar Comportarse

Suponga que tiene un código que toma la expresión exacta del registro y asume que permanecerá igual. Para hacer esto, debe conservar el contenido completo de los campos de registro con una regla como esta:

-keep,allowshrinking class Person
-keepclassmembers,allowoptimization class Person { <fields>; }

Esto asegura que cuando el persona El registro se conserva en la salida, independientemente Encadenar call produce exactamente la misma cadena de caracteres que en el programa original. Por ejemplo:

Person("John", 42).toString();
>>> Person[name=John, age=42]

Sin embargo, si solo desea mantener la impresión en los campos que realmente se usan, puede hacer que los campos que no se usan se eliminen o se reduzcan. permite encoger:

-keep,allowshrinking class Person
-keepclassmembers,allowshrinking,allowoptimization class Person { <fields>; }

Con esta regla, el compilador descarta el Viejo Campo:

Person("John", 42).toString();
>>> Person[name=John]

Preservar los miembros del registro para la búsqueda reflexiva

Por lo general, cuando necesita acceder reflexivamente a un miembro del registro, debe acceder a su método de acceso. Para hacer esto, debe mantener el método de acceso:

-keep,allowshrinking class Person
-keepclassmembers,allowoptimization class Person { java.lang.String name(); }

Si ahora los casos de persona Puede buscar con seguridad la existencia del descriptor de acceso en el resto del programa:

Person("John", 42)::class.java.getDeclaredMethod("name").invoke(obj);
>>> John

Observe que el código anterior accede al campo de registro a través del descriptor de acceso. Para el acceso directo al campo, debe mantener el campo en sí:

-keep,allowshrinking class Person
-keepclassmembers,allowoptimization class Person { java.lang.String name; }

Construir sistemas y morir Registro Clase

Si está utilizando un sistema de compilación que no sea AGP, el uso de conjuntos de datos puede requerir un ajuste en el sistema de compilación. El java.lang.Record La clase solo está presente con Android 14, introducido en el SDK de "Android UpsideDownCake Preview" Revisión 4. D8/R8 lo presenta com.android.tools.r8.RecordTag, una clase vacía para indicar que una subclase de registro es un registro. El Etiqueta de registro utilizado para hacer referencia a declaraciones java.lang.Record puede ser reescrito directamente por desazucarado para hacer referencia Etiqueta de registro y aún funcionan (firmas de instancia, método y campo, etc.).

Esto significa que cada compilación contiene una referencia a java.lang.Record crea un sintético Etiqueta de registro Clase. En una situación en la que una aplicación se divide en fragmentos, donde cada fragmento se compila en un archivo dex y los archivos dex se ensamblan sin fusionarse en la aplicación de Android, esto puede generar duplicados. Etiqueta de registro Clase.

Para evitar el problema, cada compilación intermedia D8 genera este Etiqueta de registro Clase como una clase sintética global en una salida que no sea el archivo dex. El paso Dex-Merge luego puede fusionar correctamente elementos sintéticos globales para evitar un comportamiento inesperado en tiempo de ejecución. Cualquier sistema de compilación que use compilaciones múltiples, como fragmentación o versiones intermedias, debe admitir síntesis globales para que funcionen correctamente. AGP es totalmente compatible con conjuntos de datos de la versión 8.1.

Si quieres conocer otros artículos parecidos a Grabaciones en Android Studio Flamingo puedes visitar la categoría Android.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Subir