Skip to main content

apkfile logo

apkfile • Read, inspect, and install Android app packages

PyPI Version Downloads Python Versions Tests pre-commit.ci status Coverage Docs License Code Quality

apkfile reads metadata out of Android .apk, .apkm, .xapk, .apks, and .apkv files — package name, version, permissions, supported ABIs/languages/densities, icons, signing certificates, manifest security posture (exported components, deep links, dangerous permissions), size/DEX composition, OBB expansion files, and more — and can install them to a connected device over adb, or diff two apks against each other.

Full documentation: apkfile.readthedocs.io

Install

pip install -U apkfile
# or
uv add apkfile

Usage

from apkfile import ApkFile, XapkFile, ApkmFile, ApksFile, ApkvFile

# Get apk info
apk = ApkFile("/home/david/Downloads/wa.apk")
print(apk.package_name, apk.version_name, apk.version_code)
print(apk.as_dict())

# Signing certificates, manifest security posture, size/DEX composition
print(apk.signing.is_debug_signed, [c.sha256 for c in apk.signing.all_certificates])
print(apk.security.dangerous_permissions, apk.security.unprotected_exported_components)
print(apk.size_breakdown, apk.dex_info)

# Diff two apks (e.g. two versions of the same app)
old, new = ApkFile("wa-1.apk"), ApkFile("wa-2.apk")
result = old.diff(new)
print(result.permissions_added, result.size_delta, result.signing_changed)

# A quick manifest security report
for component in apk.security.unprotected_exported_components:
    print(
        f"{component.type.value} {component.name} is exported with no permission required"
    )
for name in apk.security.dangerous_permissions:
    print(f"requests dangerous permission: {name}")

# Check whether a split apk can run on a given device ABI
from apkfile import Abi

device_abi = Abi.ARM64
print(all(device_abi.is_compatible_with(a) for a in apk.abis))

# Icons are objects, not just paths -- pick one and extract it
icon = apk.best_icon(max_dpi=320)
icon.extract("icon.png")  # or icon.read_bytes()
print(icon.density, icon.bucket)  # e.g. 320 DensityBucket.XHDPI

# Get apkm info — base/splits are read lazily, straight out of the archive
apkm = ApkmFile("/home/david/Downloads/chrome.apkm")
for split in apkm.splits:
    print(split.split_name, split.split_type)
apkm.install(check=True, upgrade=True)

# Get xapk info
xapk = XapkFile("/home/david/Downloads/telegram.xapk")
print(xapk.abis, xapk.permissions, xapk.langs)

# Get apks info
apks = ApksFile("/home/david/Downloads/facebook.apks")
print(apks.base.permissions, apks.md5, apks.sha256)

# Build a real, on-disk .apks bundle from a folder of apks (a base apk + its splits)
built = ApksFile.create(
    "/home/david/Downloads/facebook_apks/"
)  # or an explicit list of paths
print(
    built.path
)  # com.facebook.katana-<version_code>.apks in the current dir, by default

# Get apkv info (VInstall's format — https://github.com/vinstall/apkv-spec); optionally encrypted
apkv = ApkvFile(
    "/home/david/Downloads/backup.apkv", password="hunter2"
)  # password=None if unencrypted
print(apkv.app_name, apkv.exported_at)

# XAPK-bundled OBB expansion files, and pushing a standalone OBB alongside a plain apk ("apk + obb")
for obb in xapk.obb_files:
    print(obb.name, obb.is_patch, obb.size)
xapk.install()  # bundled OBBs are pushed to /sdcard/Android/obb/<package>/ automatically
apk.install(obb_paths=["main.1.com.example.game.obb"])

# Installing to every connected device happens in parallel, not one at a time
apk.install(grant_permissions=True, allow_downgrade=True)  # pm install -g -d

# Launch the app after installing (main activity by default, or a specific one)
apk.install(launch=True)
apk.install(launch_activity="com.example.app.SettingsActivity")

# Uninstall (same multi-device behavior as install: every connected device, in parallel)
apk.uninstall()
from apkfile import uninstall_apks

uninstall_apks("com.example.app", keep_data=True)

CLI

apkfile info app.apk              # print an apk/bundle's metadata as JSON (--full for every detail)
apkfile pack /path/to/apk_folder  # build a real, on-disk .apks bundle from a base apk + splits
apkfile diff old.apk new.apk      # print the differences between two apks/bundles as JSON
apkfile install app.apk           # install to connected device(s)
apkfile install app.apk --upgrade --installer com.android.vending --adb-path /path/to/adb
apkfile install app.apk --launch  # ...and launch it afterwards (main activity, or --launch-activity)
apkfile uninstall com.example.app # uninstall from connected device(s)
apkfile uninstall app.apk         # ...or by apk/bundle path, reading its package name

How this library works

apkfile parses AndroidManifest.xml and resources.arsc directly, using androguard — a pure-Python library, so there's nothing to install beyond apkfile itself.

  • For the archive formats (.apkm, .xapk, .apks), basic info (package_name, version_name, version_code, ...) comes from the archive's own JSON manifest. Everything else (base, splits, permissions, languages, ABIs, ...) is parsed lazily, directly from the archive's bytes the first time you access it — no disk extraction happens just to read metadata. ApkFile objects obtained this way have path is None until you call .save(path) on them.
  • The library can also install files (optionally checking compatibility first: min_sdk_version, abis, and langs/densities for split apks) using adb — connect a device and call .install(), or use the standalone install_apks() function directly. Installing extracts only what's needed into a temporary directory for the duration of the push, and cleans up automatically afterwards.

