Objetivo del flujo de publicación
Publicar una app Android implica convertir tu proyecto en un paquete distribuible (APK o AAB) firmado, versionado y configurado para release, con un mínimo de información para el usuario (por ejemplo, pantalla “Acerca de”) y una verificación final de estabilidad. En este capítulo seguirás un flujo reproducible: preparar versiones → configurar build types y firmado → generar APK/AAB → validar permisos y ajustes mínimos → ejecutar checklist final.
1) Versionado: cómo se identifica tu app en cada entrega
Conceptos clave
- versionCode: entero que debe aumentar en cada publicación. Android/Play lo usa para ordenar actualizaciones.
- versionName: texto visible para el usuario (por ejemplo,
1.4.2). - applicationId: identificador único del paquete (por ejemplo,
com.tuempresa.tuapp). No debe cambiar en producción porque rompe actualizaciones.
Dónde se configura (Gradle)
En proyectos modernos, la configuración suele estar en app/build.gradle.kts dentro de android { defaultConfig { ... } }. Ejemplo:
android { namespace = "com.tuempresa.tuapp" defaultConfig { applicationId = "com.tuempresa.tuapp" minSdk = 24 targetSdk = 34 versionCode = 12 versionName = "1.2.0" }}Estrategia práctica de versionado
- Usa
versionNamecon semver (MAJOR.MINOR.PATCH) para comunicar cambios. - Incrementa
versionCodesiempre, incluso si solo cambias metadatos. - Si tienes CI/CD, automatiza el incremento de
versionCode(por ejemplo, con un script que lea el último valor y sume 1).
2) Build types y configuración de release
Qué son build types
Los buildTypes definen variantes de compilación. Normalmente tendrás debug para desarrollo y release para distribución. En release se suelen activar optimizaciones, ofuscación y se deshabilitan logs/flags de depuración.
Configuración recomendada en Gradle (Kotlin DSL)
Ejemplo típico de buildTypes con minificación y shrink de recursos:
android { buildTypes { debug { applicationIdSuffix = ".debug" versionNameSuffix = "-debug" isDebuggable = true } release { isDebuggable = false isMinifyEnabled = true isShrinkResources = true proguardFiles( getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro" ) } }}ProGuard/R8: reglas mínimas
Si usas librerías que dependen de reflexión/serialización, podrías necesitar reglas. Mantén el archivo proguard-rules.pro bajo control de versiones y añade reglas solo cuando detectes problemas reales (crashes en release, clases eliminadas, etc.).
- Escuche el audio con la pantalla apagada.
- Obtenga un certificado al finalizar.
- ¡Más de 5000 cursos para que explores!
Descargar la aplicación
Ejemplo de regla genérica (solo como referencia; ajústala a tu caso):
# Mantener clases usadas por reflexión (ejemplo) -keep class com.tuempresa.tuapp.model.** { *; }3) Firmado: keystore, claves y seguridad
Por qué es obligatorio firmar
Android exige que los paquetes estén firmados. La firma identifica al autor y permite que futuras actualizaciones sean aceptadas como la misma app. Si pierdes la clave de firma, no podrás actualizar la app publicada con esa identidad.
Crear un keystore (desde Android Studio)
Flujo recomendado:
- Abre Build → Generate Signed Bundle / APK.
- Elige Android App Bundle (AAB) o APK.
- En la pantalla de firma, selecciona Create new... para crear un
.jks. - Completa: ruta del keystore, contraseña, alias, contraseña del alias y datos del certificado.
- Guarda el
.jksen un lugar seguro (idealmente fuera del repositorio) y respáldalo.
Buenas prácticas de seguridad
- No subas el
.jksal repositorio. - No hardcodees contraseñas en Gradle.
- Usa
gradle.propertieslocal o variables de entorno en CI.
Configurar signingConfigs en Gradle (sin exponer secretos)
Una forma práctica es leer valores desde propiedades. En tu máquina, define en ~/.gradle/gradle.properties (archivo local):
RELEASE_STORE_FILE=/ruta/segura/tuapp-release.jksRELEASE_STORE_PASSWORD=tu_passwordRELEASE_KEY_ALIAS=tu_aliasRELEASE_KEY_PASSWORD=tu_password_aliasLuego, en app/build.gradle.kts:
android { signingConfigs { create("release") { val storeFilePath = providers.gradleProperty("RELEASE_STORE_FILE").orNull if (storeFilePath != null) { storeFile = file(storeFilePath) } storePassword = providers.gradleProperty("RELEASE_STORE_PASSWORD").orNull keyAlias = providers.gradleProperty("RELEASE_KEY_ALIAS").orNull keyPassword = providers.gradleProperty("RELEASE_KEY_PASSWORD").orNull } } buildTypes { release { signingConfig = signingConfigs.getByName("release") } }}Si compilas en CI, configura esas propiedades como secretos del sistema y expórtalas como variables de entorno o inyecta un gradle.properties temporal.
4) Elegir formato de entrega: APK vs AAB
Cuándo usar cada uno
- AAB (Android App Bundle): recomendado para tiendas como Google Play. Permite entrega optimizada por dispositivo (split APKs) y reduce tamaño.
- APK: útil para distribución directa, pruebas internas, MDM/enterprise o sideloading controlado.
Generar AAB/APK firmado desde Android Studio
- Menú Build → Generate Signed Bundle / APK.
- Selecciona Android App Bundle o APK.
- Elige el keystore/alias, selecciona el build type
release. - Finaliza y localiza el artefacto generado (Android Studio muestra la ruta).
Generar por línea de comandos (útil para CI)
Desde la raíz del proyecto:
# AAB./gradlew :app:bundleRelease# APK./gradlew :app:assembleReleaseLos outputs suelen quedar en app/build/outputs/ (por ejemplo, bundle/release o apk/release).
5) Configuración de release: flags, endpoints y logging
Separar comportamiento por build type
Es común que debug use endpoints de staging, logs verbosos o herramientas internas, mientras que release debe ser más estricto. Puedes usar buildConfigField para inyectar constantes:
android { buildTypes { debug { buildConfigField("String", "BASE_URL", "\"https://staging.api.tuapp.com/\"") } release { buildConfigField("String", "BASE_URL", "\"https://api.tuapp.com/\"") } }}Y consumirlo:
val baseUrl = BuildConfig.BASE_URLDesactivar logs en release
Evita imprimir información sensible. Una práctica simple es encapsular logs y no ejecutarlos en release:
object AppLog { fun d(tag: String, msg: String) { if (BuildConfig.DEBUG) android.util.Log.d(tag, msg) }}6) Pantalla de ajustes mínimos: “Acerca de” y versión
Qué debe incluir
- Nombre de la app.
- Versión (versionName y opcionalmente versionCode).
- Enlace a políticas/soporte (si aplica).
- Licencias de terceros (si tu app lo requiere).
Obtener la versión desde el sistema
Puedes leer la versión desde BuildConfig.VERSION_NAME y BuildConfig.VERSION_CODE. En Android moderno, VERSION_CODE puede ser Int o Long según configuración; para mostrarlo, conviértelo a texto.
val versionName = BuildConfig.VERSION_NAMEval versionCode = BuildConfig.VERSION_CODE.toString()Ejemplo práctico con Jetpack Compose
Una pantalla simple de ajustes con sección “Acerca de”:
@Composablefun SettingsScreen( onOpenLicenses: (() -> Unit)? = null,) { val versionName = BuildConfig.VERSION_NAME val versionCode = BuildConfig.VERSION_CODE.toString() androidx.compose.material3.Surface { androidx.compose.foundation.layout.Column( modifier = androidx.compose.ui.Modifier .fillMaxSize() .padding(16.dp) ) { androidx.compose.material3.Text( text = "Ajustes", style = androidx.compose.material3.MaterialTheme.typography.headlineSmall ) androidx.compose.foundation.layout.Spacer(Modifier.height(16.dp)) androidx.compose.material3.Text( text = "Acerca de", style = androidx.compose.material3.MaterialTheme.typography.titleMedium ) androidx.compose.foundation.layout.Spacer(Modifier.height(8.dp)) androidx.compose.material3.Text(text = "Versión: $versionName ($versionCode)") if (onOpenLicenses != null) { androidx.compose.foundation.layout.Spacer(Modifier.height(12.dp)) androidx.compose.material3.Button(onClick = onOpenLicenses) { androidx.compose.material3.Text("Licencias") } } } }}Si ya tienes navegación, registra esta pantalla como un destino más y enlázala desde un ícono de ajustes o desde un menú.
7) Revisar permisos declarados antes de liberar
Por qué importa
Los permisos afectan confianza del usuario, cumplimiento de políticas y fricción de instalación. Antes de publicar, asegúrate de declarar solo lo necesario.
Checklist de revisión de AndroidManifest.xml
- Elimina permisos que ya no uses (por ejemplo, si quitaste una funcionalidad).
- Verifica que los permisos “sensibles” estén justificados por una funcionalidad visible.
- Revisa
uses-featuresi declaras hardware específico (cámara, GPS, etc.). - Confirma que no estás declarando permisos por dependencias que no necesitas (a veces se pueden remover con herramientas de manifest merge).
Cómo inspeccionar el manifest final (merged manifest)
En Android Studio, revisa el Merged Manifest del módulo app para ver el resultado final tras el merge de dependencias. Ahí podrás identificar de dónde viene cada permiso.
Ejemplo: remover un permiso agregado por una librería
Si una dependencia agrega un permiso no deseado, puedes removerlo con tools:node="remove" (requiere el namespace tools):
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"> <uses-permission android:name="android.permission.SOME_PERMISSION" tools:node="remove" /></manifest>Úsalo con cuidado: si la librería realmente lo necesita, podrías romper funcionalidad.
8) Checklist final de estabilidad antes de generar el paquete
Checklist técnico (reproducible)
| Área | Verificación | Cómo comprobar |
|---|---|---|
| Compilación | Build release compila sin warnings críticos | ./gradlew :app:assembleRelease o :app:bundleRelease |
| Arranque | La app abre en frío sin crash | Instala el artefacto release en un dispositivo real |
| Navegación | Flujos principales completos | Recorre pantallas clave: login/onboarding/home/detalle |
| Red | Manejo de offline/errores | Modo avión, red lenta, respuestas 4xx/5xx simuladas |
| Persistencia | Migraciones/lectura de datos | Actualizar desde versión anterior (si aplica) y validar datos |
| Permisos | Solo los necesarios y con UX correcta | Revisar manifest final y flujos que los solicitan |
| Rendimiento | Sin ANR en flujos comunes | Uso normal + monitoreo básico en dispositivo |
| Minificación | Release funciona con R8 | Probar build minificado; revisar crashes por clases removidas |
| Logs/seguridad | No exponer tokens/PII en logs | Buscar logs en código y validar que se deshabiliten en release |
| UI | Textos y layouts correctos | Probar tamaños de pantalla y modo oscuro si aplica |
Validación del artefacto generado
- Verifica la firma: asegúrate de que el artefacto sea “signed”.
- Verifica versión: que
versionCodeyversionNamesean los esperados. - Instalación limpia: desinstala versiones previas e instala el release.
- Actualización: si ya existe una versión instalada, instala la nueva para confirmar que actualiza correctamente (misma firma y mismo
applicationId).
9) Flujo reproducible: del proyecto al paquete listo para liberar
Paso a paso
- Actualizar versionado en
defaultConfig: incrementaversionCodey ajustaversionName. - Revisar buildTypes: confirma que
releasetengaisDebuggable = false, minificación/shrink según tu necesidad y reglas R8 listas. - Configurar firmado: crea/selecciona keystore y configura
signingConfigsleyendo secretos desde propiedades. - Revisar configuración de release: endpoints, flags y logging (evitar información sensible).
- Agregar/validar pantalla de ajustes mínimos: “Acerca de” con versión visible.
- Auditar permisos en el merged manifest: elimina permisos innecesarios y valida
uses-feature. - Ejecutar checklist de estabilidad en un dispositivo real y, si puedes, en al menos un emulador adicional.
- Generar artefacto: preferentemente AAB para tienda, o APK para distribución directa.
- Validar artefacto: firma, versión, instalación limpia y actualización.
Comandos mínimos para automatizar
# 1) Limpiar y compilar release./gradlew clean :app:bundleRelease# 2) Alternativa: APK./gradlew clean :app:assembleReleaseCon este flujo, tu proyecto queda preparado para entregar un paquete de release consistente, firmado y verificable.