Introduction: The Shift to Dynamic Delivery and Split APK Architecture
Modern Android application distribution has fundamentally transitioned from monolithic APK files to modular bundles delivered via Google Play Dynamic Feature Delivery. When downloading an app from the Play Store on modern Android devices, the Package Manager does not receive a single, self-contained binary. Instead, it installs a collection of split packages: a base.apk accompanied by multiple configuration splits, such as split_config.arm64_v8a.apk, split_config.xxhdpi.apk, and language packages like split_config.en.apk.
For reverse engineers, penetration testers, and security researchers, analyzing or instrumenting split packages in isolation is notoriously cumbersome. Standard dynamic instrumentation harnesses, emulator sideloading scripts, and offline sandboxes frequently break when fed isolated split fragments. Merging these split bundles (frequently archived as .apks, .xapk, or .apkm files) into a single unified APK restores standard binary behavior, enabling straightforward decompilation, bytecode modification, and single-binary deployment.
In this guide, we walk through the architecture of Android split packages, the technical barriers that arise during consolidation, and step-by-step procedures for unifying Smali bytecode, native ABI libraries, resource tables, and the consolidated AndroidManifest.xml.
Anatomy of a Split Package Bundle
Before attempting to merge components, it is critical to understand the division of responsibilities across the split fragments generated by modern build pipelines and Android App Bundles (AAB):
- Base Package (
base.apk): Contains the primaryclasses.dexbytecode, the fundamental application entry points (e.g., subclasses ofandroid.app.Application, exported Activities, and Content Providers), baseline assets, and root configurations. - ABI Configuration Splits (
split_config.[abi].apk): Encapsulate native compiled shared objects (.sofiles) targeting specific CPU architectures, such asarm64-v8a,armeabi-v7a, orx86_64. They typically contain an empty manifest referencing the parent package and no compiled Java bytecode. - Screen Density Splits (
split_config.[density].apk): Package pre-scaled rasterized drawables (such as PNG/WebP files indrawable-xxhdpi/) and density-specific dimensions, minimizing the download footprint on non-target screens. - Language and Localization Splits (
split_config.[lang].apk): Host language-specific string pools and localized values within targetedresources.arsctables. - Dynamic Feature Modules: Optional on-demand features that introduce distinct dex files (e.g., dynamic feature bytecode), assets, and localized components that load via the Play Core split install API.
Step-by-Step Guide: Unifying Split Packages
Consolidating these disparate archives requires a structured merge pipeline. You can perform these adjustments locally or streamline the decompilation and recompilation workflow directly in your browser using APKTool Studio, which automates file tree exploration, Smali editing, and instant re-signing.
Step 1: Disassembling the Base and Split Packages
Extract all sub-APKs from the archive wrapper (e.g., unpacking the .apks or .xapk zip structure). Once extracted, decompile the base.apk using standard disassembly flags to generate the primary Smali workspace and decoded resource trees. Decompile the ABI and resource splits into isolated temporary directories so their assets can be extracted cleanly without clobbering the base project structure.
Step 2: Manifest Consolidation and Flag Sanitization
The manifest of a split-enabled application contains structural attributes that explicitly instruct the Android Package Manager to reject execution if auxiliary splits are absent. You must remove these constraints inside AndroidManifest.xml.
Locate the root <manifest> element in the disassembled base workspace. Notice attributes such as android:isSplitRequired='true' and split='...'. You must strip these attributes and clean up dynamic split metadata tags inside <application>:
<!-- BEFORE: Original Manifest expecting split runtime delivery -->
<manifest xmlns:android='http://schemas.android.com/apk/res/android'
package='com.example.targetapp'
android:versionCode='2040'
android:versionName='2.4.0'
android:isSplitRequired='true'
split='base'>
<application
android:name='com.example.targetapp.MainApplication'
android:extractNativeLibs='true'>
<meta-data android:name='com.android.dynamic.apk.fused.modules' android:value='base'/>
<meta-data android:name='com.android.vending.splits' android:value='@xml/splits0'/>
</application>
</manifest>
<!-- AFTER: Sanitized Manifest for Standalone Monolithic Execution -->
<manifest xmlns:android='http://schemas.android.com/apk/res/android'
package='com.example.targetapp'
android:versionCode='2040'
android:versionName='2.4.0'>
<application
android:name='com.example.targetapp.MainApplication'
android:extractNativeLibs='true'>
<!-- Removed split-dependent metadata blocks -->
</application>
</manifest>Removing android:isSplitRequired='true' prevents the OS from triggering immediate installation aborts or attempting to connect to Google Play split-fetching services upon initial launch.
Step 3: Merging Native Architecture Binaries (lib/)
Split bundles segregate native libraries into architecture-specific sub-packages. When building a unified APK, you must decide whether you are targeting a single architecture (e.g., arm64-v8a for modern physical devices) or creating a fat universal binary containing both 32-bit and 64-bit binaries.
Extract the lib/ directory from the selected split_config.[abi].apk decompiled tree. Copy the target ABI directories (e.g., lib/arm64-v8a/) directly into the lib/ directory of your disassembled base.apk workspace. Ensure that file permissions on the extracted .so shared objects remain executable and that symlinks (if present) are resolved properly.
Step 4: Merging Dynamic Feature Bytecode and MultiDex Handling
If the application includes dynamic feature modules that contain independent codebases, their respective dex files or Smali representations must be merged into the base package. In Smali projects, MultiDex is organized hierarchically as smali/ (representing classes.dex), smali_classes2/, smali_classes3/, and so forth.
When importing Smali files from a feature split, verify the highest index currently present in the base directory. If base.apk contains up to smali_classes2/, create a new directory named smali_classes3/ and place the disassembled feature classes inside it. Verify that references to split-loading APIs do not trigger runtime null-pointer exceptions.
# Smali check: Ensure MultiDex is invoked if Application doesn't extend MultiDexApplication
.method protected attachBaseContext(Landroid/content/Context;)V
.registers 2
.param p1, "base" # Landroid/content/Context;
invoke-super {p0, p1}, Landroid/app/Application;->attachBaseContext(Landroid/content/Context;)V
# Explicit MultiDex installation ensures classes in smali_classes2+ resolve seamlessly
invoke-static {p0}, Landroidx/multidex/MultiDex;->install(Landroid/content/Context;)V
return-void
.end methodStep 5: Consolidating Resources and Resolving Table Collisions
Merging resource directories (such as res/values/strings.xml, res/drawable-xxhdpi/, and asset directories) requires caution to prevent resource ID clashing. In modern AAB pipelines, resource IDs are partitioned at build time. When combining resources:
- Copy density-specific drawables (e.g.,
res/drawable-xxhdpi/*) into the base project’s corresponding resource folders. - Merge localized values folders (such as
res/values-es/orres/values-de/) into the base project. If specific IDs appear in the split’sres/values/public.xmlthat are absent in the base, merge the declarations into the baseres/values/public.xml, ensuring that hexadecimal IDs match the compiledresources.arscreferences. - Transfer any arbitrary non-compiled assets located in
assets/directly into the baseassets/directory.
Rebuilding and Signing the Unified Package
Once all Smali files, native libraries, drawables, and manifest declarations are consolidated inside the base project workspace, the application is ready for recompilation, 16KB-page boundary alignment, and signature generation.
Using APKTool Studio, you can initiate the build process with a single click. The platform automatically reassembles the Smali trees into valid MultiDex binaries, compiles the unified resources.arsc table, and packages the target archive.
For command-line workflows, execute the build, alignment, and signing steps as follows:
# Step A: Recompile the unified workspace
apktool b base_merged_workspace -o unsigned_unified.apk
# Step B: 16KB Page Alignment (Mandatory for Android 15 and 16 target environments)
zipalign -p -f -v 16 unsigned_unified.apk aligned_unified.apk
# Step C: Sign using APK Signature Schemes v2 and v3
apksigner sign --ks custom_release.keystore \
--ks-key-alias my-alias \
--v2-signing-enabled true \
--v3-signing-enabled true \
--out final_unified.apk \
aligned_unified.apk
# Step D: Confirm signature validity
apksigner verify --verbose final_unified.apkTesting and Verifying Monolithic Execution
Before deploying the unified binary to dynamic testing frameworks or hardware testing benches, verify that the application operates without silent resource missing errors. Connect your test device and install the package using ADB:
adb install -r final_unified.apkDuring test execution, monitor logcat for any lingering dynamic module lookup failures:
adb logcat -s AndroidRuntime:E SplitCompat:D PlayCore:VIf you encounter exceptions related to SplitCompat.installActivity or dynamic feature missing delegates, inspect the application initialization Smali code to stub out legacy Play Core split verification calls, ensuring the application treats the unified bundle as an all-in-one local installation.
Conclusion
Deconstructing and unifying Android Split APKs bridges the gap between modern, modular app delivery systems and traditional reverse-engineering analysis pipelines. By carefully stripping split-enforcing manifest attributes, merging target ABI native libraries into the primary directory tree, mapping Smali files across MultiDex indexes, and reconciling public resource tables, security analysts can produce fully functional, monolithic APKs ready for seamless instrumentation and debugging.
Whether you prefer manual terminal tooling or cloud-based workspace automation via APKTool Studio, mastering split unification is an indispensable skill in modern Android security research.