Install adb

If you want to use .install(), you need adb.

  • You can manually provide a path to adb: apk.install(adb_path="/path/to/adb").

Migrating from 1.0

  • ApkFile.path (and every bundle's .path) is now a pathlib.Path, not a str. Comparisons against a bare string (apk.path == "/some/path") no longer match — compare against Path("/some/path"), or use str(apk.path).
  • New: .signing (SigningInfo — signing scheme(s) + certificate(s)), .security (SecurityInfo — permissions with AOSP protection levels, exported components, deep links, debuggable/allowBackup/ cleartext-traffic flags), .size_breakdown (SizeBreakdown — size by dex/resources/native libs/assets/...), .dex_info (DexInfo — method/class/string counts), and .diff(other) / apkfile.diff.diff(a, b) for comparing two apks. All available on ApkFile and every bundle class (bundle .signing/.security delegate to the base apk; .size_breakdown/.dex_info sum base + splits).
  • Two behavior-affecting bug fixes, verified against the official Android manifest/NDK docs:
    • Abi.is_compatible_with() no longer claims x86/x86_64 devices can run arm/arm64 code — stock Android has no built-in ARM↔x86 translation layer, so that was always wrong and could have caused install_apks() to push an incompatible native-code split onto an x86 emulator.
    • InstallLocation's default (when android:installLocation isn't declared) is now INTERNAL_ONLY, per the docs — it was previously (incorrectly) reported as AUTO.
  • ExportedComponent gained read_permission/write_permission (for <provider>'s android:readPermission /android:writePermission), and a provider's default exported value is now resolved correctly — it depends on targetSdkVersion (True up to API 16, False from API 17), unlike activities/services/ receivers, whose default instead depends on whether they declare an <intent-filter>.
  • ApkFile.icons is now tuple[Icon, ...] (was dict[int, str]) — each Icon has .density, .bucket (a DensityBucket), .path, and self-serving .read_bytes()/.extract(path) methods. It's also complete now: the old implementation missed anydpi (adaptive icon) and nodpi variants entirely. Use apk.best_icon(max_dpi=...) to pick a single icon the way androguard/Android itself would. ApkFile.supported_screens is now tuple[ScreenSize, ...] (was tuple[str, ...]).
  • New fields: max_sdk_version, form_factors (tuple[FormFactor, ...] — TV/wearable heuristics), SecurityInfo.implied_permissions (permissions Android silently grants under legacy compatibility rules), and on Certificate: public_key_algorithm/public_key_bit_size, canonical_subject/canonical_issuer (Java-X500Principal-compatible identity strings, safe for comparison unlike subject/issuer), and on SigningInfo: has_duplicate_signature_ids (a tamper/verifier-confusion smell).

Migrating from 0.x

apkfile 1.0 is a from-scratch rewrite. The highlights:

  • aapt is gone. Every aapt_path parameter has been removed, as has get_raw_aapt().
  • extract_path, delete_extracted_files(), and the with XapkFile(...) as xf: context-manager pattern are gone — reading metadata never touches disk anymore, so there's nothing to clean up. .install() still uses a temporary directory, but manages it internally.
  • Exceptions are now a proper hierarchy under apkfile.ApkFileError (InvalidApkError, InvalidBundleError, AdbError, AdbNotFoundError) instead of repurposed builtins.
  • Abi, InstallLocation, and SplitType are str enums now — comparisons against plain strings (apk.install_location == "auto") still work.
  • Minimum supported Python version is 3.10.

See CHANGELOG.md for the full list.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

apkfile-1.4.1.tar.gz (52.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

apkfile-1.4.1-py3-none-any.whl (62.2 kB view details)

Uploaded Python 3

File details

Details for the file apkfile-1.4.1.tar.gz.

File metadata

  • Download URL: apkfile-1.4.1.tar.gz
  • Upload date:
  • Size: 52.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.12.5 {"installer":{"name":"uv","version":"0.12.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for apkfile-1.4.1.tar.gz
Algorithm Hash digest
SHA256 9f025453c9c856f3e4fdd1bc5249d55312a01daa80cf6b64624a9fe6a17a8798
MD5 3c1de330b17f8f4e03b5cec620d5d95b
BLAKE2b-256 c535ec4bd205fafacf37acc6fa94b3c07498db319a887b8009dd6af0b6fedcf5

See more details on using hashes here.

File details

Details for the file apkfile-1.4.1-py3-none-any.whl.

File metadata

  • Download URL: apkfile-1.4.1-py3-none-any.whl
  • Upload date:
  • Size: 62.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.12.5 {"installer":{"name":"uv","version":"0.12.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for apkfile-1.4.1-py3-none-any.whl
Algorithm Hash digest
SHA256 b2c49ba8180c1465a63c843f0208c8746596c3178fd1cc93ebed8699678d5a73
MD5 a71e298c381ebdf82f6607c3af2359f9
BLAKE2b-256 305a1fd5fa4aa249138619098ac360475d9ef57cf2bccccbda515679b7f78ecc

See more details on using hashes here.

Release history Release notifications | RSS feed

1.4.2

2 files

This release

1.4.1 This release

2 files

1.4.0

2 files

1.3.0

2 files

1.2.0

2 files

1.1.0

2 files

1.0.1

2 files

1.0.0

2 files

0.1.7

2 files

0.1.6

1 file

0.1.5

1 file

0.1.4

1 file

0.1.3

1 file

0.1.2

1 file

0.1.1

1 file

0.1.0

1 file

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page