apkfile • Read, inspect, and install Android app packages
apkfile reads metadata out of Android .apk, .apkm, .xapk, and .apks files — package name, version,
permissions, supported ABIs/languages/densities, icons, signing certificates, manifest security posture
(exported components, deep links, dangerous permissions), size/DEX composition, and more — and can install
them to a connected device over adb, or diff two apks against each other.
Full documentation: apkfile.readthedocs.io
pip install -U apkfile
# or
uv add apkfilefrom apkfile import ApkFile, XapkFile, ApkmFile, ApksFile
# 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)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/adbapkfile 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.
If you want to use .install(), you need adb.
- You can manually provide a path to
adb:apk.install(adb_path="/path/to/adb").
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).
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.