Skip to content

Instantly share code, notes, and snippets.

@cheeseonamonkey
Last active November 17, 2025 12:05
Show Gist options
  • Select an option

  • Save cheeseonamonkey/da365acc2baa4363c6e2254f70f56233 to your computer and use it in GitHub Desktop.

Select an option

Save cheeseonamonkey/da365acc2baa4363c6e2254f70f56233 to your computer and use it in GitHub Desktop.
re-compress apk
#!/bin/bash
recompress_apk() {
local dry_run=0 png_opt=6 jpg_q=70 zip_lv=9 webp_q=85 use_webp=0 use_7z=0 zopfli_i=50 advzip_i=100 lossy_png=0
while [[ $# -gt 0 ]]; do
case "$1" in
-d|--dry-run) dry_run=1; shift ;;
-p|--png-opt) png_opt=$2; shift 2 ;;
-j|--jpg-quality) jpg_q=$2; shift 2 ;;
-z|--zip-level) zip_lv=$2; shift 2 ;;
-w|--webp) use_webp=1; webp_q=${2:-85}; shift $([[ $2 =~ ^[0-9]+$ ]] && echo 2 || echo 1) ;;
-7|--7zip) use_7z=1; shift ;;
--zopfli-iter) zopfli_i=$2; shift 2 ;;
--advzip-iter) advzip_i=$2; shift 2 ;;
--lossy-png) lossy_png=1; shift ;;
-h|--help) cat << 'EOF'
Usage: recompress_apk [FLAGS] <apk> [keystore]
FLAGS:
-d, --dry-run Stop before repackaging (test compression)
-p, --png-opt N PNG level 0-6 (default: 6, oxipng)
-j, --jpg-quality N JPEG quality 0-100 (default: 70)
-w, --webp [Q] Convert to WebP (default: 85)
-z, --zip-level N ZIP compression 0-9 (default: 9)
-7, --7zip Use 7z ultra (SLOW, 2-5% better)
--zopfli-iter N Zopfli iterations (default: 50)
--advzip-iter N Advzip iterations (default: 100)
--lossy-png Palette reduction (pngquant, lossy)
EXAMPLES:
recompress_apk app.apk # Standard (strip+optimize)
recompress_apk -w 80 app.apk # WebP @ 80 quality
recompress_apk -j 60 -p 6 --zopfli-iter 100 app.apk # Aggressive lossless
recompress_apk --lossy-png -w 70 -7 app.apk # Maximum lossy compression
recompress_apk -7 --advzip-iter 200 app.apk # Release build quality
EXTREME: recompress_apk --lossy-png -w 75 -7 --zopfli-iter 100 --advzip-iter 200 app.apk
EOF
return 0 ;;
-*) echo "Error: Unknown flag $1"; return 1 ;;
*) break ;;
esac
done
[[ $# -lt 1 ]] && { echo "Usage: recompress_apk [FLAGS] <apk> [keystore]"; return 1; }
local apk=$1 keystore=$2
[[ ! -f $apk ]] && { echo "Error: APK not found"; return 1; }
echo "=== Tool Check ==="
for t in zopflipng:zopfli oxipng jpegoptim cwebp:webp pngquant advzip:advancecomp 7z strip zipalign apksigner; do
IFS=: read -r cmd pkg <<< "$t"
command -v "$cmd" &>/dev/null && echo "✓ $cmd" || echo "✗ $cmd${pkg:+ ($pkg)}"
done; echo
# Keystore handling
if [[ -z $keystore ]]; then
keystore=${RECOMPRESS_KEYSTORE:-}
[[ -z $keystore ]] && read -rp "Keystore: " keystore || echo "Cached keystore"
export RECOMPRESS_KEYSTORE=$keystore
fi
[[ ! -f $keystore ]] && { unset RECOMPRESS_KEYSTORE; echo "Error: Keystore not found"; return 1; }
# Password handling
if [[ -z $RECOMPRESS_PASSWORD ]]; then
local cfg=$(dirname "$keystore")/config.yml
[[ -f $cfg ]] && RECOMPRESS_PASSWORD=$(awk '/keystorepass:/{gsub(/"/, "", $2); print $2}' "$cfg")
[[ -z $RECOMPRESS_PASSWORD ]] && read -rsp "Password: " RECOMPRESS_PASSWORD && echo || echo "Cached password"
fi
export RECOMPRESS_PASSWORD
local tmp="/tmp/rc_$$" zip="/tmp/rc_$$.zip" out="${apk%.apk}_aligned.apk" orig=$(stat -c%s "$apk")
trap "rm -rf '$tmp' '$zip'" EXIT
delta() { awk -v p=$1 -v c=$2 'BEGIN{s=p-c;printf" -%.2fMB (-%.1f%%)\n",s/1048576,s/p*100}'; }
echo "Extracting..."; mkdir -p "$tmp" && unzip -q "$apk" -d "$tmp" || return 1
local sz=$(du -sb "$tmp"|awk '{print $1}')
echo -n "Removing META-INF..."
rm -rf "$tmp/META-INF"
local new_sz=$(du -sb "$tmp"|awk '{print $1}')
delta "$sz" "$new_sz"
sz=$new_sz
# Clean junk files
echo -n "Cleaning junk..."
local junk=0
while IFS= read -r -d '' f; do junk=$((junk+$(stat -c%s "$f"))); rm "$f"; done < <(find "$tmp" -type f \( -name "*.md" -o -name "*.txt" -o -name NOTICE -o -name LICENSE -o -name "*.pro" -o -name "*.properties" \) -print0)
find "$tmp" -type d -name __MACOSX -exec rm -rf {} + 2>/dev/null
[[ $junk -gt 0 ]] && awk -v s=$junk 'BEGIN{printf" -%.2fMB\n",s/1048576}' || echo " 0B"
# Strip native libs
echo "Stripping libs..."
local strip_s=0 libs=0
while IFS= read -r -d '' f; do
local b=$(stat -c%s "$f"); strip --strip-all "$f" 2>/dev/null || true
strip_s=$((strip_s+b-$(stat -c%s "$f"))); echo -ne "\r $((++libs)) libs"
done < <(find "$tmp" -type f -name "*.so" -print0)
[[ $libs -gt 0 ]] && echo -e "\r $libs libs: -$(awk -v s=$strip_s 'BEGIN{printf"%.2fMB",s/1048576}')"
# Image compression
if [[ $use_webp -eq 1 ]]; then
command -v cwebp &>/dev/null || { echo "WARNING: cwebp missing"; }
echo "WebP (q=$webp_q)..."
local saved=0 cnt=0
while IFS= read -r -d '' f; do
local b=$(stat -c%s "$f")
cwebp -q $webp_q -m 6 -mt "$f" -o "${f%.*}.webp" &>/dev/null && rm "$f"
saved=$((saved+b-$(stat -c%s "${f%.*}.webp" 2>/dev/null||echo 0)))
echo -ne "\r $((++cnt))"
done < <(find "$tmp" -type f \( -iname "*.png" -o -iname "*.jpg" -o -iname "*.jpeg" \) -print0)
[[ $cnt -gt 0 ]] && echo -e "\r $cnt: -$(awk -v s=$saved 'BEGIN{printf"%.2fMB",s/1048576}')"
else
echo "Compressing..."
local png_s=0 jpg_s=0 pngs=0 jpgs=0 tool=""
# PNG compression (priority: pngquant > zopflipng > oxipng)
if [[ $lossy_png -eq 1 ]] && command -v pngquant &>/dev/null; then
tool="pngquant"
while IFS= read -r -d '' f; do
local b=$(stat -c%s "$f")
pngquant --force --ext .png --quality 50-85 --speed 1 "$f" 2>/dev/null || true
png_s=$((png_s+b-$(stat -c%s "$f"))); echo -ne "\r PNG:$((++pngs))"
done < <(find "$tmp" -type f -iname "*.png" -print0)
elif command -v zopflipng &>/dev/null; then
tool="zopflipng"
while IFS= read -r -d '' f; do
local b=$(stat -c%s "$f")
zopflipng -y --iterations=$zopfli_i --lossy_transparent --lossy_8bit "$f" "$f" 2>/dev/null || true
png_s=$((png_s+b-$(stat -c%s "$f"))); echo -ne "\r PNG:$((++pngs))"
done < <(find "$tmp" -type f -iname "*.png" -print0)
elif command -v oxipng &>/dev/null; then
tool="oxipng"
while IFS= read -r -d '' f; do
local b=$(stat -c%s "$f")
oxipng -q -o $png_opt --fix "$f" 2>/dev/null || true
png_s=$((png_s+b-$(stat -c%s "$f"))); echo -ne "\r PNG:$((++pngs))"
done < <(find "$tmp" -type f -iname "*.png" -print0)
fi
# JPG compression
if command -v jpegoptim &>/dev/null; then
while IFS= read -r -d '' f; do
local b=$(stat -c%s "$f")
jpegoptim -q -m $jpg_q --strip-all "$f" 2>/dev/null || true
jpg_s=$((jpg_s+b-$(stat -c%s "$f"))); echo -ne "\r JPG:$((++jpgs))"
done < <(find "$tmp" -type f \( -iname "*.jpg" -o -iname "*.jpeg" \) -print0)
fi
[[ -n $tool ]] && echo -ne "\r $tool "
[[ $pngs -gt 0 ]] && echo -e "\r PNG: $pngs (-$(awk -v s=$png_s 'BEGIN{printf"%.2fMB",s/1048576}'))"
[[ $jpgs -gt 0 ]] && echo " JPG: $jpgs (-$(awk -v s=$jpg_s 'BEGIN{printf"%.2fMB",s/1048576}'))"
fi
new_sz=$(du -sb "$tmp"|awk '{print $1}')
echo -n "Total compression:"
delta "$sz" "$new_sz"
sz=$new_sz
[[ $dry_run -eq 1 ]] && { echo "[dry-run] done"; return 0; }
# Repackage
echo -n "Re-zipping..."
if [[ $use_7z -eq 1 ]] && command -v 7z &>/dev/null; then
echo -n " (7z ultra)"
(cd "$tmp" && 7z a -tzip -mx=9 -mfb=273 -mpass=15 "$zip" . &>/dev/null) || return 1
else
(cd "$tmp" && zip -q -r -$zip_lv "$zip" .) || return 1
fi
local zip_sz=$(stat -c%s "$zip")
delta "$sz" "$zip_sz"
# Advzip post-processing
if command -v advzip &>/dev/null; then
echo -n " advzip($advzip_i)..."
advzip -z -4 -i $advzip_i "$zip" 2>&1 | grep -v "^$" || true
local new_zip=$(stat -c%s "$zip")
[[ $new_zip -lt $zip_sz ]] && awk -v b=$zip_sz -v a=$new_zip 'BEGIN{printf" -%.2fMB (-%.1f%%)\n",(b-a)/1048576,(b-a)/b*100}' || echo ""
zip_sz=$new_zip
fi
# Align & sign
echo -n "Aligning..."
zipalign -f -p 4 "$zip" "$out" 2>&1|grep -v "^Verification" || return 1
local aligned_sz=$(stat -c%s "$out")
awk -v z=$zip_sz -v a=$aligned_sz 'BEGIN{d=a-z;printf" %s%.2fMB\n",d>0?"+":"-",d<0?-d/1048576:d/1048576}'
echo "Signing..."
apksigner sign --ks "$keystore" --ks-pass "pass:$RECOMPRESS_PASSWORD" --v2-signing-enabled --min-sdk-version 1 "$out" 2>/dev/null || \
{ echo "Error: Sign failed"; unset RECOMPRESS_KEYSTORE RECOMPRESS_PASSWORD; return 1; }
echo "Validating..."
apksigner verify "$out" 2>&1|grep -qE "(Verified|ERROR)" || { echo "Error: Invalid"; rm "$out"; return 1; }
# Test install
echo "Test install..."
if command -v adb &>/dev/null && adb devices|grep -q "device$"; then
adb install -r "$out" 2>&1|grep -q "Success" && echo " OK (on device)" || echo " FAIL"
else
echo " Skip (no device)"
fi
awk -v o=$orig -v f=$(stat -c%s "$out") -v n="$out" 'BEGIN{s=o-f;printf"\n=== FINAL ===\n%s\n %.2fMB -> %.2fMB (-%.2fMB / -%.1f%%)\n",n,o/1048576,f/1048576,s/1048576,s/o*100}'
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment