Skip to content

Instantly share code, notes, and snippets.

@jensck
Last active December 11, 2015 02:39
Show Gist options
  • Save jensck/4532039 to your computer and use it in GitHub Desktop.
Save jensck/4532039 to your computer and use it in GitHub Desktop.
Script for Android developers to help find which of jars are causing the most pain re: the dex method count limit (more info: http://goo.gl/kHSox )
#!/usr/bin/env python
"""Util script to determine how many methods are in a given (list of) jar(s).
Requires Python 2.6 or higher (it even works with Python 3!)
Special thanks to "JesusFreke" on StackOverflow for providing the info on how to extract the
method count from a dex file: http://stackoverflow.com/a/14026180/180788
"""
from __future__ import print_function
import glob
import os
import struct
import subprocess
import sys
from textwrap import dedent
def dex_it(jar_path, dex_path):
cmd = "dx --dex --output={dex} {jar} &> /dev/null".format(dex=dex_path, jar=jar_path)
print("Processing '{0}'...".format(jar_path))
subprocess.call(cmd, shell=True)
def get_method_count(dex_path):
with open(dex_path, 'rb') as dex:
# read 88 bytes in, plus the 4 bytes for the actual integer data we want
dex.seek(88)
method_count_bytes = dex.read(4)
return struct.unpack('<I', method_count_bytes)[0]
def print_report(jar_list):
def _process_jar(jar_path):
dex_path = 'dex_counter_temp.dex'
try:
dex_it(jar_path, dex_path)
count = get_method_count(dex_path)
except KeyboardInterrupt:
sys.exit("\n\nCancelled by ^C, exiting.\n")
finally:
# make sure we clean up after ourselves.
try: os.unlink(dex_path)
except: pass
return count
# print "manifest" of jars we're going to inspect
print("Found {0} jars to check:\n".format(len(jar_list)))
for jar in jar_list:
print(jar)
print('\n')
# analyze each jar and immediately give feedback
counts = []
for jar in jar_list:
count = _process_jar(jar)
print("Methods found:", count, "\n")
counts.append((count, jar))
# don't bother for a single jar
if len(jar_list) > 1:
total = 0
# give a list of the worst offenders
print("Worst offenders (jars with the most methods): ")
for count, jar in sorted(counts, reverse=True):
print("{0} methods: {1}".format(count, jar))
total += count
print("Total count:", total)
if total > 65535:
print("DEX METHOD COUNT LIMIT EXCEEDED BY:", total - 65535)
def _get_jar_list():
if len(sys.argv) < 2:
print("No args given; will analyze all jars in current dir\n")
return glob.glob("*.jar")
elif len(sys.argv) == 2 and os.path.isdir(sys.argv[1]):
return glob.glob(sys.argv[1] + "/*.jar")
else:
return [jar_path for jar_path in sys.argv[1:] if jar_path.endswith(".jar")]
def main():
jars = sorted(_get_jar_list())
if not jars:
sys.exit(dedent("""\
ERROR: No jars found to process. Either run the script from a dir with jars in it, or
provide arguments of either:
A) a path with jars in it, OR
B) a list of paths to individual jars"""))
else:
print_report(jars)
print("\n\nDone.")
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment