τTau SolutionsHerramientas

Desempaquetado y reempaquetado

panorama · Revisado el 12 de agosto de 2026

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:

  1. Reconstruye APK que 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».
  2. No hereda los errores de aapt2. Los «resource is private» y los conflictos de namespace de la sección 2.5 sencillamente no ocurren.
  3. No depende de la versión del SDK instalado. Un JAR y Java 8; nada más.
  4. 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 merge o build hay que encadenar apksigner o 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.so en 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 --help y 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/ARSCLibApache-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

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.

  1. Apktool — repositorio oficial — https://github.com/iBotPeaches/Apktool Vía https://api.github.com/repos/iBotPeaches/Apktool y /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.
  2. Apktool — CLI Parameters — https://apktool.org/docs/cli-parameters/ De aquí salen íntegras las tablas de opciones de decode, build e install-framework de las secciones 2.6 y 2.7, con la redacción de cada flag.
  3. 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.
  4. 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/sig y las opciones de la sección 3.3, y la declaración de independencia de aapt2.
  5. 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.
  6. aapt2 — documentación oficial de Android — https://developer.android.com/tools/aapt2 La lista de subcomandos y la tabla completa de subcomandos de dump con sus opciones --no-values, --file y -v de la sección 5.2.
  7. bundletool — documentación oficial de Android — https://developer.android.com/tools/bundletool Las opciones de build-apks, extract-apks, install-apks y get-device-spec, y el formato del fichero de especificación de dispositivo de la sección 6.4.
  8. 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.
  9. Ejecuciones locales sobre el corpus — realizadas con build-tools 37.0.0 (aapt2 2.20-15087165), bundletool 1.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 contra com.termux_1002.apk, org.fossify.calculator_1.4.0.apk y splits/com.ejemplo.fixture.aab del corpus.
  10. 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.