apkfile • Read, inspect, and install Android app packages
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)
# 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
# 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
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 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.ApkFileobjects obtained this way havepath is Noneuntil you call.save(path)on them. - The library can also install files (optionally checking compatibility first:
min_sdk_version,abis, andlangs/densities for split apks) using adb — connect a device and call.install(), or use the standaloneinstall_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 apathlib.Path, not astr. Comparisons against a bare string (apk.path == "/some/path") no longer match — compare againstPath("/some/path"), or usestr(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 onApkFileand every bundle class (bundle.signing/.securitydelegate to the base apk;.size_breakdown/.dex_infosum base + splits). - Two behavior-affecting bug fixes, verified against the official Android manifest/NDK docs:
Abi.is_compatible_with()no longer claimsx86/x86_64devices can runarm/arm64code — stock Android has no built-in ARM↔x86 translation layer, so that was always wrong and could have causedinstall_apks()to push an incompatible native-code split onto an x86 emulator.InstallLocation's default (whenandroid:installLocationisn't declared) is nowINTERNAL_ONLY, per the docs — it was previously (incorrectly) reported asAUTO.
ExportedComponentgainedread_permission/write_permission(for<provider>'sandroid:readPermission/android:writePermission), and a provider's defaultexportedvalue is now resolved correctly — it depends ontargetSdkVersion(Trueup to API 16,Falsefrom API 17), unlike activities/services/ receivers, whose default instead depends on whether they declare an<intent-filter>.ApkFile.iconsis nowtuple[Icon, ...](wasdict[int, str]) — eachIconhas.density,.bucket(aDensityBucket),.path, and self-serving.read_bytes()/.extract(path)methods. It's also complete now: the old implementation missedanydpi(adaptive icon) andnodpivariants entirely. Useapk.best_icon(max_dpi=...)to pick a single icon the wayandroguard/Android itself would.ApkFile.supported_screensis nowtuple[ScreenSize, ...](wastuple[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 onCertificate:public_key_algorithm/public_key_bit_size,canonical_subject/canonical_issuer(Java-X500Principal-compatible identity strings, safe for comparison unlikesubject/issuer), and onSigningInfo:has_duplicate_signature_ids(a tamper/verifier-confusion smell).
Migrating from 0.x
apkfile 1.0 is a from-scratch rewrite. The highlights:
aaptis gone. Everyaapt_pathparameter has been removed, as hasget_raw_aapt().extract_path,delete_extracted_files(), and thewith 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, andSplitTypearestrenums 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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file apkfile-1.3.0.tar.gz.
File metadata
- Download URL: apkfile-1.3.0.tar.gz
- Upload date:
- Size: 47.4 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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d60a58b97a0009550cb0fac8f02f489432af1e4cd9c308a050a3aa7b2f24d0fd
|
|
| MD5 |
e3661902ca2fe84a83982fa3d3b14922
|
|
| BLAKE2b-256 |
86ffd39de7edb78e994793eb236b598bf95a931e9f5aa3dc62fde3fbe04df3d9
|
File details
Details for the file apkfile-1.3.0-py3-none-any.whl.
File metadata
- Download URL: apkfile-1.3.0-py3-none-any.whl
- Upload date:
- Size: 57.9 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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
dc25d1a8b3e7df99836dc68666ada87782718322a85c4bf6131da506f232cebf
|
|
| MD5 |
7644abec4b700b4fe4e38d7baddbd89e
|
|
| BLAKE2b-256 |
c6d72b044d8b8a84dfae6c68a955fe018128000962612c36f7f14b5f659d5328
|