Desempaquetado y reempaquetado
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. El censo 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).
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», el mismo principio que conviene aplicar 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.
| Campo | Para qué sirve |
|---|---|
version |
Versión de apktool que hizo el decode |
apkFileName |
Nombre del fichero original |
isFrameworkApk |
Si lo decodificado es un framework y no una aplicación |
usesFramework |
Qué frameworks —y con qué tag— hacen falta para reconstruir |
sdkInfo |
minSdkVersion y targetSdkVersion, para repoblarlos en el manifiesto al construir |
packageInfo |
Renombrado de paquete: detecta la diferencia entre el paquete de recursos y el del manifiesto y aplica --rename-manifest-package |
versionInfo |
versionCode y versionName, para repoblarlos al construir |
compressionType |
Si resources.arsc venía comprimido, para replicarlo |
sharedLibrary |
Si el APK es una biblioteca compartida, para usar --shared-lib al construir |
sparseResources |
Si la tabla de recursos usaba codificación sparse |
unknownFiles |
Nombre y ubicación de los ficheros no estándar, para recolocarlos |
doNotCompress |
Qué entradas deben quedar stored y no deflate |
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.
⚠️ sin verificar: el significado de cada campo procede de una agregación de fuentes de la
comunidad y del gestor de incidencias del propio proyecto, no de una página de documentación
oficial que los enumere. La página apktool.org/docs/the-basics/apktool-yml devolvió HTTP 404
el 12 de agosto de 2026. Los nombres de los campos sí están confirmados; la redacción exacta de
sus definiciones, no.
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 el
censo 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 le pasa a APK Editor Studio, congelada en apktool 2.x desde enero de 2025.
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 falla con -s, 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 / --force-manifest |
Solo el manifiesto / el manifiesto pase lo que pase |
--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 |
--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 hoy la referencia funcional del ecosistema.
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. El censo 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 el único del lote de trece apto para CI y automatización: todo lo demás es interfaz gráfica o extensión de editor. El recuento completo está en Comparativa y selección, sección 4.2.
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: 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: las demás herramientas que hacen merge lo hacen empaquetando APKEditor por dentro, y 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—, 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. 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). 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 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 desde septiembre de 2025 sin respuesta: alrededor de veinte
aplicaciones fusionadas desde Play caen con segfault en
libpairipcore.soen Android 14 y 15. Afecta a las protegidas con PairIP, cada vez más comunes. - Issue #213, la de Play Protect de la sección 3.5.
- 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.
Las dos issues sin respuesta desde hace casi un año son el dato de mantenimiento que más pesa: no es que el proyecto esté muerto, es que los fallos difíciles se quedan sin atender.
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 para cualquiera que se plantee
reimplementar un lector-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
CORPUS=~/corpus-apk
$BT/aapt2 dump badging $CORPUS/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 $CORPUS/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 enseñan las extensiones de los contenedores: el nombre miente, y la
identidad se lee de dentro.
configurations — el inventario de variantes.
$BT/aapt2 dump configurations $CORPUS/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 $CORPUS/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 $CORPUS/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.
Reproducir todo eso es un proyecto en sí mismo, y la conclusión sensata es la contraria: 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:
CORPUS=~/corpus-apk
java -jar bundletool.jar dump manifest --bundle $CORPUS/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 $CORPUS/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=$CORPUS/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 es literal y hay que hacerle caso: sin --ks, --ks-pass, --ks-key-alias y
--key-pass, el resultado no se instala. 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 hay que imponerse al escribir
un ZIP que se quiera poder comparar byte a byte. 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 corpus de verificación, 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 Lo que no se puede ejecutar aquí
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.
com.android.tools.build.bundletool.model.exceptions.CommandExecutionException: Unable to determine the location of ADB. …
⚠️ No ejecutado localmente: get-device-spec e install-apks requieren adb y un
dispositivo Android conectado, que no existe en el entorno de referencia. En la máquina de
referencia adb está en ~/Library/Android/sdk/platform-tools/adb pero no en el PATH: con
--adb <ruta> se resuelve lo primero, lo segundo no. El error de arriba sí es salida real.
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.
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. - APKEditor — README — https://raw.githubusercontent.com/REAndroid/APKEditor/master/README.md
Los seis comandos de la sección 3.2, los tipos de decodificación
xml/json/raw/sigy las opciones de la sección 3.3, 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.
- 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. - 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. - 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.
- Ejecuciones locales sobre el corpus — realizadas con
build-tools37.0.0 (aapt22.20-15087165),bundletool1.18.3 y OpenJDK 21.0.10 sobre macOS Darwin 25.5.0. De aquí salen todas las salidas pegadas de las secciones 5.2 y 6, ejecutadas contracom.termux_1002.apk,org.fossify.calculator_1.4.0.apkysplits/com.ejemplo.fixture.aabdel corpus. - Censo de las trece herramientas de manipulación de
APK— el inventario de Comparativa y selección, sección 4, con los datos de cada herramienta tomados de su repositorio, su página de descargas y su documentación el 11 y el 12 de agosto de 2026. De aquí salen las issues #211 y #213 de APKEditor, la #228 de APKLab, la #50 de APKToolGUI, las cifras de descargas y el hecho de que APKEditor sea el único del lote con CLI real.