Fundamentos de Android: Niveles API
A la hora de desarrollar aplicaciones para Android, una de las cosas que hay que tener en cuenta es la gran cantidad de dispositivos distintos en los que potencialmente se podría ejecutar, no solo smartphones, con distintas características de hardware y distintas versiones de sistema operativo, sino otros dispositivos como tablets, entre otros. Por eso se deben considerar ciertas restricciones y estrategias de compatibilidad cuando se escriben apps para Android.
Para hacer esto dos buenos métodos son, por un lado, orientar la aplicación a un nivel específico de la API de Android (versión) y, por otro, usar las bibliotecas de Android Jetpack para admitir dispositivos más antiguos, puesto que cada versión del sistema operativo viene con nuevas características y funcionalidades. Vamos a analizar ambas técnicas.
Recuerda que cuando se crea un proyecto con Android Studio, en la ventana 'Configure your project', en 'Minimun API level' se indica un nivel mínimo específico de la API de Android que la aplicación admite, que podemos escoger entre las opciones de un desplegable que contiene las distintas versiones de Android. Los niveles API corresponden a las versiones de Android, y éstas se identifican además de por un número de API, por un número de versión y un nombre (usualmente un dulce o un postre) en orden alfabético, por ejemplo: API 23: Android 6.0 (Marshmallow).
Al seleccionar una versión API determinada, especificamos la versión más antigua de Android que admite la aplicación, y Android Studio nos indica el porcentaje de dispositivos en los que puede funcionar nuestra app. Además, si tenemos dudas sobre cuál elegir, podemos pulsar sobre el enlace Help me choose y se abrirá una ventana con información más detallada sobre cada versión:
Pero la aplicación no solo tiene el nivel al que se dirige, también tiene un nivel al que se compila. Cada uno de estos niveles es un parámetro de configuración en los archivos de compilación de Gradle.
Para ilustrar esto vamos a utilizar como ejemplo el código del proyecto 'TirarDados' que hemos utilizado en entradas anteriores (ver Fundamentos de Android: Recursos de imagen). Expandimos la carpeta Gradle Scripts, y abrimos el archivo build.gradle (Module: app). Este archivo define los parámetros de compilación y las dependencias específicas del módulo de la aplicación -por su parte, el archivo build.gradle (Project: TirarDados) define los parámetros de compilación para el proyecto como un todo. En build.gradle (Module: app) encontramos esta sección:
android { compileSdkVersion 28 defaultConfig { applicationId "com.android.tirardados" minSdkVersion 23 targetSdkVersion 28 versionCode 1 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" }
El parámetro compileSdkVersion especifica el nivel de API de Android que Gradle usa para compilar la aplicación, y ésta es la versión más reciente de Android que la aplicación puede admitir. Es decir, la aplicación puede utilizar las funciones incluidas en este nivel de API y anteriores. En el ejemplo, la aplicación es compatible con API 28, que corresponde a Android 9 (Pie).
También vemos, dentro de la sección defaultConfig, el parámetro targetSdkVersion. Su valor, que en muchos casos coincide con el valor de compileSdkVersion, es la API más reciente en la que se ha probado la aplicación.
Aunque quizá el parámetro más importante es minSdkVersion, ya que determina la versión más antigua de Android en la que se ejecutará la aplicación, y los dispositivos que ejecutan un sistema operativo Android anterior a este nivel de API no pueden ejecutar la aplicación en absoluto. Elegir el nivel de API mínimo para una aplicación puede ser un desafío con importantes implicaciones, puesto que si se establece un nivel de API demasiado bajo se perderán las funciones más recientes del sistema operativo Android, y si se establece demasiado alto la aplicación solo podrá ejecutarse en dispositivos más nuevos.
Por eso cuando creas un nuevo proyecto reflexiona un momento antes de definir el nivel de API mínimo para la aplicación y repasa la información del enlace Help me choose que vimos antes.
Además de definir los niveles de API de la aplicación, una buena estrategia para aumentar la compatibilidad de nuestra aplicación es utilizar Android Jetpack, que es una colección de bibliotecas de soporte que ofrece clases compatibles con versiones anteriores y funciones útiles para ampliar la compatibilidad.
Abre el archivo de la actividad (MainActivity) y observa que la clase MainActivity extiende AppCompatActivity, que es una clase de compatibilidad que garantiza que la actividad tenga el mismo aspecto en distintos niveles del sistema operativo. Observa también las importaciones para esta clase: ten en cuenta que el espacio de nombres para las bibliotecas de Android Jetpack es androidx y que la clase AppCompatActivity se importa desde el paquete androidx.appcompat.app.
Ahora abre build.gradle (Module: app) y observa en la sección de dependencias la dependencia de la biblioteca appcompat, que forma parte de androidx y contiene la clase AppCompatActivity:
dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation 'androidx.appcompat:appcompat:1.0.0-beta01' implementation 'androidx.core:core-ktx:1.2.0-alpha02' implementation 'androidx.constraintlayout:constraintlayout:1.1.2' testImplementation 'junit:junit:4.12' androidTestImplementation 'androidx.test:runner:1.1.0-alpha4' androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0-alpha4' }
En definitiva, suele ser recomendable usar una clase de compatibilidad de las bibliotecas de Jetpack porque amplían el soporte para una gran cantidad de funciones y dispositivos.
Ahora vamos a aplicar algunos de estos conceptos a nuestro proyecto 'TirarDados' para optimizar su tamaño y aumentar así su compatibilidad en dispositivos más antiguos.
En la carpeta res desplegamos drawable y abrimos una imagen del dado. Como ya sabemos, estas imágenes son archivos xml, esto es, vectores, que frente a otros formatos de imagen se pueden escalar sin perder calidad y suelen ocupar menos espacio. Pero estos vectores son compatibles solo a partir de la API 21, aunque si en tu aplicación tienes minSdkVersion con una API inferior (no es mi caso: minSdkVersion 23) verás que también funciona, y esto es así porque Gradle genera un archivo PNG de cada uno de los archivos vectoriales, y esos archivos PNG se usan en cualquier dispositivo Android inferior a 21. Pero esto también genera un mayor tamaño de la aplicación que ocupará más espacio en el dispositivo.
Para resolver esto, a partir de la API 7 podemos usar una biblioteca de compatibilidad de Android X para vectores, agregando en el archivo build.gradle (Module: app), en la sección defaultConfig (recuerda que esto solo es necesario si la minSdkVersion de tu aplicación es inferior a 21):
vectorDrawables.useSupportLibrary = true
Después debes pulsar sobre el botón Sync Now porque cada vez que se modifica un archivo build.gradle los archivos de compilación se deben sincronizar con el proyecto. Abrimos el archivo de diseño main_activity.xml y agregamos el espacio de nombres 'app' a la etiqueta raíz LinearLayout, debajo del espacio de nombres tools:
xmlns:app="http://schemas.android.com/apk/res-auto"
Este espacio de nombres ('app') es para los atributos que provienen de código personalizado o de las bibliotecas, y no del marco central de Android. Ahora en el elemento ImageView cambiamos el atributo android:src para que sea app:srcCompat:
app:srcCompat="@drawable/dado_vacio"
El atributo app:srcCompat utiliza la biblioteca X de Android para admitir los vectores en versiones anteriores de Android, hasta la API de nivel 7. Si compilas y ejecutas la aplicación no verás nada diferente pero ahora la aplicación no necesita generar los archivos PNG para las imágenes vectoriales, lo que significa una aplicación de tamaño más reducido.
Comentarios
Publicar un comentario