Created
February 6, 2026 01:46
-
-
Save stuaxo/2c085fb82375da68c05496ad104b2a5b to your computer and use it in GitHub Desktop.
Install apkm over adb
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| # /// script | |
| # requires-python = ">=3.8" | |
| # /// | |
| """Advanced APKM Installer with Dry-Run and Namespace Awareness.""" | |
| import argparse | |
| import re | |
| import subprocess | |
| import sys | |
| import tempfile | |
| import zipfile | |
| from pathlib import Path | |
| ARCH_SPLITS = { | |
| "arm64-v8a": ["arm64_v8a", "arm64-v8a"], | |
| "armeabi-v7a": ["armeabi_v7a", "armeabi-v7a"], | |
| "x86_64": ["x86_64"], | |
| "x86": ["x86"], | |
| } | |
| def run_adb(*args): | |
| return subprocess.run(["adb", *args], capture_output=True, text=True) | |
| def get_device_info(): | |
| abi = run_adb("shell", "getprop", "ro.product.cpu.abi").stdout.strip() | |
| density = run_adb("shell", "getprop", "ro.sf.lcd_density").stdout.strip() | |
| locale = run_adb("shell", "getprop", "persist.sys.locale").stdout.strip() or \ | |
| run_adb("shell", "getprop", "ro.product.locale").stdout.strip() | |
| lang = locale.split('-')[0].split('_')[0].lower() if locale else "en" | |
| return abi, density, lang | |
| def density_to_split_name(density: str) -> str: | |
| try: d = int(density) | |
| except: return "mdpi" | |
| if d <= 120: return "ldpi" | |
| if d <= 160: return "mdpi" | |
| if d <= 240: return "hdpi" | |
| if d <= 320: return "xhdpi" | |
| if d <= 480: return "xxhdpi" | |
| return "xxxhdpi" | |
| def get_apk_arch(apk_name: str) -> str or None: | |
| name_lower = apk_name.lower() | |
| for abi, markers in ARCH_SPLITS.items(): | |
| if any(m in name_lower for m in markers): return abi | |
| return None | |
| def evaluate_apk(apk_name: str, device_abi: str, device_density: str, device_lang: str): | |
| """Returns (bool: keep, str: reason)""" | |
| name_lower = apk_name.lower() | |
| # 1. Base/Master Logic | |
| if "base" in name_lower or "master" in name_lower: | |
| if not ("config" in name_lower or "split" in name_lower): | |
| return True, "Core Component" | |
| # 2. Arch Check | |
| apk_arch = get_apk_arch(apk_name) | |
| if apk_arch: | |
| return (apk_arch == device_abi), f"Arch: {apk_arch}" | |
| # 3. Density Check | |
| density_markers = ["ldpi", "mdpi", "hdpi", "xhdpi", "xxhdpi", "xxxhdpi"] | |
| for marker in density_markers: | |
| if marker in name_lower: | |
| target = density_to_split_name(device_density) | |
| return (marker == target), f"Density: {marker}" | |
| # 4. Language Check | |
| lang_match = re.search(r'[\._]([a-z]{2})[\._]apk$', name_lower) | |
| if lang_match: | |
| apk_lang = lang_match.group(1) | |
| keep = (apk_lang == device_lang or apk_lang == "en") | |
| return keep, f"Lang: {apk_lang}" | |
| return True, "Feature/Other" | |
| def install_apkm(apkm_path: Path, install_all: bool = False, dry_run: bool = False) -> bool: | |
| if not apkm_path.exists(): | |
| print(f"Error: {apkm_path} not found.") | |
| return False | |
| abi, density, lang = get_device_info() | |
| print(f"\n--- Processing: {apkm_path.name} ---") | |
| print(f"Device Profile: {abi} | {density}dpi | Lang: {lang}\n") | |
| with tempfile.TemporaryDirectory() as tmp: | |
| tmp_path = Path(tmp) | |
| try: | |
| with zipfile.ZipFile(apkm_path, "r") as zf: | |
| zf.extractall(tmp_path) | |
| except: return False | |
| all_apks = sorted(list(tmp_path.rglob("*.apk")), | |
| key=lambda x: ("base" in x.name.lower() or "master" in x.name.lower()), | |
| reverse=True) | |
| selected = [] | |
| print(f"{'STATUS':<10} | {'REASON':<15} | {'FILENAME'}") | |
| print("-" * 60) | |
| for apk in all_apks: | |
| keep, reason = evaluate_apk(apk.name, abi, density, lang) | |
| if install_all: keep, reason = True, "Force All" | |
| status = "KEEP" if keep else "SKIP" | |
| print(f"{status:<10} | {reason:<15} | {apk.name}") | |
| if keep: selected.append(str(apk)) | |
| if dry_run: | |
| print(f"\n[Dry Run] Would install {len(selected)} APKs.") | |
| return True | |
| if not selected: | |
| print("\nNo compatible APKs found.") | |
| return False | |
| print(f"\nInstalling {len(selected)} splits via ADB...") | |
| res = run_adb("install-multiple", "-r", *selected) | |
| if res.returncode == 0: | |
| print("✓ Installation Successful") | |
| return True | |
| else: | |
| print(f"✗ ADB Error: {res.stderr}") | |
| return False | |
| def main(): | |
| parser = argparse.ArgumentParser(description="Namespace-Aware APKM Installer") | |
| parser.add_argument("files", nargs="+", help="APKM files") | |
| parser.add_argument("--all", action="store_true", help="Install all splits") | |
| parser.add_argument("--dry-run", action="store_true", help="Preview selections without installing") | |
| args = parser.parse_args() | |
| for f in args.files: | |
| install_apkm(Path(f), args.all, args.dry_run) | |
| if __name__ == "__main__": | |
| main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment