Desempaquetado y reempaquetado
Antes conviene leer «Anatomía de un APK»
1. Qué es y por qué existe
Un APK es un ZIP, así que abrirlo es trivial: unzip basta. El problema es que lo que hay
dentro no se puede leer. AndroidManifest.xml no es XML de texto sino AXML binario;
resources.arsc es una tabla binaria de recursos; los .xml de res/ están compilados; y el
código está en DEX. Desempaquetar, aquí, es decodificar ese contenido a algo editable, y
reempaquetar es rehacer el camino inverso: la operación central de cualquier modificación de
una aplicación, y donde más cosas se rompen.
La razón de que se rompan tiene nombre. Durante quince años la única forma de reconstruir los
recursos ha sido volver a compilarlos con aapt2, la herramienta de Google — que no está
pensada para reconstruir sino para construir desde fuentes. La decodificación pierde
información que aapt2 luego tiene que adivinar, y cuando adivina mal el APK sale distinto
del original o directamente no sale. La alternativa moderna es no volver a compilar nada:
leer los binarios, mutar lo que haga falta y reescribirlos tal cual. Ese es el reparto de
aguas de todo el documento, y la línea que separa a apktool de APKEditor.
2. apktool
La herramienta clásica. Casi todo el ecosistema de interfaces gráficas de Android es un envoltorio suyo.
2.1 Ficha
| Dato | Valor |
|---|---|
| Repositorio | https://github.com/iBotPeaches/Apktool |
| Licencia · lenguaje | Apache-2.0 · Java |
| Versión estable | 3.0.3, publicada el 20 de julio de 2026 |
| Último push · creado | 11 de agosto de 2026 · 19 de marzo de 2012 |
| Tracción | 25.265 ★ · 3.980 forks · 77 issues abiertas · no archivado |
Consultado el 12 de agosto de 2026 vía la API de GitHub. Activo y bien mantenido: catorce años de historia, commits del día anterior a la consulta, y una rama 3.x reciente. Que solo tenga 77 issues abiertas frente a las 441 de jadx habla de una gestión de cola disciplinada.
El salto de la rama 2.x a la 3.x es relevante porque rompió compatibilidad de flags, y eso se propagó a todas las interfaces que lo empaquetan. La evaluación de las trece herramientas recoge el efecto: APKToolGUI tuvo que publicar un «Fixed invalid flags for Apktool to 3.0.x», y la issue #50 de ese proyecto documenta que recompilar un XAPK que antes funcionaba empezó a fallar con «resource ... is private» al pasar a apktool 3.0.1.
2.2 El workflow: d y b
app.apk ──apktool d──▶ app/ ──[editar]──▶ app/ ──apktool b──▶ app/dist/app.apk
Dos verbos —d/decode para pasar de APK a un árbol de ficheros editables, y b/build
para el camino inverso— más un tercero, if/install-framework, que registra un APK de
framework para poder decodificar aplicaciones que dependan de él («sección 2.7 · Ficheros de framework»).
El APK que sale de b no está firmado ni alineado. apktool no firma, por diseño. El
ciclo se cierra con las herramientas de «Firma y empaquetado», y
saltarse ese paso es el error número uno de quien empieza.
2.3 Qué produce apktool d
app/
├── AndroidManifest.xml ← AXML decodificado a XML de texto
├── apktool.yml ← metadatos para poder reconstruir
├── res/ ← recursos decodificados: values/, layout/, drawable/…
│ ├── values/strings.xml (reconstruido desde resources.arsc)
│ ├── values/public.xml (los resource ID originales, para preservarlos)
│ └── layout/activity_main.xml (AXML decodificado)
├── smali/ ← classes.dex desensamblado
├── smali_classes2/ ← classes2.dex, y sucesivos con multidex
├── assets/ ← copiado tal cual
├── lib/ ← copiado tal cual
├── original/ ← AndroidManifest.xml binario y META-INF originales
└── unknown/ ← todo lo que no encaja en la estructura estándar
Tres directorios merecen comentario. res/values/public.xml guarda la asignación original
de resource ID a nombre: sin él la recompilación asignaría identificadores nuevos y el código
que referencia un recurso por su número —que es como lo hace el DEX— apuntaría a otra cosa.
Es la pieza frágil que hace posible el round-trip de recursos con aapt2. original/
conserva el AndroidManifest.xml binario y el META-INF/ intactos, para el flag
--copy-original de b. Y unknown/ recoge las entradas del ZIP ajenas a la estructura
conocida, anotándolas en apktool.yml para recolocarlas: un mecanismo honesto de «no entiendo
esto pero no lo pierdo», que es el mismo principio que se aplica a los chunks desconocidos de
resources.arsc.
2.4 apktool.yml
El fichero de estado que hace posible reconstruir. Sin él, apktool b no sabe qué decisiones
tomó el d.
En apktool 3.x el esquema son exactamente nueve campos, los que declara
brut.androlib.meta.ApkInfo. No son los mismos que en 2.x, y la diferencia importa porque casi
todo lo que circula por foros describe el esquema viejo:
| Campo | Para qué sirve |
|---|---|
version |
Versión de apktool que hizo el decode |
apkFileName |
Nombre del fichero original |
usesFramework |
Qué frameworks —ids— y con qué tag hacen falta para reconstruir |
usesLibrary |
Bibliotecas compartidas declaradas. Sustituye al sharedLibrary de 2.x; se fuerza con -l/--lib |
sdkInfo |
minSdkVersion, targetSdkVersion y maxSdkVersion, para repoblarlos en el manifiesto al construir |
versionInfo |
versionCode y versionName, para repoblarlos al construir |
resourcesInfo |
packageId, packageName, sparseEntries, compactEntries, keepRawValues. Absorbe el packageInfo y el sparseResources de 2.x |
featureFlags |
Los feature flags del manifiesto, con su valor |
doNotCompress |
Qué extensiones deben quedar stored y no deflate |
Y estos, que sí estaban en 2.x, ya no existen: isFrameworkApk, packageInfo,
compressionType, sharedLibrary, sparseResources y unknownFiles. No es que se omitan
cuando no hacen falta: no están en el esquema y no pueden emitirse nunca.
El patrón es claro: sdkInfo y versionInfo existen porque la información se pierde en el
viaje. aapt2 exige que esos valores se le pasen por parámetro al enlazar, así que apktool
los extrae, los guarda y se los devuelve. Es un parche a la asimetría entre decodificar y
compilar, y explica por qué el fichero es crítico: corromperlo hace que la reconstrucción
produzca un APK distinto del original. doNotCompress es el que más se nota — si se pierde,
entradas que debían ir sin comprimir (resources.arsc, y las .so cuando extractNativeLibs
es false) salen comprimidas, y la aplicación falla al instalar o al cargar sus bibliotecas
nativas.
Y esto es lo que escribe de verdad. Generado con apktool 3.0.3 sobre
com.termux_1002.apk del «muestrario» el 13 de agosto de 2026:
version: 3.0.3
apkFileName: com.termux_1002.apk
usesFramework:
ids:
- 1
sdkInfo:
minSdkVersion: 24
targetSdkVersion: 28
versionInfo:
versionCode: 1002
versionName: 0.118.3
resourcesInfo:
packageId: 127
doNotCompress:
- arsc
- ogg
- png
Siete de los nueve campos: usesLibrary y featureFlags se omiten por estar vacíos. Y
unknownFiles no aparece pese a que el desempaquetado sí produjo un directorio unknown/
con cinco entradas — porque en 3.x ese campo ya no existe: el directorio se recoloca al
construir recorriéndolo, no leyendo una lista del yml. Quien venga del esquema de 2.x buscará
esa clave y no la va a encontrar nunca.
Nótese también que doNotCompress no lista ficheros sino extensiones —arsc, ogg,
png—, que es como apktool decide qué dejar stored al reconstruir.
⚠️ El inventario de campos es firme: sale de javap -p brut.androlib.meta.ApkInfo sobre el
apktool_3.0.3.jar instalado, más las clases anidadas SdkInfo, VersionInfo,
ResourcesInfo y UsesFramework. Lo que no procede de documentación oficial es la
redacción de para qué sirve cada uno, porque no la hay: la página
apktool.org/docs/the-basics/apktool-yml
devolvía HTTP 404 el 12 de agosto de 2026. Las definiciones de la tabla se agregaron de
fuentes de la comunidad y del gestor de incidencias del proyecto.
2.5 La dependencia de aapt2, que es el origen de casi todos sus fallos
Esta es la sección que explica apktool. apktool b no escribe resources.arsc: escribe
XML de texto en un directorio temporal y llama a aapt2 para que los compile y los enlace. La
opción --aapt <FILE> permite señalar un binario concreto en lugar del que trae dentro.
De ahí salen tres familias de problemas:
1. aapt2 compila, no reconstruye. Está escrito para transformar fuentes de un proyecto en
un APK nuevo. Cuando se le pide reproducir un APK existente aplica sus propias reglas:
optimiza PNG, normaliza valores, reordena entradas y resuelve referencias a su manera. El
resultado es funcionalmente parecido y byte a byte distinto — para un editor, la diferencia
entre modificar un fichero y regenerarlo.
2. Lo que apktool no supo decodificar, aapt2 no lo puede recompilar. El error clásico es
«resource ... is private»: el APK original referencia un recurso del framework marcado como
privado, cosa legal en un binario ya construido pero que aapt2 rechaza al enlazar, porque su
trabajo es impedir precisamente eso. También aparecen fallos con namespaces, atributos
desconocidos y frameworks desalineados — el patrón de queja recurrente que identifica la
evaluación de las trece herramientas.
3. La versión de aapt2 importa. Las interfaces gráficas que congelan su apktool —y con él
su aapt2— se van rompiendo solas con el tiempo sin tocar una línea de código. Es exactamente
lo que la evaluación documenta de APK Editor Studio, congelada en apktool 2.x desde enero de
2025.
Hay un cuarto problema, de otro orden: apktool ni siquiera preserva el esquema de firma al decodificar, con su issue #3779 abierta desde enero de 2025.
Confirmado directamente el 13 de agosto de 2026 con
gh api repos/iBotPeaches/Apktool/issues/3779: la incidencia se titula
«[BUG]Preserve original signature scheme v2, v3, v4 in modded APK», se abrió el 23 de enero
de 2025, acumula tres comentarios y sigue abierta diecinueve meses después. El dato de la
hoja de ruta era correcto.
Y un quinto que no está en ninguna lista de problemas conocidos porque no parece un problema:
apktool devuelve código de salida 0 sobre entradas que no entiende. Dándole un .xapk o
un .aab no protesta, no avisa y no crea el directorio smali/; copia el contenido sin abrir
a un subdirectorio unknown/ y termina con éxito. Medido y con la salida literal en la
«sección 6.1 de Formatos y extensiones · jadx los abre todos, y apktool ninguno». Quien
encadene apktool d en un guion y compruebe $? no se entera de nada.
2.6 -r/--no-res: cuándo salva la papeleta
apktool d -r app.apk -o app/
Con --no-res, apktool no decodifica los recursos: deja resources.arsc y los .xml de
res/ tal cual, en binario, y los copia sin tocar al reconstruir — así que aapt2 no
interviene en la parte de recursos. Sirve cuando solo hay que tocar código y los recursos dan
igual (el caso mayoritario), cuando la decodificación de recursos falla sin arreglo posible, o
cuando se quiere garantizar que salen idénticos a los de entrada. Su hermano -s/--no-src
hace lo simétrico: no desensambla el DEX, lo copia.
La combinación de los dos es el diagnóstico: si apktool d falla, probar con -r; si con
-r funciona, el problema está en los recursos. Si con -s funciona, está en el código.
Otras opciones que importan del decode — la lista completa está en la fuente 2:
| Opción | Efecto |
|---|---|
-f, --force · -o <DIR> |
Borra el destino si existe · directorio de salida |
--only-manifest |
Solo el manifiesto. Ojo: --force-manifest, que aún se cita por ahí, no existe en apktool 3.0.3 |
--keep-broken-res |
Continúa aunque haya recursos que no se pudieron decodificar |
--match-original |
Los ficheros generados se parecen lo más posible a los originales, a costa de que el resultado ya no se pueda reconstruir («prevents rebuild») |
--no-debug-info |
baksmali no emite .line, .local ni .param |
-a, --all-src |
Desensambla todos los .dex, incluidos los no estándar |
-p <DIR> · -t <TAG> |
Dónde están los frameworks · cuál usar |
Y del build:
| Opción | Efecto |
|---|---|
--aapt <FILE> |
Usa ese binario de aapt2 en lugar del interno |
--copy-original |
Reinyecta el AndroidManifest.xml y el META-INF de original/ |
--debuggable |
Añade android:debuggable="true" al manifiesto |
--no-crunch |
No re-optimiza los recursos (evita que aapt2 toque los PNG) |
--net-sec-conf · --no-apk · -o <FILE> |
Network Security Config genérica · no empaqueta · salida |
--no-crunch merece atención: sin él, aapt2 recomprime los PNG al reconstruir, lo que cambia
bytes que nadie pidió cambiar y engorda o adelgaza el APK de forma impredecible.
2.7 Ficheros de framework
Una aplicación referencia recursos del framework de Android (0x01......) y, en dispositivos
de fabricante, de frameworks propios: Samsung, MIUI, HTC. apktool necesita esos APK de
framework para resolver los identificadores al decodificar.
apktool if framework-res.apk
apktool if -t miui framework-miui-res.apk
apktool d -t miui app.apk
El -t/--frame-tag permite tener varios juegos de frameworks conviviendo, uno por ROM. apktool
trae empotrado el framework estándar de Android, así que para aplicaciones normales de Play no
hay que hacer nada; para aplicaciones de sistema sí, y es la causa habitual de que una
aplicación de sistema no decodifique.
3. APKEditor
La alternativa moderna, y la referencia funcional de la categoría.
3.1 Ficha
| Dato | Valor |
|---|---|
| Repositorio | https://github.com/REAndroid/APKEditor |
| Licencia · lenguaje | Apache-2.0 · Java (JAR ejecutable, Java 8+) |
| Versión estable | V1.4.9, publicada el 19 de mayo de 2026 |
| Último push · creado | 19 de mayo de 2026 · 9 de diciembre de 2022 |
| Tracción | 2.269 ★ · 402 forks · 36 issues abiertas · no archivado |
Consultado el 12 de agosto de 2026 vía la API de GitHub. Activo, aunque con una señal que conviene leer: el último push coincide con la fecha de la última release, o sea que llevaba casi tres meses sin commits en el momento de la consulta. La evaluación de las trece herramientas le contabiliza 454.000 descargas de releases y cinco publicaciones en nueve meses, de modo que la cadencia histórica es buena; el hueco es reciente. Su descripción propia lo resume: «Powerful android apk editor - aapt/aapt2 independent».
3.2 Los seis comandos
| Comando | Alias | Qué hace |
|---|---|---|
decode |
d |
Decodifica los recursos binarios a JSON, XML o crudo |
build |
b |
Reconstruye el binario desde JSON, XML o crudo |
merge |
m |
Fusiona splits de un directorio o de un XAPK, APKM o APKS |
refactor |
x |
Renombra los recursos ofuscados a nombres legibles |
protect |
p |
Ofusca los recursos del APK |
info |
— | Vuelca metadatos: manifiesto, permisos, actividades, firmas, locales, xmltree |
Es la superficie de línea de órdenes más amplia del ecosistema, y la evaluación de las trece herramientas concluye que es el único del lote apto para CI y automatización: todo lo demás es interfaz gráfica o extensión de editor.
3.3 decode: los tipos de salida
El flag -t selecciona el modo, y la diferencia importa:
| Tipo | Qué produce | Cuándo usarlo |
|---|---|---|
xml |
Recursos decodificados a XML de texto, estilo apktool | Editar recursos a mano |
json |
Recursos volcados a JSON, con la estructura de la tabla expuesta | Automatizar; es el más fiel a la estructura real |
raw |
Los binarios tal cual, sin decodificar | Round-trip garantizado; solo se toca lo que se toca |
sig |
Los ficheros de firma | Conservar la firma por separado |
El modo raw es la respuesta directa al problema de la «sección 2.5 · La dependencia de aapt2, que es el origen de casi todos sus fallos»: si no se decodifica, no
hay que recompilar, y no hay nada que se pueda perder por el camino. El modo json es el que
expone la tabla de recursos como estructura de datos en vez de como XML reconstruido, que es
una decisión de diseño distinta de la de apktool y notablemente más honesta con el formato.
Otras opciones relevantes: -f fuerza el borrado del destino, -framework indica ficheros de
framework, -res-dir configura el nombre del directorio de recursos, -dex-lib internal|jf elige la biblioteca de DEX, -comment-level controla el detalle de los
comentarios de baksmali, -load-dex fija cuántos DEX se cargan en memoria a la vez,
-no-cache salta la caché y -clean-meta elimina META-INF y firmas.
El README del propio proyecto admite que el valor por defecto de -load-dex 3
sacrifica la corrección de la jerarquía de clases para no agotar la memoria. Es una
confesión valiosa y hay que tenerla presente al leer su salida de smali.
3.4 merge: lo que nadie más hace bien
java -jar APKEditor.jar m -i app.xapk -o app-completo.apk
Acepta un directorio de splits, un .apks, un .xapk o un .apkm, y produce un APK único
instalable. Por el camino fusiona las tablas de recursos, sanea los atributos de split del
manifiesto y controla extractNativeLibs.
Es el estándar de facto de la operación: la evaluación de las trece herramientas señala que las demás que hacen merge lo hacen empaquetando APKEditor por dentro, y que AntiSplit-M —la aplicación de Android más usada para esto— está construida sobre su biblioteca ARSCLib. El proceso completo, con sus casos difíciles, está en «Fusión de splits».
3.5 refactor y protect
refactor deshace la ofuscación de recursos: cuando R8 ha renombrado las entradas y las
rutas de fichero —lo que produce las rutas res/aM.xml de la «sección 5.2 · dump: los subcomandos de inspección»—, refactor recupera
nombres legibles. Es el único del lote de trece que lo hace.
protect hace lo contrario: es un ofuscador propio de recursos y DEX, con niveles de 0 a
5 y una opción confuse-zip. La evaluación recoge que es tan efectivo que un análisis de
malware documenta cómo rompe a apktool, jadx y MobSF a la vez — dato de doble filo, porque
también explica su issue #213, abierta desde octubre de 2025 y sin respuesta: los APK pasados
por protect son bloqueados por Google Play Protect aunque el original no lo fuera.
3.6 Por qué no necesita aapt2, y qué implica
APKEditor lee y escribe resources.arsc y AXML directamente, con su propia biblioteca
(ARSCLib, «sección 4 · ARSCLib: la biblioteca de debajo»). No hay compilación intermedia ni XML de texto de por medio salvo que se
pida. Cuatro consecuencias:
- Reconstruye
APKque apktool corrompe. Es el motivo declarado por el que la gente migra. La issue #228 de APKLab —abierta en marzo de 2026 por un usuario, no por el proyecto— pide integrarlo porque «para muchos APK que fallan al reconstruir con Apktool, APKEditor funciona sin corromper recursos». - No hereda los errores de
aapt2. Los «resource is private» y los conflictos denamespacede la «sección 2.5 · La dependencia de aapt2, que es el origen de casi todos sus fallos» sencillamente no ocurren. - No depende de la versión del SDK instalado. Un JAR y Java 8; nada más.
- Puede hacer round-trip. Lo que no toca, no cambia — y eso es lo que lo convierte en un editor y no en un regenerador.
3.7 Los puntos ciegos
- No firma y no alinea. Por diseño: es un motor. Tras
mergeobuildhay que encadenarapksignero uber-apk-signer a mano, y el flujo no se cierra. - No decompila a Java. Solo
smali. - Issue #211, abierta en septiembre de 2025 y todavía sin resolver: alrededor de veinte
aplicaciones fusionadas desde Play caen con segfault en
libpairipcore.soen Android 14 y 15. El mantenedor respondió el mismo día y lo atribuye al antimanipulación de PairIP, no al merge —«any change on the apk could trigger such crash»—, así que el límite real no es de APKEditor: es que la fusión no puede sortear una verificación de integridad. Afecta a las aplicaciones protegidas con PairIP, cada vez más comunes. - Issue #213, la de Play Protect de la «sección 3.5 · refactor y protect», esa sí sin respuesta del mantenedor.
- Bus factor de 1. Mismo autor que ARSCLib, del que depende por completo. Documentación
limitada a
--helpy soporte por Telegram, sin trazabilidad pública.
El dato de mantenimiento que más pesa es que las dos siguen abiertas casi un año después: el mantenedor contesta —en la #211, en menos de una hora—, pero los fallos difíciles se quedan sin resolver.
4. ARSCLib: la biblioteca de debajo
https://github.com/REAndroid/ARSCLib — Apache-2.0, Java, creado el 14 de noviembre de
2021, último push el 1 de julio de 2026, 402 ★ · 81 forks · 18 issues abiertas. Consultado el
12 de agosto de 2026 vía la API de GitHub. Su descripción: «Android binary resources read/write
library».
Es la pieza que resuelve el problema estructural del documento: leer y escribir los formatos
binarios de recursos de Android sin aapt2. APKEditor es la línea de órdenes; ARSCLib es el
motor — y también el de AntiSplit-M y de varias herramientas más.
Su relevancia aquí es doble. Es la demostración empírica de que el enfoque funciona, con cinco
años en producción; y es la referencia de diseño obligada de cualquier reimplementación,
cuyo tamaño sostiene la estimación del camino crítico — 6 a 12 meses-persona para un escritor
de resources.arsc. El detalle del formato está en
«Tabla de recursos» y
«XML binario».
5. aapt2
La herramienta oficial de Google. Está instalada en la máquina de referencia, así que todo lo de esta sección está ejecutado de verdad.
BT=~/Library/Android/sdk/build-tools/37.0.0
$BT/aapt2 version
Android Asset Packaging Tool (aapt) 2.20-15087165
5.1 compile y link
Los dos verbos que construyen. compile toma un fichero de recurso del proyecto y produce un
.flat intermedio; link toma todos los .flat y produce el resources.arsc y el APK.
res/values/strings.xml ──compile──▶ values_strings.arsc.flat ─┐
res/layout/main.xml ──compile──▶ layout_main.xml.flat ─────┼──link──▶ APK
AndroidManifest.xml ──────────────────────────────────────────┘
La separación existe por velocidad de compilación incremental. Los otros subcomandos son
dump, diff, optimize, convert y daemon. Este par no interesa para el análisis:
interesa para construir desde fuentes, que es lo que hace Gradle. Lo que sí interesa —y
mucho— es dump.
5.2 dump: los subcomandos de inspección
aapt2 dump <subcomando> fichero.apk [opciones], con --no-values, --file <fichero> y -v
como opciones transversales.
| Subcomando | Qué imprime |
|---|---|
badging |
Lo extraído del manifiesto: paquete, versiones, SDK, permisos, actividades, densidades |
packagename |
Solo el nombre de paquete |
permissions |
Los permisos declarados y usados |
configurations |
Todas las configuraciones que hacen variar algún recurso |
resources |
La tabla de recursos completa |
strings |
El string pool de la tabla de recursos |
xmltree |
Un AXML como árbol |
xmlstrings |
Los strings de un AXML compilado |
styleparents |
Los padres de los estilos |
overlayable |
Los recursos sobreescribibles |
apc |
El contenido de un contenedor .flat de compilación |
badging — la primera orden de cualquier análisis.
BT=~/Library/Android/sdk/build-tools/37.0.0
MUESTRAS=~/muestras-apk
$BT/aapt2 dump badging $MUESTRAS/com.termux_1002.apk | head -20
package: name='com.termux' versionCode='1002' versionName='0.118.3' platformBuildVersionName='11' platformBuildVersionCode='30' compileSdkVersion='30' compileSdkVersionCodename='11'
install-location:'internalOnly'
minSdkVersion:'24'
targetSdkVersion:'28'
uses-permission: name='android.permission.ACCESS_NETWORK_STATE'
…
uses-permission: name='com.android.alarm.permission.SET_ALARM'
application-label:'Termux'
Salida recortada: entre medias van otros catorce uses-permission.
packagename — y por qué no hay que fiarse del nombre del fichero.
$BT/aapt2 dump packagename $MUESTRAS/org.fossify.calculator_1.4.0.apk
org.fossify.math
El fichero se llama org.fossify.calculator_1.4.0.apk y el paquete es org.fossify.math. Es
la misma lección que dejan las extensiones de los contenedores:
el nombre miente, y la identidad se lee de dentro.
configurations — el inventario de variantes.
$BT/aapt2 dump configurations $MUESTRAS/org.fossify.calculator_1.4.0.apk | head -20
w600dp
h480dp
h720dp
sw600dp
watch
night
…
night-v31
v34
port
w400dp-port
Es exactamente lo que hay que saber para decidir qué splits de configuración hacen falta al fusionar. La primera línea vacía es la configuración por defecto.
xmltree — el manifiesto con los identificadores de atributo.
$BT/aapt2 dump xmltree $MUESTRAS/com.termux_1002.apk --file AndroidManifest.xml | head -14
N: android=http://schemas.android.com/apk/res/android (line=2)
E: manifest (line=2)
A: http://schemas.android.com/apk/res/android:versionCode(0x0101021b)=1002
A: http://schemas.android.com/apk/res/android:versionName(0x0101021c)="0.118.3" (Raw: "0.118.3")
A: http://schemas.android.com/apk/res/android:sharedUserLabel(0x01010261)=@0x7f1000c5
A: package="com.termux" (Raw: "com.termux")
…
E: uses-sdk (line=10)
A: http://schemas.android.com/apk/res/android:minSdkVersion(0x0101020c)=24
A: http://schemas.android.com/apk/res/android:targetSdkVersion(0x01010270)=28
Salida recortada: entre medias van sharedUserId, installLocation, compileSdkVersion y los
dos platformBuildVersion*. Lo valioso son los 0x0101.... entre paréntesis: son los
resource ID del framework para cada atributo, que es como el AXML los guarda de verdad. El
nombre legible lo resuelve aapt2 consultando su tabla del framework, y un lector propio tiene
que hacer lo mismo — el mecanismo está en
«XML binario». Los atributos sin identificador, como
package, no pertenecen al espacio de nombres de Android y viajan solo por nombre.
resources — la tabla, y una huella de ofuscación.
$BT/aapt2 dump resources $MUESTRAS/org.fossify.calculator_1.4.0.apk | head -12
Binary APK
Package name=org.fossify.math id=7f
type anim id=01 entryCount=40
resource 0x7f010002 anim/abc_grow_fade_in_from_bottom
() (file) res/aM.xml type=XML
resource 0x7f010003 anim/abc_popup_enter
() (file) res/k_.xml type=XML
resource 0x7f010004 anim/abc_popup_exit
() (file) res/UX.xml type=XML
resource 0x7f010005 anim/abc_shrink_fade_out_from_bottom
() (file) res/5T.xml type=XML
Aquí se ve el resource ID con su estructura 0xPPTTEEEE —paquete 7f, tipo 01, entrada—
y, sobre todo, la ofuscación de rutas de recursos en estado puro: el nombre lógico
(anim/abc_popup_enter) se conserva, pero el fichero se llama res/k_.xml. Es justo el
material sobre el que trabaja refactor de APKEditor.
strings — el tamaño del problema. Sobre el mismo APK, la cabecera del volcado dice:
String pool of 31667 unique UTF-8 non-sorted strings, 31667 entries and 78 styles using 1369084 bytes:
31.667 cadenas y 1.369.084 bytes en una calculadora. Y confirma tres cosas que un escritor de
resources.arsc tiene que reproducir exactamente: codificación UTF-8, no ordenado y
78 estilos asociados — los estilos son la parte más delicada del round-trip byte-exacto.
5.3 Por qué reimplementar aapt2 está fuera de alcance para cualquiera
No es una cuestión de esfuerzo sino de superficie. aapt2 incluye el crunch de PNG con su
formato propio de nine-patch, la pseudolocalización, la generación de feature splits, la
compilación de todos los tipos de recurso de Android con sus reglas de validación, el manejo de
overlays, el modo daemon, y la compatibilidad con el histórico entero de niveles de API. Una
estimación razonable lo cifra en 12 a 24 meses-persona, y la conclusión sensata es que la
vía correcta es no necesitarlo, que es exactamente lo que hizo ARSCLib.
Lo que sí hay que hacer con aapt2 es usarlo como oráculo: si escribes un resources.arsc
y aapt2 dump resources lo lee y devuelve lo mismo que el original, tu escritor funciona.
6. bundletool
La herramienta oficial para AAB y APK Set. Está disponible en la máquina de referencia, así
que también está ejecutada de verdad. java -jar bundletool.jar version devuelve 1.18.3,
publicada el 15 de diciembre de 2025 según la API de GitHub, consultada el 12 de agosto de 2026.
6.1 dump
Inspecciona un AAB sin desempaquetarlo:
MUESTRAS=~/muestras-apk
java -jar bundletool.jar dump manifest --bundle $MUESTRAS/splits/com.ejemplo.fixture.aab
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:compileSdkVersion="36" android:compileSdkVersionCodename="16" android:versionCode="1" android:versionName="1.0" package="com.ejemplo.fixture" platformBuildVersionCode="36" platformBuildVersionName="16">
<uses-sdk android:minSdkVersion="24" android:targetSdkVersion="36"/>
<uses-permission android:name="android.permission.INTERNET"/>
<application android:extractNativeLibs="false" android:icon="@drawable/ic_fixture" android:label="@string/app_name">
<activity android:exported="true" android:name=".MainActivity">
…
</activity>
</application>
</manifest>
Salida recortada: se han quitado el intent-filter y las líneas en blanco que bundletool
intercala entre cada elemento al convertir desde protobuf. Con --xpath se extrae un valor
suelto, que es lo que sirve para scripts — --xpath /manifest/@android:versionCode devuelve
1. Los otros objetivos de dump son resources y config.
6.2 validate
java -jar bundletool.jar validate --bundle $MUESTRAS/splits/com.ejemplo.fixture.aab
App Bundle information
------------
Feature modules:
Feature module: base
File: res/drawable-xxhdpi-v4/ic_fixture.png
…
File: lib/arm64-v8a/libtermux.so
File: lib/x86_64/libtermux.so
File: dex/classes.dex
Comprueba la estructura del bundle y lista sus módulos. Nótese dex/classes.dex: dentro de un
AAB el DEX vive bajo dex/, no en la raíz como en un APK.
6.3 build-apks
Convierte el AAB en un APK Set:
java -jar bundletool.jar build-apks \
--bundle=$MUESTRAS/splits/com.ejemplo.fixture.aab \
--output=fixture.apks --overwrite
WARNING: The APKs won't be signed and thus not installable unless you also pass a keystore via the flag --ks. See the command help for more information.
El aviso sale porque la máquina de referencia no tiene ~/.android/debug.keystore. Sin --ks,
bundletool firma con la clave de depuración si ese fichero existe —«attempts to sign your APKs
with a debug key», dice su documentación—, y solo si no existe deja los APK sin firmar y, por
tanto, sin instalar. Para distribuir hay que pasarle --ks, --ks-pass, --ks-key-alias y
--key-pass en cualquier caso. Y esto es lo que hay dentro del APK Set resultante:
unzip -l fixture.apks | head -12
Archive: fixture.apks
Length Date Time Name
--------- ---------- ----- ----
3166 01-01-1981 01:01 toc.pb
25551 01-01-1981 01:01 splits/base-arm64_v8a.apk
25551 01-01-1981 01:01 splits/base-arm64_v8a_2.apk
25551 01-01-1981 01:01 splits/base-arm64_v8a_3.apk
23037 01-01-1981 01:01 splits/base-armeabi_v7a.apk
1283 01-01-1981 01:01 splits/base-de.apk
…
Tres observaciones que no salen en la documentación. La marca de tiempo 01-01-1981 01:01
es constante en todas las entradas: bundletool normaliza las fechas para que la salida sea
reproducible — la misma disciplina de escritura determinista que exige cualquier salida
verificable. Los
sufijos _2 y _3 son variantes del mismo split para distintos rangos de nivel de API, que es
cómo el APK Set cubre la matriz completa de dispositivos. Y toc.pb en la raíz es la tabla de
contenidos en Protocol Buffers que distingue un APK Set genuino de un ZIP plano de splits
con la extensión puesta a mano: en el muestrario, catorce de los dieciséis .apks no lo
llevan.
6.4 extract-apks
Selecciona del APK Set los splits que corresponden a un dispositivo. No hace falta el
dispositivo: basta un fichero de especificación escrito a mano.
cat > pixel.json <<'EOF'
{
"supportedAbis": ["arm64-v8a"],
"supportedLocales": ["es", "en"],
"screenDensity": 420,
"sdkVersion": 34
}
EOF
java -jar bundletool.jar extract-apks --apks=fixture.apks \
--output-dir=extracted --device-spec=pixel.json
ls extracted/
The APKs have been extracted in the directory: extracted
base-arm64_v8a_3.apk
base-es_3.apk
base-master_3.apk
base-xxhdpi_3.apk
De las decenas de splits del APK Set han quedado cuatro: el maestro, la ABI, el idioma y la
densidad. El sufijo _3 confirma que ha elegido la variante del rango de API que corresponde a
sdkVersion 34. Esta es la operación que hay que replicar para fusionar splits, y verla
funcionar con un fichero JSON de cuatro campos deja claro cuál es el modelo mental. Como extra,
get-size total --apks=fixture.apks --device-spec=pixel.json devuelve 8381,8381: el tamaño
de descarga mínimo y máximo para ese dispositivo.
6.5 get-device-spec e install-apks, contra un dispositivo real
Sin adb en el PATH, bundletool se planta antes de empezar:
java -jar bundletool.jar get-device-spec --output=devspec.json
[BT:1.18.3] Error: Unable to determine the location of ADB. Please set the --adb flag or define ANDROID_HOME or PATH environment variable.
Se resuelve con --adb <ruta>. Ejecutado el 13 de agosto de 2026 contra un emulador Android 14
arm64-v8a:
PT=~/Library/Android/sdk/platform-tools
java -jar bundletool.jar get-device-spec --adb=$PT/adb --output=devspec.json
python3 -m json.tool devspec.json | head -8
{
"supportedAbis": [ "arm64-v8a" ],
"supportedLocales": [ "en-US" ],
"deviceFeatures": [ … 89 entradas … ],
"glExtensions": [ … 53 entradas … ],
"screenDensity": 420,
"sdkVersion": 34
}
Doce campos en total: a los seis de arriba se suman sdkRuntime, ramBytes, buildBrand,
buildDevice, socManufacturer y socModel. Los cuatro que deciden qué splits se instalan
son supportedAbis, supportedLocales, screenDensity y sdkVersion; el resto solo entra
en juego si el bundle declara condiciones por deviceFeatures. Por eso el fichero escrito a
mano de cuatro campos de la «sección 6.4 · extract-apks» funciona igual de bien.
Y la instalación completa, de una sola orden:
java -jar bundletool.jar install-apks --adb=$PT/adb --apks fixture.apks
$PT/adb shell pm path com.ejemplo.fixture
Success
package:/data/app/~~XcdbAyYz…==/com.ejemplo.fixture-KGMk_yrD…==/base.apk
package:/data/app/~~XcdbAyYz…==/com.ejemplo.fixture-KGMk_yrD…==/split_config.arm64_v8a.apk
package:/data/app/~~XcdbAyYz…==/com.ejemplo.fixture-KGMk_yrD…==/split_config.xxhdpi.apk
Ese Success no cuadra con la «sección 6.3 · build-apks»: el fixture.apks que se genera allí, sin --ks
y en una máquina sin ~/.android/debug.keystore, sale sin firmar, y Android no instala un
APK sin firma. El APK Set instalado aquí tuvo que generarse firmado, con --ks o con un
keystore de depuración presente. Para repetir la prueba hay que regenerarlo de una de esas dos
maneras.
Tres splits, no cuatro: hay ABI y densidad, pero ningún split de idioma. El emulador
está en en-US y el fixture solo lleva de, es y fr, así que bundletool no instala
ninguno y las cadenas salen del maestro. Es el comportamiento correcto y conviene haberlo visto
una vez, porque un fusionador que asuma «siempre hay un split por dimensión» se equivoca aquí.
7. Qué usar según lo que vayas a hacer después
| Quiero… | Herramienta | Por qué |
|---|---|---|
| Solo mirar el manifiesto y los permisos | aapt2 dump badging |
Instantáneo, no desempaqueta nada |
Inventariar densidades, idiomas y ABI |
aapt2 dump configurations |
Da la lista exacta |
Leer un AXML cualquiera |
aapt2 dump xmltree --file |
Resuelve los nombres de atributo del framework |
| Editar código y reempaquetar | apktool con -r |
El smali es su punto fuerte; -r evita los fallos de aapt2 |
| Editar recursos y reempaquetar | APKEditor decode -t xml + build |
Sin aapt2, sin corrupción |
| Garantizar round-trip byte a byte | APKEditor decode -t raw |
No decodifica, no puede perder |
| Fusionar splits | APKEditor merge |
Es el estándar de facto |
Reconstruir un APK que apktool corrompe |
APKEditor | Es el motivo por el que existe |
Convertir un AAB en APK instalables |
bundletool build-apks |
Es el único que entiende AAB |
| Elegir splits para un dispositivo concreto | bundletool extract-apks |
Con --device-spec, sin dispositivo |
| Decompilar a Java | Ninguna de estas | «Decompiladores a Java» |
| Firmar lo que salga de cualquiera de ellas | Ninguna de estas | «Firma y empaquetado» |
La fila que más importa es la última: ni apktool ni APKEditor ni bundletool cierran el ciclo
— los tres producen un APK que hay que firmar aparte; bundletool, sin --ks, como mucho lo
firma con la clave de depuración, que no sirve para distribuir.
Fuentes
Todas las URL se consultaron el 12 de agosto de 2026.
- Apktool — repositorio oficial — https://github.com/iBotPeaches/Apktool
Vía
https://api.github.com/repos/iBotPeaches/Apktooly/releases/latest. Licencia, versión 3.0.3 del 20 de julio de 2026, push del 11 de agosto de 2026 y recuentos de la ficha 2.1. - Apktool — CLI Parameters — https://apktool.org/docs/cli-parameters/
De aquí salen íntegras las tablas de opciones de
decode,buildeinstall-frameworkde las secciones «2.6» y «2.7», con la redacción de cada flag. - APKEditor — repositorio oficial — https://github.com/REAndroid/APKEditor
Vía la API de GitHub y
/releases/latest. Licencia, versión V1.4.9 del 19 de mayo de 2026 y recuentos de la «sección 3.1 · Ficha». - APKEditor — README — https://raw.githubusercontent.com/REAndroid/APKEditor/master/README.md
Los seis comandos de la «sección 3.2 · Los seis comandos», los tipos de decodificación
xml/json/raw/sigy las opciones de la «sección 3.3 · decode: los tipos de salida», y la declaración de independencia deaapt2. - ARSCLib — repositorio oficial — https://github.com/REAndroid/ARSCLib Vía la API de GitHub. Licencia, fecha de creación, push del 1 de julio de 2026 y recuentos de la «sección 4 · ARSCLib: la biblioteca de debajo».
- aapt2 — documentación oficial de Android — https://developer.android.com/tools/aapt2
La lista de subcomandos y la tabla completa de subcomandos de
dumpcon sus opciones--no-values,--filey-vde la «sección 5.2 · dump: los subcomandos de inspección». - bundletool — documentación oficial de Android — https://developer.android.com/tools/bundletool
Las opciones de
build-apks,extract-apks,install-apksyget-device-spec, y el formato del fichero de especificación de dispositivo de la «sección 6.4 · extract-apks». - bundletool — release 1.18.3 — https://api.github.com/repos/google/bundletool/releases/latest El número de versión y su fecha de publicación, el 15 de diciembre de 2025.
- apktool 3.0.3 ejecutado localmente — instalado con Homebrew el 13 de agosto de 2026 y
ejecutado sobre
com.termux_1002.apkdel muestrario. De aquí sale elapktool.ymlreal de la «sección 2.4 · apktool.yml», con sus siete claves. iBotPeaches/Apktool, issue #3779 — https://github.com/iBotPeaches/Apktool/issues/3779 Consultada el 13 de agosto de 2026 congh api. De aquí salen el título, la fecha de apertura y el estado abierto que confirma el dato de la «sección 2.5 · La dependencia de aapt2, que es el origen de casi todos sus fallos».bundletoolyapksignerejecutados localmente — el 25 de septiembre de 2026, conbundletool1.17.0 yapksignerdebuild-tools37.0.0, sobre un.aabmínimo. Sin--ksy sin~/.android/debug.keystore,build-apksavisa de que no firmará yapksigner verifyrespondeDOES NOT VERIFYsobrebase-master.apk; con undebug.keystorepresente, lo firma y verifica. De aquí sale la nota de la «sección 6.5 · get-device-spec e install-apks, contra un dispositivo real».